-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
167 lines (141 loc) · 3.32 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package main
import (
"bufio"
"crypto/rand"
"encoding/base64"
"errors"
"flag"
"fmt"
"html/template"
"io"
"log"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/atotto/clipboard"
)
var HtmlTemplate = template.Must(template.New("password").Parse(html))
const CHILD_PASSWORD_ENV = "_SHAREPASS_PASSWORD"
func main() {
copyFlag := flag.Bool("copy", true, "Copy sharing URL to clipboard")
daemonFlag := flag.Bool("daemon", true, "Run as background process after entering password")
timeoutFlag := flag.Duration("timeout", time.Minute*10, "Timeout before exiting (e.g. 60s, 10m)")
flag.Parse()
log.SetFlags(0)
pass := os.Getenv(CHILD_PASSWORD_ENV)
if pass == "" {
// Read the password
inputPass, err := getPass(os.Stdin)
if err != nil {
log.Fatalf("Failed to read password: %s\n", err)
}
pass = inputPass
if *daemonFlag {
forkChild(pass)
return
}
}
// Get the local network address
ip, err := getLocalAddr()
if err != nil {
log.Fatalln(err)
}
// Generate a secret key
key, err := getSecretKey()
if err != nil {
log.Fatalln(err)
}
// Open a socket with a randomly assigned local port
listener, err := net.Listen("tcp", ip+":0")
if err != nil {
log.Fatal(err)
}
addr := listener.Addr().String()
// Generate the URL to send to the recipient
serveUrl := url.URL{
Scheme: "http",
Host: addr,
Path: "/" + key,
}
url := serveUrl.String()
log.Printf("Listening on %s for %s\n", url, *timeoutFlag)
// Copy the URL to the clipboard, if enabled
if *copyFlag {
if err := clipboard.WriteAll(url); err != nil {
log.Printf("Error copying to clipboard: %s\n", err)
} else {
log.Printf("Copied to clipboard: \"%s\"\n", url)
}
}
// channel for signalling success or timeout
done := make(chan bool)
time.AfterFunc(*timeoutFlag, func() {
done <- true
})
http.HandleFunc("/"+key, func(res http.ResponseWriter, req *http.Request) {
err := HtmlTemplate.Execute(res, pass)
if err != nil {
log.Printf("Error serving HTML: %s\n", err)
return
}
done <- true
})
server := &http.Server{Addr: addr, Handler: nil}
go server.Serve(listener)
// wait until one successful request is complete or timeout occurs
<-done
}
func getLocalAddr() (ip string, err error) {
host, err := os.Hostname()
if err != nil {
return
}
addrs, err := net.LookupIP(host)
if err != nil {
return
}
for _, addr := range addrs {
ipv4 := addr.To4()
if ipv4 != nil && !ipv4.IsLoopback() {
ip = ipv4.String()
break
}
}
if ip == "" {
err = errors.New("Failed to find local IP address")
}
return
}
func getSecretKey() (string, error) {
randBytes := make([]byte, 16)
_, err := rand.Read(randBytes)
if err != nil {
log.Fatal(err)
}
encoding := base64.URLEncoding.WithPadding(base64.NoPadding)
key := encoding.EncodeToString(randBytes)
return key, nil
}
func getPass(input io.Reader) (pass string, err error) {
reader := bufio.NewReader(input)
fmt.Fprintf(os.Stderr, "Enter password > ")
pass, err = reader.ReadString('\n')
pass = strings.TrimRight(pass, "\n\r ")
if err != nil {
return
}
return
}
func forkChild(pass string) (err error) {
attr := &os.ProcAttr{
Env: append(os.Environ(), CHILD_PASSWORD_ENV+"="+pass),
Files: []*os.File{nil, nil, os.Stderr},
}
if _, err = os.StartProcess("sharepass", os.Args, attr); err != nil {
return
}
return nil
}