├── systemd
└── go-flashpaper.service
├── LICENSE
├── README.md
└── flashpaper.go
/systemd/go-flashpaper.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=go-flashpaper
3 |
4 | [Service]
5 | Type=simple
6 | User=flashpaper
7 | Restart=on-failure
8 | RestartSec=60
9 | WorkingDirectory=#fill in
10 | ExecStart=#fill in
11 |
12 | # make sure log directory exists and owned by syslog
13 | PermissionsStartOnly=true
14 | ExecStartPre=/bin/mkdir -p /var/log/go-flashpaper
15 | ExecStartPre=/bin/chown syslog:adm /var/log/go-flashpaper
16 | ExecStartPre=/bin/chmod 755 /var/log/go-flashpaper
17 | StandardOutput=syslog
18 | StandardError=syslog
19 | SyslogIdentifier=go-flashpaper
20 |
21 | [Install]
22 | WantedBy=multi-user.target
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Ryan Huber
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flashpaper
2 |
3 | Flashpaper is a simple go-based service for creating one time use links to text data or individual files.
4 |
5 |
6 | ## What is this?
7 |
8 | It is a web service that allows you to upload text snippets or files and generates one time use links to share these things with other people. As soon as the sharing link is accessed, the data is deleted from the web service's memory and the link expires. This means that old links are useless, even if shared somewhere insecure. This can be used to share sensitive data with friends or colleagues. Flashpaper has a maximum data retention period of 24 hours. It has no build dependencies outside of the go standard library.
9 |
10 | ## Changelog
11 |
12 | 1. Let's Encrypt support for auto-certificate generation.
13 | 2. A new UI.
14 | 3. Optional integration with a Canarytoken.
15 | 4. Optional multi-user authentication using BasicAuth.
16 |
17 | ## Installation
18 |
19 | 1. Get a server - preferably one you run yourself and install Golang.
20 | 2. Disable swap on the server so you don't write secrets to disk.
21 | 3. Build flashpaper: `go build`
22 | 4. Run go-flashpaper: `./flashpaper` specifying all optional parameters.
23 | 5. Connect to the web service via `https://yourserver.example.com:8443` (note: 8443 is the default port, so no one has an excuse to run this as root)
24 | 6. Share secret things.
25 |
26 | For development purposes, the server can be setup to run on your local machine. Simply run the code and navigate to:
27 | `https://localhost:8443`
28 | Browser dependent, you may get a security warning. Simply click on `Advanced` and proceed.
29 |
30 | ## Usage
31 |
32 | As mentioned in the Installation section, optional parameters can be specified to customise the server. The flags are as follows:
33 | 1. `-auth {"filename.txt}`: specify this flag to enable authentication. Leave it out to remove authentication.
34 | 2. `-token {canary token link}`: specify this to be notified when a link has been accessed more than once. Leave it out to not enable it.
35 | 3. `-autocert {true}`: specify this to make use of autocert so that you don't need to specify your own cert files.
36 |
37 | #### Example
38 | 1. Specify an auth file, a token and autocert.
39 | `./flashpaper -auth auth.txt -autocert true -token http://canarytokens.com/tags/feedback/terms/...`
40 | 2. Use no auth nor a token.
41 | `./flashpaper`
42 |
43 |
44 | ## Canarytoken integration
45 |
46 | The basis of this tool is that a link is only accessed once. However, valuable information may be gathered if a user clicks on a link that has already been used. Subsequently, integration with `canarytokens.org` has been provided. If a user clicks on a link that has already been used, their browser will be fingerprinted and a notification sent. The setup process is as follows:
47 | 1. Visit [canarytokens.org/generate](http://canarytokens.org/generate).
48 | 2. Click on the dropdown box and select either a `Fast Redirect` or `Slow Redirect` token. The trade-off is as follows:
49 | - A `Fast Redirect` will not be visible in the redirect process. This means that the user will not know that they have been caught. However, less information can be gathered.
50 | - A `Slow Redirect` will be visible to the user giving away your position. However, you will be able to gather more information about their browser.
51 | Both work, it is simply a matter of priorities.
52 | 3. Specify the email address where you would like to be notified.
53 | 4. Provide a note.
54 | 5. Provide a redirect link. As mentioned in the code, it is advisable to use `https://yourserver.example.com:8443/usedlink` so that the user is still met with a Flashpaper screen after the redirect. Note that you will not be able to test this on your localhost as Canarytokens does not allow for generation of tokens with localhost as a URL.
55 | 6. Generate the token. You will be provided with a link that will be used in the Usage section.
56 |
57 | ## Multi-user Authentication
58 |
59 | If required, authentication can be specified for creating and viewing secrets. Validation is done against a text file, specified by you, which stores a hash of a username and password on a line. This way, new credentials can be specified. If the file is not found, a file with the specified name will be created for you with default credentials. This file is read on each Auth attempt meaning that users can be added / removed without having to restart the server. The default credentials specified in the code are as follows:
60 | `Username: user1`
61 | `Password: pass1`
62 |
63 |
64 | ## Let's Encrypt
65 |
66 | Functionality has been provided to auto-generate `tls` certificates which means that you don't need to specify certificates manually.
67 |
68 |
69 | ## Systemd service ##
70 |
71 | It is important to note that if the service restarts we will lose all current secrets (as they are stored in memory and pointed to by the process). Running it as a systemd service and auto-restarting will mean that we won't notice if it restarts and old links may fail. This might lead us to think that the link has been used before. We should check logs to see if this is the case or if it was due to a restart:
72 |
73 | `sudo journalctl -u go-flashpaper`
74 |
75 | If you are happy with this then:
76 |
77 | 1. Specify your arguments in the `go-flashpaper.service` file. Add them to the end of `ExecStart=...`.
78 | 2. Copy `systmd/go-flashpaper.service` to `/lib/systemd/system/`
79 | 3. `sudo chmod 755 /lib/systemd/system/go-flashpaper.service`
80 | 4. `sudo systemctl enable go-flashpaper.service`
81 | 5. `sudo systemctl start go-flashpaper`
82 |
83 | ## Manually running in background ##
84 |
85 | Once up and running, you can run with:
86 |
87 | `nohup ./go-flashpaper &`
88 |
89 | This will run in a background process and log to `nohup.out`. The service will not be automatically restarted.
90 |
91 | *Please note that this feature has not yet been tested with the new updates.
92 |
93 | ## FAQ
94 |
95 | Q: *How do I configure it?*
96 |
97 | A: You don't. As shown in the Usage, after building `flashpaper.go` you simply run it with the required flags.
98 |
99 | Q: *Should I run this on (cloud provider x)?*
100 |
101 | A: That is up to you and your individual risk appetite. This comes with no warranty or promise of security, but it is probably way better than using gtalk to share your root password.
102 |
103 | Q: *Should I put this on the public internet?*
104 |
105 | A: Probably not. It is susceptible to Denial of Service by simply uploading a lot of data.
106 |
107 | Q: *Why do you limit uploads to 10mb?*
108 |
109 | A: Because everything is kept in memory and we ain't made of money.
110 |
111 |
112 | ## What are some examples of using this?
113 |
114 | ### Example 1
115 |
116 | **Alice**: Hi Bob, I just set your new linux account with a random password. The password is available here: `https://seekret.example.com:8443/de818e28daa568cb433b39da292b589bdcbc1bc771d52cffe453b0e01e93865b` Please log in and change your password immediately.
117 |
118 | **Bob**: Thanks, I logged in with that password and changed my password!
119 |
120 | ### Example 2
121 |
122 | **Alice**: Hi Bob, I just set your new linux account with a random password. The password is available here: `https://seekret.example.com:8443/de818e28daa568cb433b39da292b589bdcbc1bc771d52cffe453b0e01e93865b` Please log in and change your password immediately.
123 |
124 | **Bob**: Hmmm, that link didn't work.
125 |
126 | **Alice**: It hasn't been 24 hours, so someone may have intercepted it. I'll delete that account and create a new one. Then I'll send you another link by carrier pigeon.
127 |
128 | **Bob**: Perfect, thanks!
129 |
130 | ### Example 3
131 |
132 | **Alice**: Hi Bob, I have that document on our new security infrastructure plans. Go here to download it: `https://seekret.example.com/de818e28daa568cb433b39da292b589bdcbc1bc771d52cffe453b0e01e93865b`
133 |
134 | **Bob**: Yay!
135 |
136 | ## Have a nice day!
137 |
--------------------------------------------------------------------------------
/flashpaper.go:
--------------------------------------------------------------------------------
1 | //Ryan Huber rhuber@gmail.com - 2016
2 |
3 | package main
4 |
5 | import (
6 | "bytes"
7 | "context"
8 | "crypto/rand"
9 | "crypto/sha256"
10 | "crypto/subtle"
11 | "crypto/tls"
12 | "errors"
13 | "flag"
14 | "fmt"
15 | "html"
16 | "io"
17 | "log"
18 | "net/http"
19 | "os"
20 | "strconv"
21 | "strings"
22 | "sync"
23 | "time"
24 |
25 | "golang.org/x/crypto/acme/autocert"
26 | )
27 |
28 | // MAXUPLOADSIZE of 10mb
29 | const MAXUPLOADSIZE = 104857600
30 |
31 | // MAXHOURSTOKEEP colloquially: "a day"
32 | const MAXHOURSTOKEEP = 24.0
33 |
34 | // SECRETSTOREKEY if you don't know what this is, don't worry about it
35 | const SECRETSTOREKEY = 234
36 |
37 | // DEFAULTCREDS a Hash of user1 pass1.
38 | const DEFAULTCREDS = "0a041b9462caa4a31bac3567e0b6e6fd9100787db2ab433d96f6d178cabfce90" +
39 | " e6c3da5b206634d7f3f3586d747ffdb36b5c675757b380c6a5fe5c570c714349"
40 |
41 | // CANARYTOKEN The provided canarytoken.
42 | var CANARYTOKEN = ""
43 |
44 | // AUTHFILENAME the provided file name.
45 | var AUTHFILENAME = "auth.txt"
46 |
47 | //AUTH where auth is enabled or not.
48 | var AUTH = false
49 |
50 | //Because typing this over and over is silly
51 | type smap map[string]*secret
52 |
53 | //Secrets are this
54 | type secret struct {
55 | ID string `json:"id"`
56 | Type string `json:"type"`
57 | Data []byte `json:"data"`
58 | time time.Time
59 | Name string `json:"name"`
60 | Auth bool `json:"auth"`
61 | }
62 |
63 | //Clear the actual bytes in memory .. hopefully.
64 | func (s *secret) Wipe() {
65 | for i := range s.Data {
66 | s.Data[i] = 0
67 | }
68 | }
69 |
70 | // NewSecret Generate a new secret.
71 | func NewSecret() *secret {
72 | id, err := randPathString()
73 | if err != nil {
74 | log.Fatal(err)
75 | }
76 | return &secret{ID: id, time: time.Now()}
77 | }
78 |
79 | func secretHandler(w http.ResponseWriter, r *http.Request) {
80 |
81 | secrets := r.Context().Value(SECRETSTOREKEY).(smap)
82 |
83 | path := r.URL.Path[1:]
84 | r.ParseForm()
85 |
86 | //prevent slackbot from exploding links when posted to a channel
87 | if strings.Contains(r.UserAgent(), "Slack") {
88 | http.NotFound(w, r)
89 | return
90 | }
91 |
92 | switch r.Method {
93 | case "GET":
94 | switch path {
95 | case "favicon.ico":
96 | return
97 | case "":
98 | w.Header().Set("Content-Type", "text/html")
99 | fmt.Fprint(w, lackofstyle+index+endofstyle)
100 | case "add":
101 | w.Header().Set("Content-Type", "text/html")
102 |
103 | var ret = ""
104 | if AUTH {
105 | ret = fmt.Sprintf(lackofstyle+inputtextform+endofstyle, checkBox)
106 | } else {
107 | ret = fmt.Sprintf(lackofstyle+inputtextform+endofstyle, "")
108 | }
109 | fmt.Fprint(w, ret)
110 | case "addfile":
111 | w.Header().Set("Content-Type", "text/html")
112 |
113 | var ret = ""
114 | if AUTH {
115 | ret = fmt.Sprintf(lackofstyle+inputfileform+endofstyle, checkBox)
116 | } else {
117 | ret = fmt.Sprintf(lackofstyle+inputfileform+endofstyle, "")
118 | }
119 | fmt.Fprint(w, ret)
120 | case "help":
121 | w.Header().Set("Content-Type", "text/html")
122 | fmt.Fprint(w, lackofstyle+help+endofstyle)
123 | case "usedlink":
124 | w.Header().Set("Content-Type", "text/html")
125 | ret := fmt.Sprintf(lackofstyle+display+endofstyle, "This link has already been used.")
126 | fmt.Fprint(w, ret)
127 | default:
128 | // Access the secret without popping it.
129 | val, ok := secrets[path]
130 |
131 | if ok {
132 | // First check whether we need to perform Auth.
133 | if val.Auth {
134 | user, pass, ok := r.BasicAuth()
135 | // Check if the provided username and password that have been hashed, match the provided ones.
136 | if !ok || !checkFileAuth(hasher(user), hasher(pass)) {
137 | realm := "Please enter a username and password."
138 | w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
139 | http.Error(w, "Unauthorized.", http.StatusUnauthorized)
140 | return
141 | }
142 | }
143 | //If this is a file, set to octet-stream to force download
144 | //Otherwise just print the data
145 | if val.Type == "file" {
146 | sec, _ := popSecret(secrets, path)
147 |
148 | defer sec.Wipe()
149 |
150 | w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", sec.Name))
151 | w.Header().Set("Content-Type", "application/octet-stream")
152 | w.Write(sec.Data)
153 | } else {
154 | w.Header().Set("Content-Type", "text/html")
155 |
156 | sec, _ := popSecret(secrets, path)
157 |
158 | defer sec.Wipe()
159 |
160 | ret := fmt.Sprintf(lackofstyle+display+endofstyle, html.EscapeString(string(sec.Data)))
161 | fmt.Fprint(w, ret)
162 | }
163 | } else {
164 | // When setting up the token on a live server, please generate a new token that redirects to /usedlink.
165 | if CANARYTOKEN != "" {
166 | http.Redirect(w, r, CANARYTOKEN+"?l="+path, http.StatusSeeOther)
167 | } else {
168 | w.Header().Set("Content-Type", "text/html")
169 | ret := fmt.Sprintf(lackofstyle+display+endofstyle, "This link has already been used.")
170 | fmt.Fprint(w, ret)
171 | }
172 | }
173 | }
174 | case "POST":
175 | //I could lock on adding things to the map, but i'm not gonna.
176 | //If randomString() collides, sorry...?
177 | switch path {
178 | case "add":
179 |
180 | // Extract the parameters from the POST.
181 | _, existsAuth := r.Form["auth"]
182 | secretVal, existsSecret := r.Form["secret"]
183 |
184 | if existsSecret {
185 | secretVal := strings.Join(secretVal, "")
186 | secret := NewSecret()
187 | secrets[secret.ID] = secret
188 | secrets[secret.ID].Data = []byte(secretVal)
189 |
190 | if existsAuth {
191 | // We don't want them to Specify auth if the server can't check for it.
192 | if AUTH {
193 | secrets[secret.ID].Auth = true
194 | }
195 | }
196 | shareable(secret.ID, w, r)
197 | } else {
198 | fmt.Fprint(w, "no secret provided.")
199 | }
200 |
201 | case "addfile":
202 | f, h, _ := r.FormFile("file")
203 | _, existsAuth := r.Form["auth"]
204 |
205 | defer f.Close()
206 | d := new(bytes.Buffer)
207 |
208 | //Limit the size of uploads. We aren't made of money.
209 | mb := http.MaxBytesReader(w, f, MAXUPLOADSIZE)
210 | _, err := io.Copy(d, mb)
211 | if err != nil {
212 | return
213 | }
214 |
215 | secret := NewSecret()
216 | secrets[secret.ID] = secret
217 | secrets[secret.ID].Type = "file"
218 | secrets[secret.ID].Data = d.Bytes()
219 | secrets[secret.ID].Name = h.Filename
220 |
221 | if existsAuth {
222 | // We don't want them to Specify auth if the server can't check for it.
223 | if AUTH {
224 | secrets[secret.ID].Auth = true
225 | }
226 | }
227 |
228 | shareable(secret.ID, w, r)
229 | }
230 | default:
231 | http.NotFound(w, r)
232 | }
233 | }
234 |
235 | func shareable(id string, w http.ResponseWriter, r *http.Request) {
236 | w.Header().Set("Content-Type", "text/html")
237 |
238 | proto := "https"
239 | if r.TLS == nil {
240 | proto = "http"
241 | }
242 |
243 | ret := fmt.Sprintf(lackofstyle+shareform+endofstyle, int(MAXHOURSTOKEEP), proto, r.Host, id)
244 | fmt.Fprint(w, ret)
245 | }
246 |
247 | //This generates a (crypto) random 32 byte string for the path
248 | func randPathString() (string, error) {
249 | rb := make([]byte, 32)
250 | _, err := rand.Read(rb)
251 | s := fmt.Sprintf("%x", rb)
252 | if err == nil {
253 | return s, nil
254 | }
255 | return "", errors.New("could not generate random string")
256 | }
257 |
258 | func popSecret(secrets smap, sec string) (secret, bool) {
259 | //It is important to use the mutex here, otherwise a race condition
260 | //could lead to the ability to read the secret twice.
261 | //This would defeat the whole purpose of flashpaper...
262 | mu.Lock()
263 | defer mu.Unlock()
264 | val, ok := secrets[sec]
265 | if ok {
266 | delete(secrets, sec)
267 | } else {
268 | return secret{}, false
269 | }
270 | return *val, ok
271 | }
272 |
273 | //Runs every 1 second(s) to remove things that haven't been read and are expired
274 | func janitor(secrets smap) {
275 | for {
276 | for k, v := range secrets {
277 | duration := time.Since(v.time)
278 | if duration.Hours() > MAXHOURSTOKEEP {
279 | sec, _ := popSecret(secrets, k)
280 | sec.Wipe()
281 | }
282 | }
283 | //Sleep one second
284 | time.Sleep(1000000000)
285 | }
286 | }
287 |
288 | func contextify(fn func(w http.ResponseWriter, r *http.Request), secrets smap) http.HandlerFunc {
289 | return func(w http.ResponseWriter, r *http.Request) {
290 | c := context.WithValue(r.Context(), SECRETSTOREKEY, secrets)
291 | fn(w, r.WithContext(c))
292 | }
293 | }
294 |
295 | // Hasher returns a buffer of bytes. This is not very useful when comparing them hashes in the text file.
296 | // After hasher has returned, the buffer is converted to hex so as to make it easier to compare.
297 | func hasher(s string) []byte {
298 | val := sha256.Sum256([]byte(s))
299 | var hex []string
300 |
301 | // Iterate through the bytes.
302 | for i := 0; i < len(val); i++ {
303 | // We want each number to be represented by 2 chars.
304 | placeHolder := []string{"0"}
305 | value := strconv.FormatInt(int64(val[i]), 16)
306 |
307 | if len(value) != 2 {
308 | placeHolder = append(placeHolder, value)
309 | hex = append(hex, strings.Join(placeHolder, ""))
310 | } else {
311 | hex = append(hex, value)
312 | }
313 | }
314 | return []byte(strings.Join(hex, ""))
315 |
316 | }
317 |
318 | // This function will verify that the provided username and password are in fact in the text file.
319 | func checkFileAuth(user []byte, pass []byte) bool {
320 |
321 | fi, err := os.Open(AUTHFILENAME)
322 |
323 | // Something has gone wrong whilst opening the file.
324 | if err != nil {
325 | fmt.Println("The Specified auth file does not exist.")
326 | // Create a new auth,txt file.
327 | f, err2 := os.OpenFile(AUTHFILENAME, os.O_CREATE|os.O_RDWR, 0644)
328 | if err2 != nil {
329 | panic(err2)
330 | }
331 |
332 | // Ensure that the file will be closed.
333 | defer func() {
334 | if err2 := f.Close(); err2 != nil {
335 | panic(err2)
336 | }
337 | }()
338 |
339 | // Give it the default credentials.
340 | _, err2 = f.Write([]byte(DEFAULTCREDS))
341 |
342 | if err2 != nil {
343 | panic(err2)
344 | }
345 |
346 | fmt.Println("A file was created for you with a set of default credentials.")
347 | return false
348 |
349 | }
350 |
351 | // Ensure that the file will be closed.
352 | defer func() {
353 | if err := fi.Close(); err != nil {
354 | panic(err)
355 | }
356 | }()
357 |
358 | data := make([]byte, 130)
359 |
360 | // Iterate through all entries in the file.
361 | for {
362 | count, err := fi.Read(data)
363 | // Read until we reach the EOF.
364 | if err != nil && err != io.EOF {
365 | panic(err)
366 | }
367 |
368 | if count == 0 {
369 | break
370 | }
371 |
372 | // Check whether we have the username/password combination.
373 | if subtle.ConstantTimeCompare(data[0:64], user) == 1 && subtle.ConstantTimeCompare(data[65:129], pass) == 1 {
374 | return true
375 | }
376 | }
377 |
378 | return false
379 | }
380 |
381 | func authHandler(handler http.HandlerFunc, realm string) http.HandlerFunc {
382 | return func(w http.ResponseWriter, r *http.Request) {
383 | // Don't authenticate for links.
384 | if len(r.URL.Path) > 10 || r.URL.Path == "/usedlink" {
385 | handler(w, r)
386 | } else {
387 | user, pass, ok := r.BasicAuth()
388 | // Check if the provided username and password that have been hashed, match the provided ones.
389 | if !ok || !checkFileAuth(hasher(user), hasher(pass)) {
390 | w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
391 | http.Error(w, "Unauthorized.", http.StatusUnauthorized)
392 | return
393 | }
394 | handler(w, r)
395 | }
396 |
397 | }
398 | }
399 |
400 | //ya ya ya, globals are bad, but this is a lock.
401 | var mu = &sync.Mutex{}
402 |
403 | func main() {
404 |
405 | fmt.Println(ascii)
406 |
407 | realm := "Please enter a username and password."
408 |
409 | //set up the map that stores secrets
410 | secrets := smap{}
411 |
412 | //set up cert manager for ssl certs
413 | certManager := autocert.Manager{
414 | Prompt: autocert.AcceptTOS,
415 | HostPolicy: autocert.HostWhitelist(""), //Your domain here
416 | Cache: autocert.DirCache("certs"), //Folder for storing certificates
417 | }
418 |
419 | //launch the janitor to remove secrets that haven't been retrieved
420 | go janitor(secrets)
421 |
422 | // Setup the flags.
423 | // value = default when not specified.
424 | var auth = flag.String("auth", "", "The auth filename.")
425 | var token = flag.String("token", "", "The token link.")
426 | var autocert = flag.Bool("autocert", false, "Whether to use Autocert or not.")
427 | flag.Parse()
428 |
429 | // Check whether a token was specified. If not, it will get an empty string which is fine.
430 | CANARYTOKEN = *token
431 |
432 | if *token == "" {
433 | fmt.Println("Token: False.")
434 | } else {
435 | fmt.Println("Token: True.")
436 | }
437 |
438 | // Auth needs to be used.
439 | if *auth != "" {
440 | AUTH = true
441 | AUTHFILENAME = *auth
442 | fmt.Println("Authentication: Enabled.")
443 | http.HandleFunc("/", authHandler(contextify(secretHandler, secrets), realm))
444 |
445 | } else {
446 | fmt.Println("Authentication: Disabled.")
447 | http.HandleFunc("/", contextify(secretHandler, secrets))
448 |
449 | }
450 |
451 | if *autocert {
452 | fmt.Println("AutoCert: True.")
453 | server := &http.Server{
454 | Addr: ":8443",
455 | TLSConfig: &tls.Config{
456 | GetCertificate: certManager.GetCertificate,
457 | },
458 | }
459 |
460 | go http.ListenAndServe(":http", certManager.HTTPHandler(nil))
461 | err := server.ListenAndServeTLS("", "") //Key and cert are coming from Let's Encrypt
462 | if err != nil {
463 | fmt.Printf("main(): %s\n", err)
464 | }
465 |
466 | } else {
467 | fmt.Println("AutoCert: False.")
468 | //Key and cert are coming from Let's Encrypt.
469 | err := http.ListenAndServeTLS(":8443", "server.crt", "server.key", nil)
470 | if err != nil {
471 | fmt.Printf("main(): %s\n", err)
472 | fmt.Printf("Errors usually mean you don't have the required server.crt or server.key files.\n")
473 | }
474 | }
475 |
476 | }
477 |
478 | //That's right friend, all the terrible HTML is right here in the source.
479 | const lackofstyle = `
480 |
Flashpaper provides you with a unique link that you can use to send a password or file to someone else. For security purposes, the link is only active for 24 hours. 644 | In the main menu, you can either generate a link by entering text or by providing a file. This link can then be opened by anyone and viewed. However, this can only be done 645 | once, after which the data is destroyed. This way, if the intended party tries to open the link and they can't, it means that someone has already accessed it.
646 |Give this link, which will expire in %d hours or when opened, to the person you want to share information with.
908 | 909 |