2018-10-11 21:28:52 -04:00
|
|
|
package main // import "github.com/otremblay/sharethis"
|
|
|
|
|
|
|
|
|
|
import (
|
2018-10-12 07:10:07 -04:00
|
|
|
"crypto/sha256"
|
2018-10-11 21:28:52 -04:00
|
|
|
"encoding/gob"
|
|
|
|
|
"flag"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"log"
|
|
|
|
|
"net"
|
|
|
|
|
"net/http"
|
|
|
|
|
"os"
|
2018-10-12 07:10:07 -04:00
|
|
|
"os/user"
|
|
|
|
|
"path/filepath"
|
2018-10-11 21:28:52 -04:00
|
|
|
"strings"
|
2018-10-12 19:04:45 -04:00
|
|
|
"sync"
|
2018-10-11 21:28:52 -04:00
|
|
|
"syscall"
|
|
|
|
|
|
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
|
"golang.org/x/crypto/ssh/agent"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type FileReq struct {
|
2018-10-15 07:29:55 -04:00
|
|
|
Path string
|
|
|
|
|
ShareCount uint
|
|
|
|
|
serverconn *ssh.ServerConn
|
2018-10-11 21:28:52 -04:00
|
|
|
}
|
|
|
|
|
|
2018-10-17 21:22:32 -04:00
|
|
|
var authorizedKeys = os.Getenv("SHARETHIS_AUTHORIZEDKEYS")
|
|
|
|
|
|
2018-10-11 21:28:52 -04:00
|
|
|
func main() {
|
|
|
|
|
bg := flag.Bool("bg", false, "sends the process in the background")
|
|
|
|
|
server := flag.Bool("server", false, "makes the process an http server")
|
2018-10-14 06:51:27 -04:00
|
|
|
remotehost := flag.String("remote", "share.otremblay.com", "remote server for sharethis to contact")
|
|
|
|
|
sshport := flag.String("sshport", "2022", "the remote ssh port")
|
|
|
|
|
httpport := flag.String("httpport", "8888", "the remote server's http port")
|
2018-10-15 07:29:55 -04:00
|
|
|
sharecount := flag.Uint("count", 1, "Amount of times you want to share this file")
|
2018-10-17 21:22:32 -04:00
|
|
|
serverkey := flag.String("serverkey", "id_rsa", "Path to the server private key")
|
2018-10-11 21:28:52 -04:00
|
|
|
flag.Parse()
|
2018-10-17 21:22:32 -04:00
|
|
|
if envsshport := os.Getenv("SHARETHIS_SSHPORT"); envsshport != "" {
|
|
|
|
|
*sshport = envsshport
|
|
|
|
|
}
|
|
|
|
|
if envhttpport := os.Getenv("SHARETHIS_HTTPPORT"); envhttpport != "" {
|
|
|
|
|
*httpport = envhttpport
|
|
|
|
|
}
|
|
|
|
|
if envremotehost := os.Getenv("SHARETHIS_REMOTEHOST"); envremotehost != "" {
|
|
|
|
|
*remotehost = envremotehost
|
|
|
|
|
}
|
|
|
|
|
if authorizedKeys == "" {
|
|
|
|
|
authorizedKeys = fmt.Sprintf("%s/.ssh/authorized_keys", os.ExpandEnv("HOME"))
|
|
|
|
|
}
|
2018-10-17 19:54:34 -04:00
|
|
|
if *sharecount > 0 {
|
|
|
|
|
*sharecount--
|
|
|
|
|
}
|
2018-10-11 21:28:52 -04:00
|
|
|
if *server {
|
2018-10-17 21:22:32 -04:00
|
|
|
if envserverkey := os.Getenv("SHARETHIS_SERVERKEY"); envserverkey != "" {
|
|
|
|
|
*serverkey = envserverkey
|
|
|
|
|
}
|
|
|
|
|
runServer("0.0.0.0", *sshport, *httpport, *serverkey)
|
2018-10-11 21:28:52 -04:00
|
|
|
}
|
|
|
|
|
if len(flag.Args()) < 1 {
|
|
|
|
|
log.Fatalln("Need filename")
|
|
|
|
|
}
|
2018-10-12 07:10:07 -04:00
|
|
|
var path string
|
|
|
|
|
if fs, err := os.Stat(flag.Arg(0)); err != nil {
|
2018-10-11 21:28:52 -04:00
|
|
|
log.Fatalln("Can't read file")
|
2018-10-12 07:10:07 -04:00
|
|
|
} else {
|
|
|
|
|
p, err := filepath.Abs(fs.Name())
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalln("Can't read file")
|
|
|
|
|
}
|
|
|
|
|
path = p
|
2018-10-11 21:28:52 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if *bg {
|
|
|
|
|
_, err := syscall.ForkExec(os.Args[0], append([]string{os.Args[0]}, flag.Args()...), &syscall.ProcAttr{Files: []uintptr{0, 1, 2}})
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalln(err)
|
|
|
|
|
}
|
|
|
|
|
os.Exit(0)
|
|
|
|
|
}
|
2018-10-17 19:54:34 -04:00
|
|
|
var username string
|
|
|
|
|
userobj, err := user.Current()
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Fprintln(os.Stderr, "Could not get user with user.Current()")
|
|
|
|
|
username = "unknown"
|
|
|
|
|
} else {
|
|
|
|
|
username = userobj.Username
|
|
|
|
|
}
|
2018-10-17 21:22:32 -04:00
|
|
|
|
2018-10-11 21:28:52 -04:00
|
|
|
sshConfig := &ssh.ClientConfig{
|
2018-10-17 19:54:34 -04:00
|
|
|
User: username,
|
2018-10-17 21:22:32 -04:00
|
|
|
Auth: []ssh.AuthMethod{},
|
2018-10-11 21:28:52 -04:00
|
|
|
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
}
|
2018-10-17 21:22:32 -04:00
|
|
|
if agent, err := SSHAgent(); err == nil {
|
|
|
|
|
sshConfig.Auth = append(sshConfig.Auth, agent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
keypath := fmt.Sprintf("%s/.ssh/st_rsa", os.Getenv("HOME"))
|
|
|
|
|
auth, err := PublicKeyFile(keypath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println(err)
|
|
|
|
|
} else {
|
|
|
|
|
sshConfig.Auth = append(sshConfig.Auth, auth)
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-14 06:51:27 -04:00
|
|
|
connection, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", *remotehost, *sshport), sshConfig)
|
2018-10-11 21:28:52 -04:00
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalln("Failed to dial: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ch, reqch, err := connection.OpenChannel("Nope", nil)
|
|
|
|
|
go ssh.DiscardRequests(reqch)
|
|
|
|
|
if err != nil {
|
2018-10-17 21:22:32 -04:00
|
|
|
log.Fatalln("poop", err)
|
2018-10-11 21:28:52 -04:00
|
|
|
}
|
|
|
|
|
enc := gob.NewEncoder(ch)
|
|
|
|
|
path = flag.Arg(0)
|
2018-10-17 19:54:34 -04:00
|
|
|
|
2018-10-12 07:10:07 -04:00
|
|
|
hostname, err := os.Hostname()
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Fprintln(os.Stderr, "Could not get hostname with os.Hostname()")
|
|
|
|
|
hostname = "unknown"
|
|
|
|
|
}
|
|
|
|
|
fullpath := fmt.Sprintf("%s@%s:%s", username, hostname, path)
|
|
|
|
|
hashedpath := fmt.Sprintf("%x", sha256.Sum256([]byte(fullpath)))
|
2018-10-14 06:51:27 -04:00
|
|
|
|
2018-10-12 07:10:07 -04:00
|
|
|
// In the words of weezer, I've got my hashed path.
|
2018-10-14 06:51:27 -04:00
|
|
|
// TODO: Get the remote URL from the remote server instead of rebuilding it locally.
|
2018-10-15 07:29:55 -04:00
|
|
|
var fileurl string
|
|
|
|
|
if *httpport == "443" {
|
|
|
|
|
fileurl = fmt.Sprintf("https://%s/%s", *remotehost, hashedpath)
|
|
|
|
|
} else if *httpport == "80" {
|
|
|
|
|
fileurl = fmt.Sprintf("http://%s/%s", *remotehost, hashedpath)
|
|
|
|
|
} else {
|
|
|
|
|
fileurl = fmt.Sprintf("http://%s:%s/%s", *remotehost, *httpport, hashedpath)
|
|
|
|
|
}
|
|
|
|
|
fmt.Println(fileurl)
|
|
|
|
|
err = enc.Encode(&FileReq{Path: hashedpath, ShareCount: *sharecount})
|
2018-10-11 21:28:52 -04:00
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println(err)
|
|
|
|
|
ch.Close()
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
2018-10-15 07:29:55 -04:00
|
|
|
|
|
|
|
|
mu := &sync.Mutex{}
|
|
|
|
|
|
|
|
|
|
defer ch.Close()
|
|
|
|
|
ncc := connection.HandleChannelOpen(hashedpath)
|
2018-10-11 21:28:52 -04:00
|
|
|
for {
|
2018-10-15 07:29:55 -04:00
|
|
|
nc := <-ncc
|
|
|
|
|
ch, req, err := nc.Accept()
|
|
|
|
|
go ssh.DiscardRequests(req)
|
2018-10-11 21:28:52 -04:00
|
|
|
if err != nil {
|
2018-10-15 07:29:55 -04:00
|
|
|
fmt.Fprintln(os.Stderr, err)
|
2018-10-11 21:28:52 -04:00
|
|
|
}
|
2018-10-15 07:29:55 -04:00
|
|
|
|
|
|
|
|
go func() {
|
2018-10-11 21:28:52 -04:00
|
|
|
f, err := os.Open(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Fprintln(ch, "Sharethis error")
|
2018-10-15 07:29:55 -04:00
|
|
|
return
|
2018-10-11 21:28:52 -04:00
|
|
|
}
|
2018-10-15 07:29:55 -04:00
|
|
|
_, err = io.Copy(ch, f)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println(err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
mu.Lock()
|
|
|
|
|
if *sharecount == 0 {
|
|
|
|
|
mu.Unlock()
|
|
|
|
|
os.Exit(0)
|
|
|
|
|
}
|
|
|
|
|
*sharecount--
|
|
|
|
|
mu.Unlock()
|
|
|
|
|
ch.Close()
|
|
|
|
|
}()
|
|
|
|
|
|
2018-10-11 21:28:52 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func PublicKeyFile(file string) (ssh.AuthMethod, error) {
|
|
|
|
|
buffer, err := ioutil.ReadFile(file)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("Couldn't read key file: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
key, err := ssh.ParsePrivateKey(buffer)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("Couldn't parse key file: %v", err)
|
|
|
|
|
}
|
|
|
|
|
return ssh.PublicKeys(key), nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-17 21:22:32 -04:00
|
|
|
func SSHAgent() (ssh.AuthMethod, error) {
|
2018-10-11 21:28:52 -04:00
|
|
|
if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
2018-10-17 21:22:32 -04:00
|
|
|
a := agent.NewClient(sshAgent)
|
|
|
|
|
signers, _ := a.Signers()
|
|
|
|
|
if len(signers) == 0 {
|
|
|
|
|
return nil, fmt.Errorf("No signer found")
|
|
|
|
|
}
|
|
|
|
|
return ssh.PublicKeysCallback(a.Signers), nil
|
2018-10-11 21:28:52 -04:00
|
|
|
} else {
|
|
|
|
|
fmt.Println(err)
|
2018-10-17 21:22:32 -04:00
|
|
|
return nil, err
|
2018-10-11 21:28:52 -04:00
|
|
|
}
|
2018-10-17 21:22:32 -04:00
|
|
|
return nil, nil
|
2018-10-11 21:28:52 -04:00
|
|
|
}
|
|
|
|
|
|
2018-10-14 06:51:27 -04:00
|
|
|
func runServer(host, sshport, httpport, keyfile string) {
|
2018-10-15 07:29:55 -04:00
|
|
|
filemap := map[string]*FileReq{}
|
2018-10-12 19:04:45 -04:00
|
|
|
syncy := &sync.RWMutex{}
|
|
|
|
|
|
2018-10-15 07:29:55 -04:00
|
|
|
mapget := func(key string) (*FileReq, bool) {
|
2018-10-12 19:04:45 -04:00
|
|
|
syncy.RLock()
|
|
|
|
|
defer syncy.RUnlock()
|
|
|
|
|
c, ok := filemap[key]
|
|
|
|
|
return c, ok
|
|
|
|
|
}
|
2018-10-15 07:29:55 -04:00
|
|
|
mapset := func(key string, fr *FileReq) {
|
2018-10-12 19:04:45 -04:00
|
|
|
syncy.Lock()
|
2018-10-15 07:29:55 -04:00
|
|
|
filemap[key] = fr
|
2018-10-12 19:04:45 -04:00
|
|
|
syncy.Unlock()
|
|
|
|
|
}
|
|
|
|
|
mapdel := func(key string) {
|
|
|
|
|
syncy.Lock()
|
|
|
|
|
delete(filemap, key)
|
|
|
|
|
syncy.Unlock()
|
|
|
|
|
}
|
2018-10-11 21:28:52 -04:00
|
|
|
|
|
|
|
|
cfg := buildCfg()
|
|
|
|
|
|
|
|
|
|
// You can generate a keypair with 'ssh-keygen -t rsa'
|
|
|
|
|
privateBytes, err := ioutil.ReadFile(keyfile)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(fmt.Sprintf("Failed to load private key (%s): %v", keyfile, err))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private, err := ssh.ParsePrivateKey(privateBytes)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(fmt.Sprintf("Failed to parse private key: %v", err))
|
|
|
|
|
}
|
|
|
|
|
cfg.AddHostKey(private)
|
|
|
|
|
|
2018-10-14 06:51:27 -04:00
|
|
|
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", host, sshport))
|
2018-10-11 21:28:52 -04:00
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal("failed to listen for connection: ", err)
|
|
|
|
|
}
|
|
|
|
|
go func() {
|
|
|
|
|
for {
|
|
|
|
|
nConn, err := listener.Accept()
|
|
|
|
|
if err != nil {
|
2018-10-17 21:22:32 -04:00
|
|
|
log.Println("failed to accept incoming connection: ", err)
|
|
|
|
|
continue
|
2018-10-11 21:28:52 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
serverConn, chans, reqs, err := ssh.NewServerConn(nConn, cfg)
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
2018-10-17 21:22:32 -04:00
|
|
|
log.Println("failed to handshake: ", err)
|
|
|
|
|
continue
|
2018-10-11 21:28:52 -04:00
|
|
|
}
|
|
|
|
|
// The incoming Request channel must be serviced.
|
|
|
|
|
go ssh.DiscardRequests(reqs)
|
|
|
|
|
|
|
|
|
|
// Service the incoming Channel channel.
|
|
|
|
|
go func() {
|
|
|
|
|
for newChannel := range chans {
|
|
|
|
|
channel, requests, err := newChannel.Accept()
|
|
|
|
|
if err != nil {
|
2018-10-17 21:22:32 -04:00
|
|
|
log.Println("Could not accept channel: ", err)
|
|
|
|
|
continue
|
2018-10-11 21:28:52 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
go func(in <-chan *ssh.Request) {
|
|
|
|
|
for req := range in {
|
|
|
|
|
req.Reply(true, nil)
|
|
|
|
|
}
|
|
|
|
|
}(requests)
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
dec := gob.NewDecoder(channel)
|
|
|
|
|
var filereq FileReq
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
err := dec.Decode(&filereq)
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2018-10-15 07:29:55 -04:00
|
|
|
|
|
|
|
|
// TODO: also take the sharecount in the map.
|
|
|
|
|
filereq.serverconn = serverConn
|
|
|
|
|
mapset(filereq.Path, &filereq)
|
2018-10-12 19:04:45 -04:00
|
|
|
go func() { serverConn.Wait(); mapdel(filereq.Path) }()
|
2018-10-11 21:28:52 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
}()
|
2018-10-14 06:51:27 -04:00
|
|
|
http.ListenAndServe(fmt.Sprintf(":%s", httpport), http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
2018-10-15 07:29:55 -04:00
|
|
|
if strings.Contains(req.UserAgent(), "Slackbot-LinkExpanding") {
|
|
|
|
|
rw.WriteHeader(http.StatusForbidden)
|
|
|
|
|
fmt.Fprintln(rw, "No slackbots allowed!")
|
|
|
|
|
return
|
|
|
|
|
}
|
2018-10-11 21:28:52 -04:00
|
|
|
fp := strings.TrimPrefix(req.URL.Path, "/")
|
2018-10-15 07:29:55 -04:00
|
|
|
if fr, ok := mapget(fp); ok {
|
|
|
|
|
channel, req, err := fr.serverconn.OpenChannel(fp, nil)
|
2018-10-11 21:28:52 -04:00
|
|
|
if err != nil {
|
2018-10-15 07:29:55 -04:00
|
|
|
fmt.Fprintln(os.Stderr, err)
|
2018-10-11 21:28:52 -04:00
|
|
|
return
|
|
|
|
|
}
|
2018-10-15 07:29:55 -04:00
|
|
|
go ssh.DiscardRequests(req)
|
2018-10-11 21:28:52 -04:00
|
|
|
|
2018-10-15 07:29:55 -04:00
|
|
|
_, err = io.Copy(rw, channel)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println(err)
|
|
|
|
|
}
|
|
|
|
|
if fr.ShareCount == 0 {
|
|
|
|
|
mapdel(fp)
|
|
|
|
|
channel.Close()
|
|
|
|
|
}
|
|
|
|
|
fr.ShareCount--
|
2018-10-11 21:28:52 -04:00
|
|
|
return
|
|
|
|
|
} else {
|
|
|
|
|
rw.WriteHeader(http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
os.Exit(0)
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func buildCfg() *ssh.ServerConfig {
|
2018-10-17 21:22:32 -04:00
|
|
|
authorizedKeysBytes, err := ioutil.ReadFile(authorizedKeys)
|
2018-10-11 21:28:52 -04:00
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("Failed to load authorized_keys, err: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-15 07:29:55 -04:00
|
|
|
authorizedKeysMap := map[string]string{}
|
2018-10-11 21:28:52 -04:00
|
|
|
for len(authorizedKeysBytes) > 0 {
|
2018-10-15 07:29:55 -04:00
|
|
|
pubKey, comment, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
|
|
|
|
|
fmt.Println(comment)
|
2018-10-11 21:28:52 -04:00
|
|
|
if err != nil {
|
2018-10-17 21:24:43 -04:00
|
|
|
log.Fatal("authorized_keys error", err)
|
2018-10-11 21:28:52 -04:00
|
|
|
}
|
|
|
|
|
|
2018-10-15 07:29:55 -04:00
|
|
|
authorizedKeysMap[string(pubKey.Marshal())] = comment
|
2018-10-11 21:28:52 -04:00
|
|
|
authorizedKeysBytes = rest
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//ssh serverstuffs
|
|
|
|
|
cfg := &ssh.ServerConfig{}
|
|
|
|
|
cfg.SetDefaults()
|
|
|
|
|
cfg.PasswordCallback = func(ssh.ConnMetadata, []byte) (*ssh.Permissions, error) { return nil, fmt.Errorf("Public key only") }
|
|
|
|
|
cfg.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
2018-10-17 21:22:32 -04:00
|
|
|
if user, ok := authorizedKeysMap[string(key.Marshal())]; ok {
|
|
|
|
|
fmt.Println("Key used:", user)
|
2018-10-11 21:28:52 -04:00
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
return nil, fmt.Errorf("unknown public key for %q", conn.User())
|
|
|
|
|
}
|
|
|
|
|
return cfg
|
|
|
|
|
}
|