honk.go (view raw)
1//
2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
3//
4// Permission to use, copy, modify, and distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16package main
17
18import (
19 "bytes"
20 "crypto/rand"
21 "crypto/rsa"
22 "database/sql"
23 "fmt"
24 "html"
25 "html/template"
26 "io"
27 "log"
28 notrand "math/rand"
29 "net/http"
30 "os"
31 "sort"
32 "strconv"
33 "strings"
34 "sync"
35 "time"
36
37 "github.com/gorilla/mux"
38 "humungus.tedunangst.com/r/webs/image"
39 "humungus.tedunangst.com/r/webs/login"
40 "humungus.tedunangst.com/r/webs/rss"
41 "humungus.tedunangst.com/r/webs/templates"
42)
43
44type WhatAbout struct {
45 ID int64
46 Name string
47 Display string
48 About string
49 Key string
50 URL string
51}
52
53type Honk struct {
54 ID int64
55 UserID int64
56 Username string
57 What string
58 Honker string
59 XID string
60 RID string
61 Date time.Time
62 URL string
63 Noise string
64 Convoy string
65 Audience []string
66 Privacy string
67 HTML template.HTML
68 Donks []*Donk
69}
70
71type Donk struct {
72 FileID int64
73 XID string
74 Name string
75 URL string
76 Media string
77 Content []byte
78}
79
80type Honker struct {
81 ID int64
82 UserID int64
83 Name string
84 XID string
85 Flavor string
86 Combos []string
87}
88
89var serverName string
90var iconName = "icon.png"
91
92var readviews *templates.Template
93
94func getInfo(r *http.Request) map[string]interface{} {
95 templinfo := make(map[string]interface{})
96 templinfo["StyleParam"] = getstyleparam("views/style.css")
97 templinfo["LocalStyleParam"] = getstyleparam("views/local.css")
98 templinfo["ServerName"] = serverName
99 templinfo["IconName"] = iconName
100 templinfo["UserInfo"] = login.GetUserInfo(r)
101 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
102 return templinfo
103}
104
105func homepage(w http.ResponseWriter, r *http.Request) {
106 templinfo := getInfo(r)
107 u := login.GetUserInfo(r)
108 var honks []*Honk
109 if u != nil {
110 if r.URL.Path == "/atme" {
111 honks = gethonksforme(u.UserID)
112 } else {
113 honks = gethonksforuser(u.UserID)
114 }
115 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
116 } else {
117 honks = getpublichonks()
118 }
119
120 var modtime time.Time
121 if len(honks) > 0 {
122 modtime = honks[0].Date
123 }
124 debug := false
125 getconfig("debug", &debug)
126 imh := r.Header.Get("If-Modified-Since")
127 if !debug && imh != "" && !modtime.IsZero() {
128 ifmod, err := time.Parse(http.TimeFormat, imh)
129 if err == nil && !modtime.After(ifmod) {
130 w.WriteHeader(http.StatusNotModified)
131 return
132 }
133 }
134 reverbolate(honks)
135
136 msg := "Things happen."
137 getconfig("servermsg", &msg)
138 templinfo["Honks"] = honks
139 templinfo["ShowRSS"] = true
140 templinfo["ServerMessage"] = msg
141 if u == nil {
142 w.Header().Set("Cache-Control", "max-age=60")
143 } else {
144 w.Header().Set("Cache-Control", "max-age=0")
145 }
146 w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
147 err := readviews.Execute(w, "honkpage.html", templinfo)
148 if err != nil {
149 log.Print(err)
150 }
151}
152
153func showrss(w http.ResponseWriter, r *http.Request) {
154 name := mux.Vars(r)["name"]
155
156 var honks []*Honk
157 if name != "" {
158 honks = gethonksbyuser(name)
159 } else {
160 honks = getpublichonks()
161 }
162 reverbolate(honks)
163
164 home := fmt.Sprintf("https://%s/", serverName)
165 base := home
166 if name != "" {
167 home += "u/" + name
168 name += " "
169 }
170 feed := rss.Feed{
171 Title: name + "honk",
172 Link: home,
173 Description: name + "honk rss",
174 Image: &rss.Image{
175 URL: base + "icon.png",
176 Title: name + "honk rss",
177 Link: home,
178 },
179 }
180 var modtime time.Time
181 for _, honk := range honks {
182 desc := string(honk.HTML)
183 for _, d := range honk.Donks {
184 desc += fmt.Sprintf(`<p><a href="%sd/%s">Attachment: %s</a>`,
185 base, d.XID, html.EscapeString(d.Name))
186 }
187
188 feed.Items = append(feed.Items, &rss.Item{
189 Title: fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
190 Description: rss.CData{desc},
191 Link: honk.URL,
192 PubDate: honk.Date.Format(time.RFC1123),
193 })
194 if honk.Date.After(modtime) {
195 modtime = honk.Date
196 }
197 }
198 w.Header().Set("Cache-Control", "max-age=300")
199 w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
200
201 err := feed.Write(w)
202 if err != nil {
203 log.Printf("error writing rss: %s", err)
204 }
205}
206
207func butwhatabout(name string) (*WhatAbout, error) {
208 row := stmtWhatAbout.QueryRow(name)
209 var user WhatAbout
210 err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key)
211 user.URL = fmt.Sprintf("https://%s/u/%s", serverName, user.Name)
212 return &user, err
213}
214
215func crappola(j map[string]interface{}) bool {
216 t, _ := jsongetstring(j, "type")
217 a, _ := jsongetstring(j, "actor")
218 o, _ := jsongetstring(j, "object")
219 if t == "Delete" && a == o {
220 log.Printf("crappola from %s", a)
221 return true
222 }
223 return false
224}
225
226func ping(user *WhatAbout, who string) {
227 box, err := getboxes(who)
228 if err != nil {
229 log.Printf("no inbox for ping: %s", err)
230 return
231 }
232 j := NewJunk()
233 j["@context"] = itiswhatitis
234 j["type"] = "Ping"
235 j["id"] = user.URL + "/ping/" + xfiltrate()
236 j["actor"] = user.URL
237 j["to"] = who
238 keyname, key := ziggy(user.Name)
239 err = PostJunk(keyname, key, box.In, j)
240 if err != nil {
241 log.Printf("can't send ping: %s", err)
242 return
243 }
244 log.Printf("sent ping to %s: %s", who, j["id"])
245}
246
247func pong(user *WhatAbout, who string, obj string) {
248 box, err := getboxes(who)
249 if err != nil {
250 log.Printf("no inbox for pong %s : %s", who, err)
251 return
252 }
253 j := NewJunk()
254 j["@context"] = itiswhatitis
255 j["type"] = "Pong"
256 j["id"] = user.URL + "/pong/" + xfiltrate()
257 j["actor"] = user.URL
258 j["to"] = who
259 j["object"] = obj
260 keyname, key := ziggy(user.Name)
261 err = PostJunk(keyname, key, box.In, j)
262 if err != nil {
263 log.Printf("can't send pong: %s", err)
264 return
265 }
266}
267
268func inbox(w http.ResponseWriter, r *http.Request) {
269 name := mux.Vars(r)["name"]
270 user, err := butwhatabout(name)
271 if err != nil {
272 http.NotFound(w, r)
273 return
274 }
275 var buf bytes.Buffer
276 io.Copy(&buf, r.Body)
277 payload := buf.Bytes()
278 j, err := ReadJunk(bytes.NewReader(payload))
279 if err != nil {
280 log.Printf("bad payload: %s", err)
281 io.WriteString(os.Stdout, "bad payload\n")
282 os.Stdout.Write(payload)
283 io.WriteString(os.Stdout, "\n")
284 return
285 }
286 if crappola(j) {
287 return
288 }
289 keyname, err := zag(r, payload)
290 if err != nil {
291 log.Printf("inbox message failed signature: %s", err)
292 if keyname != "" {
293 keyname, err = makeitworksomehowwithoutregardforkeycontinuity(keyname, r, payload)
294 }
295 if err != nil {
296 fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
297 io.WriteString(fd, "bad signature:\n")
298 WriteJunk(fd, j)
299 io.WriteString(fd, "\n")
300 fd.Close()
301 return
302 }
303 }
304 what, _ := jsongetstring(j, "type")
305 if what == "Like" {
306 return
307 }
308 who, _ := jsongetstring(j, "actor")
309 if !keymatch(keyname, who, what, user.ID) {
310 log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
311 return
312 }
313 objid, _ := jsongetstring(j, "id")
314 if thoudostbitethythumb(user.ID, []string{who}, objid) {
315 log.Printf("ignoring thumb sucker %s", who)
316 return
317 }
318 fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
319 WriteJunk(fd, j)
320 io.WriteString(fd, "\n")
321 fd.Close()
322 switch what {
323 case "Ping":
324 obj, _ := jsongetstring(j, "id")
325 log.Printf("ping from %s: %s", who, obj)
326 pong(user, who, obj)
327 case "Pong":
328 obj, _ := jsongetstring(j, "object")
329 log.Printf("pong from %s: %s", who, obj)
330 case "Follow":
331 obj, _ := jsongetstring(j, "object")
332 if obj == user.URL {
333 log.Printf("updating honker follow: %s", who)
334 rubadubdub(user, j)
335 } else {
336 log.Printf("can't follow %s", obj)
337 }
338 case "Accept":
339 log.Printf("updating honker accept: %s", who)
340 _, err = stmtUpdateFlavor.Exec("sub", user.ID, who, "presub")
341 if err != nil {
342 log.Printf("error updating honker: %s", err)
343 return
344 }
345 case "Undo":
346 obj, ok := jsongetmap(j, "object")
347 if !ok {
348 log.Printf("unknown undo no object")
349 } else {
350 what, _ := jsongetstring(obj, "type")
351 switch what {
352 case "Follow":
353 log.Printf("updating honker undo: %s", who)
354 _, err = stmtUpdateFlavor.Exec("undub", user.ID, who, "dub")
355 if err != nil {
356 log.Printf("error updating honker: %s", err)
357 return
358 }
359 case "Like":
360 default:
361 log.Printf("unknown undo: %s", what)
362 }
363 }
364 default:
365 xonk := xonkxonk(user, j)
366 if xonk != nil {
367 savexonk(user, xonk)
368 }
369 }
370}
371
372func outbox(w http.ResponseWriter, r *http.Request) {
373 name := mux.Vars(r)["name"]
374 user, err := butwhatabout(name)
375 if err != nil {
376 http.NotFound(w, r)
377 return
378 }
379 honks := gethonksbyuser(name)
380
381 var jonks []map[string]interface{}
382 for _, h := range honks {
383 j, _ := jonkjonk(user, h)
384 jonks = append(jonks, j)
385 }
386
387 j := NewJunk()
388 j["@context"] = itiswhatitis
389 j["id"] = user.URL + "/outbox"
390 j["type"] = "OrderedCollection"
391 j["totalItems"] = len(jonks)
392 j["orderedItems"] = jonks
393
394 w.Header().Set("Cache-Control", "max-age=60")
395 w.Header().Set("Content-Type", theonetruename)
396 WriteJunk(w, j)
397}
398
399func emptiness(w http.ResponseWriter, r *http.Request) {
400 name := mux.Vars(r)["name"]
401 user, err := butwhatabout(name)
402 if err != nil {
403 http.NotFound(w, r)
404 return
405 }
406 colname := "/followers"
407 if strings.HasSuffix(r.URL.Path, "/following") {
408 colname = "/following"
409 }
410 j := NewJunk()
411 j["@context"] = itiswhatitis
412 j["id"] = user.URL + colname
413 j["type"] = "OrderedCollection"
414 j["totalItems"] = 0
415 j["orderedItems"] = []interface{}{}
416
417 w.Header().Set("Cache-Control", "max-age=60")
418 w.Header().Set("Content-Type", theonetruename)
419 WriteJunk(w, j)
420}
421
422func showuser(w http.ResponseWriter, r *http.Request) {
423 name := mux.Vars(r)["name"]
424 user, err := butwhatabout(name)
425 if err != nil {
426 http.NotFound(w, r)
427 return
428 }
429 if friendorfoe(r.Header.Get("Accept")) {
430 j := asjonker(user)
431 w.Header().Set("Cache-Control", "max-age=600")
432 w.Header().Set("Content-Type", theonetruename)
433 WriteJunk(w, j)
434 return
435 }
436 honks := gethonksbyuser(name)
437 u := login.GetUserInfo(r)
438 honkpage(w, r, u, user, honks, "")
439}
440
441func showhonker(w http.ResponseWriter, r *http.Request) {
442 name := mux.Vars(r)["name"]
443 u := login.GetUserInfo(r)
444 honks := gethonksbyhonker(u.UserID, name)
445 honkpage(w, r, u, nil, honks, "honks by honker: "+name)
446}
447
448func showcombo(w http.ResponseWriter, r *http.Request) {
449 name := mux.Vars(r)["name"]
450 u := login.GetUserInfo(r)
451 honks := gethonksbycombo(u.UserID, name)
452 honkpage(w, r, u, nil, honks, "honks by combo: "+name)
453}
454func showconvoy(w http.ResponseWriter, r *http.Request) {
455 c := r.FormValue("c")
456 var userid int64 = -1
457 u := login.GetUserInfo(r)
458 if u != nil {
459 userid = u.UserID
460 }
461 honks := gethonksbyconvoy(userid, c)
462 honkpage(w, r, u, nil, honks, "honks in convoy: "+c)
463}
464
465func fingerlicker(w http.ResponseWriter, r *http.Request) {
466 orig := r.FormValue("resource")
467
468 log.Printf("finger lick: %s", orig)
469
470 if strings.HasPrefix(orig, "acct:") {
471 orig = orig[5:]
472 }
473
474 name := orig
475 idx := strings.LastIndexByte(name, '/')
476 if idx != -1 {
477 name = name[idx+1:]
478 if "https://"+serverName+"/u/"+name != orig {
479 log.Printf("foreign request rejected")
480 name = ""
481 }
482 } else {
483 idx = strings.IndexByte(name, '@')
484 if idx != -1 {
485 name = name[:idx]
486 if name+"@"+serverName != orig {
487 log.Printf("foreign request rejected")
488 name = ""
489 }
490 }
491 }
492 user, err := butwhatabout(name)
493 if err != nil {
494 http.NotFound(w, r)
495 return
496 }
497
498 j := NewJunk()
499 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
500 j["aliases"] = []string{user.URL}
501 var links []map[string]interface{}
502 l := NewJunk()
503 l["rel"] = "self"
504 l["type"] = `application/activity+json`
505 l["href"] = user.URL
506 links = append(links, l)
507 j["links"] = links
508
509 w.Header().Set("Cache-Control", "max-age=3600")
510 w.Header().Set("Content-Type", "application/jrd+json")
511 WriteJunk(w, j)
512}
513
514func showhonk(w http.ResponseWriter, r *http.Request) {
515 name := mux.Vars(r)["name"]
516 xid := mux.Vars(r)["xid"]
517 user, err := butwhatabout(name)
518 if err != nil {
519 http.NotFound(w, r)
520 return
521 }
522 h := getxonk(name, xid)
523 if h == nil {
524 http.NotFound(w, r)
525 return
526 }
527 if friendorfoe(r.Header.Get("Accept")) {
528 _, j := jonkjonk(user, h)
529 j["@context"] = itiswhatitis
530 w.Header().Set("Cache-Control", "max-age=3600")
531 w.Header().Set("Content-Type", theonetruename)
532 WriteJunk(w, j)
533 return
534 }
535 u := login.GetUserInfo(r)
536 honkpage(w, r, u, nil, []*Honk{h}, "one honk")
537}
538
539func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
540 honks []*Honk, infomsg string) {
541 reverbolate(honks)
542 templinfo := getInfo(r)
543 if u != nil {
544 if user != nil && u.Username == user.Name {
545 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
546 }
547 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
548 }
549 if u == nil {
550 w.Header().Set("Cache-Control", "max-age=60")
551 }
552 if user != nil {
553 templinfo["Name"] = user.Name
554 whatabout := user.About
555 templinfo["RawWhatAbout"] = whatabout
556 whatabout = obfusbreak(whatabout)
557 templinfo["WhatAbout"] = cleanstring(whatabout)
558 }
559 templinfo["Honks"] = honks
560 templinfo["ServerMessage"] = infomsg
561 err := readviews.Execute(w, "honkpage.html", templinfo)
562 if err != nil {
563 log.Print(err)
564 }
565}
566
567func saveuser(w http.ResponseWriter, r *http.Request) {
568 whatabout := r.FormValue("whatabout")
569 u := login.GetUserInfo(r)
570 db := opendatabase()
571 _, err := db.Exec("update users set about = ? where username = ?", whatabout, u.Username)
572 if err != nil {
573 log.Printf("error bouting what: %s", err)
574 }
575
576 http.Redirect(w, r, "/u/"+u.Username, http.StatusSeeOther)
577}
578
579func gethonkers(userid int64) []*Honker {
580 rows, err := stmtHonkers.Query(userid)
581 if err != nil {
582 log.Printf("error querying honkers: %s", err)
583 return nil
584 }
585 defer rows.Close()
586 var honkers []*Honker
587 for rows.Next() {
588 var f Honker
589 var combos string
590 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
591 f.Combos = strings.Split(strings.TrimSpace(combos), " ")
592 if err != nil {
593 log.Printf("error scanning honker: %s", err)
594 return nil
595 }
596 honkers = append(honkers, &f)
597 }
598 return honkers
599}
600
601func getdubs(userid int64) []*Honker {
602 rows, err := stmtDubbers.Query(userid)
603 if err != nil {
604 log.Printf("error querying dubs: %s", err)
605 return nil
606 }
607 defer rows.Close()
608 var honkers []*Honker
609 for rows.Next() {
610 var f Honker
611 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
612 if err != nil {
613 log.Printf("error scanning honker: %s", err)
614 return nil
615 }
616 honkers = append(honkers, &f)
617 }
618 return honkers
619}
620
621func getxonk(name, xid string) *Honk {
622 var h Honk
623 var dt, aud string
624 row := stmtOneXonk.QueryRow(xid)
625 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.XID, &h.RID,
626 &dt, &h.URL, &aud, &h.Noise, &h.Convoy)
627 if err != nil {
628 if err != sql.ErrNoRows {
629 log.Printf("error scanning xonk: %s", err)
630 }
631 return nil
632 }
633 if name != "" && h.Username != name {
634 log.Printf("user xonk mismatch")
635 return nil
636 }
637 h.Date, _ = time.Parse(dbtimeformat, dt)
638 h.Audience = strings.Split(aud, " ")
639 donksforhonks([]*Honk{&h})
640 return &h
641}
642
643func getpublichonks() []*Honk {
644 dt := time.Now().UTC().Add(-2 * 24 * time.Hour).Format(dbtimeformat)
645 rows, err := stmtPublicHonks.Query(dt)
646 return getsomehonks(rows, err)
647}
648func gethonksbyuser(name string) []*Honk {
649 dt := time.Now().UTC().Add(-2 * 24 * time.Hour).Format(dbtimeformat)
650 rows, err := stmtUserHonks.Query(name, dt)
651 return getsomehonks(rows, err)
652}
653func gethonksforuser(userid int64) []*Honk {
654 dt := time.Now().UTC().Add(-2 * 24 * time.Hour).Format(dbtimeformat)
655 rows, err := stmtHonksForUser.Query(userid, dt, userid)
656 return getsomehonks(rows, err)
657}
658func gethonksforme(userid int64) []*Honk {
659 dt := time.Now().UTC().Add(-4 * 24 * time.Hour).Format(dbtimeformat)
660 rows, err := stmtHonksForMe.Query(userid, dt, userid)
661 return getsomehonks(rows, err)
662}
663func gethonksbyhonker(userid int64, honker string) []*Honk {
664 rows, err := stmtHonksByHonker.Query(userid, honker, userid)
665 return getsomehonks(rows, err)
666}
667func gethonksbycombo(userid int64, combo string) []*Honk {
668 combo = "% " + combo + " %"
669 rows, err := stmtHonksByCombo.Query(userid, combo, userid)
670 return getsomehonks(rows, err)
671}
672func gethonksbyconvoy(userid int64, convoy string) []*Honk {
673 rows, err := stmtHonksByConvoy.Query(userid, convoy)
674 return getsomehonks(rows, err)
675}
676
677func getsomehonks(rows *sql.Rows, err error) []*Honk {
678 if err != nil {
679 log.Printf("error querying honks: %s", err)
680 return nil
681 }
682 defer rows.Close()
683 var honks []*Honk
684 for rows.Next() {
685 var h Honk
686 var dt, aud string
687 err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.XID, &h.RID,
688 &dt, &h.URL, &aud, &h.Noise, &h.Convoy)
689 if err != nil {
690 log.Printf("error scanning honks: %s", err)
691 return nil
692 }
693 h.Date, _ = time.Parse(dbtimeformat, dt)
694 h.Audience = strings.Split(aud, " ")
695 honks = append(honks, &h)
696 }
697 rows.Close()
698 donksforhonks(honks)
699 return honks
700}
701
702func donksforhonks(honks []*Honk) {
703 db := opendatabase()
704 var ids []string
705 hmap := make(map[int64]*Honk)
706 for _, h := range honks {
707 ids = append(ids, fmt.Sprintf("%d", h.ID))
708 hmap[h.ID] = h
709 }
710 q := fmt.Sprintf("select honkid, donks.fileid, xid, name, url, media from donks join files on donks.fileid = files.fileid where honkid in (%s)", strings.Join(ids, ","))
711 rows, err := db.Query(q)
712 if err != nil {
713 log.Printf("error querying donks: %s", err)
714 return
715 }
716 defer rows.Close()
717 for rows.Next() {
718 var hid int64
719 var d Donk
720 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media)
721 if err != nil {
722 log.Printf("error scanning donk: %s", err)
723 continue
724 }
725 h := hmap[hid]
726 h.Donks = append(h.Donks, &d)
727 }
728}
729
730func savebonk(w http.ResponseWriter, r *http.Request) {
731 xid := r.FormValue("xid")
732
733 log.Printf("bonking %s", xid)
734
735 xonk := getxonk("", xid)
736 if xonk == nil {
737 return
738 }
739 if xonk.Honker == "" {
740 xonk.XID = fmt.Sprintf("https://%s/u/%s/h/%s", serverName, xonk.Username, xonk.XID)
741 }
742 convoy := xonk.Convoy
743
744 userinfo := login.GetUserInfo(r)
745
746 dt := time.Now().UTC()
747 bonk := Honk{
748 UserID: userinfo.UserID,
749 Username: userinfo.Username,
750 Honker: xonk.Honker,
751 What: "bonk",
752 XID: xonk.XID,
753 Date: dt,
754 Noise: xonk.Noise,
755 Convoy: convoy,
756 Donks: xonk.Donks,
757 Audience: oneofakind(prepend(thewholeworld, xonk.Audience)),
758 }
759
760 user, _ := butwhatabout(userinfo.Username)
761
762 aud := strings.Join(bonk.Audience, " ")
763 whofore := 0
764 if strings.Contains(aud, user.URL) {
765 whofore = 1
766 }
767 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", "", xid, "",
768 dt.Format(dbtimeformat), "", aud, bonk.Noise, bonk.Convoy, whofore)
769 if err != nil {
770 log.Printf("error saving bonk: %s", err)
771 return
772 }
773 bonk.ID, _ = res.LastInsertId()
774 for _, d := range bonk.Donks {
775 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
776 if err != nil {
777 log.Printf("err saving donk: %s", err)
778 return
779 }
780 }
781
782 go honkworldwide(user, &bonk)
783
784}
785
786func zonkit(w http.ResponseWriter, r *http.Request) {
787 wherefore := r.FormValue("wherefore")
788 var what string
789 switch wherefore {
790 case "this honk":
791 what = r.FormValue("honk")
792 wherefore = "zonk"
793 case "this honker":
794 what = r.FormValue("honker")
795 wherefore = "zonker"
796 case "this convoy":
797 what = r.FormValue("convoy")
798 wherefore = "zonvoy"
799 }
800 if what == "" {
801 return
802 }
803
804 log.Printf("zonking %s %s", wherefore, what)
805 userinfo := login.GetUserInfo(r)
806 if wherefore == "zonk" {
807 xonk := getxonk(userinfo.Username, what)
808 stmtZonkIt.Exec(userinfo.UserID, what)
809 if xonk != nil && xonk.Honker == "" {
810 zonk := Honk{
811 What: "zonk",
812 XID: xonk.XID,
813 Date: time.Now().UTC(),
814 Audience: oneofakind(xonk.Audience),
815 }
816
817 user, _ := butwhatabout(userinfo.Username)
818 log.Printf("announcing deleted honk: %s", what)
819 go honkworldwide(user, &zonk)
820 }
821 } else {
822 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
823 if err != nil {
824 log.Printf("error saving zonker: %s", err)
825 return
826 }
827 }
828}
829
830func savehonk(w http.ResponseWriter, r *http.Request) {
831 rid := r.FormValue("rid")
832 noise := r.FormValue("noise")
833
834 userinfo := login.GetUserInfo(r)
835
836 dt := time.Now().UTC()
837 xid := xfiltrate()
838 what := "honk"
839 if rid != "" {
840 what = "tonk"
841 }
842 honk := Honk{
843 UserID: userinfo.UserID,
844 Username: userinfo.Username,
845 What: "honk",
846 XID: xid,
847 Date: dt,
848 }
849 if noise != "" && noise[0] == '@' {
850 honk.Audience = append(grapevine(noise), thewholeworld)
851 } else {
852 honk.Audience = prepend(thewholeworld, grapevine(noise))
853 }
854 var convoy string
855 if rid != "" {
856 xonk := getxonk("", rid)
857 if xonk != nil {
858 if xonk.Honker == "" {
859 rid = "https://" + serverName + "/u/" + xonk.Username + "/h/" + rid
860 }
861 honk.Audience = append(honk.Audience, xonk.Audience...)
862 convoy = xonk.Convoy
863 } else {
864 xonkaud, c := whosthere(rid)
865 honk.Audience = append(honk.Audience, xonkaud...)
866 convoy = c
867 }
868 honk.RID = rid
869 }
870 if convoy == "" {
871 convoy = "data:,electrichonkytonk-" + xfiltrate()
872 }
873 butnottooloud(honk.Audience)
874 honk.Audience = oneofakind(honk.Audience)
875 noise = obfusbreak(noise)
876 honk.Noise = noise
877 honk.Convoy = convoy
878
879 file, filehdr, err := r.FormFile("donk")
880 if err == nil {
881 var buf bytes.Buffer
882 io.Copy(&buf, file)
883 file.Close()
884 data := buf.Bytes()
885 xid := xfiltrate()
886 var media, name string
887 img, err := image.Vacuum(&buf)
888 if err == nil {
889 data = img.Data
890 format := img.Format
891 media = "image/" + format
892 if format == "jpeg" {
893 format = "jpg"
894 }
895 name = xid + "." + format
896 xid = name
897 } else {
898 maxsize := 100000
899 if len(data) > maxsize {
900 log.Printf("bad image: %s too much text: %d", err, len(data))
901 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
902 return
903 }
904 for i := 0; i < len(data); i++ {
905 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
906 log.Printf("bad image: %s not text: %d", err, data[i])
907 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
908 return
909 }
910 }
911 media = "text/plain"
912 name = filehdr.Filename
913 if name == "" {
914 name = xid + ".txt"
915 }
916 xid += ".txt"
917 }
918 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
919 res, err := stmtSaveFile.Exec(xid, name, url, media, data)
920 if err != nil {
921 log.Printf("unable to save image: %s", err)
922 return
923 }
924 var d Donk
925 d.FileID, _ = res.LastInsertId()
926 d.XID = name
927 d.Name = name
928 d.Media = media
929 d.URL = url
930 honk.Donks = append(honk.Donks, &d)
931 }
932 herd := herdofemus(honk.Noise)
933 for _, e := range herd {
934 donk := savedonk(e.ID, e.Name, "image/png")
935 if donk != nil {
936 donk.Name = e.Name
937 honk.Donks = append(honk.Donks, donk)
938 }
939 }
940
941 user, _ := butwhatabout(userinfo.Username)
942
943 aud := strings.Join(honk.Audience, " ")
944 whofore := 0
945 if strings.Contains(aud, user.URL) {
946 whofore = 1
947 }
948 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, "", xid, rid,
949 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore)
950 if err != nil {
951 log.Printf("error saving honk: %s", err)
952 return
953 }
954 honk.ID, _ = res.LastInsertId()
955 for _, d := range honk.Donks {
956 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
957 if err != nil {
958 log.Printf("err saving donk: %s", err)
959 return
960 }
961 }
962
963 go honkworldwide(user, &honk)
964
965 http.Redirect(w, r, "/", http.StatusSeeOther)
966}
967
968func showhonkers(w http.ResponseWriter, r *http.Request) {
969 userinfo := login.GetUserInfo(r)
970 templinfo := getInfo(r)
971 templinfo["Honkers"] = gethonkers(userinfo.UserID)
972 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
973 err := readviews.Execute(w, "honkers.html", templinfo)
974 if err != nil {
975 log.Print(err)
976 }
977}
978
979func showcombos(w http.ResponseWriter, r *http.Request) {
980 userinfo := login.GetUserInfo(r)
981 templinfo := getInfo(r)
982 honkers := gethonkers(userinfo.UserID)
983 var combos []string
984 for _, h := range honkers {
985 combos = append(combos, h.Combos...)
986 }
987 combos = oneofakind(combos)
988 sort.Strings(combos)
989 templinfo["Combos"] = combos
990 err := readviews.Execute(w, "combos.html", templinfo)
991 if err != nil {
992 log.Print(err)
993 }
994}
995
996var handfull = make(map[string]string)
997var handlock sync.Mutex
998
999func gofish(name string) string {
1000 if name[0] == '@' {
1001 name = name[1:]
1002 }
1003 m := strings.Split(name, "@")
1004 if len(m) != 2 {
1005 log.Printf("bad fish name: %s", name)
1006 return ""
1007 }
1008 handlock.Lock()
1009 ref, ok := handfull[name]
1010 handlock.Unlock()
1011 if ok {
1012 return ref
1013 }
1014 j, err := GetJunk(fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
1015 handlock.Lock()
1016 defer handlock.Unlock()
1017 if err != nil {
1018 log.Printf("failed to go fish %s: %s", name, err)
1019 handfull[name] = ""
1020 return ""
1021 }
1022 links, _ := jsongetarray(j, "links")
1023 for _, l := range links {
1024 href, _ := jsongetstring(l, "href")
1025 rel, _ := jsongetstring(l, "rel")
1026 t, _ := jsongetstring(l, "type")
1027 if rel == "self" && friendorfoe(t) {
1028 handfull[name] = href
1029 return href
1030 }
1031 }
1032 handfull[name] = ""
1033 return ""
1034}
1035
1036func savehonker(w http.ResponseWriter, r *http.Request) {
1037 u := login.GetUserInfo(r)
1038 name := r.FormValue("name")
1039 url := r.FormValue("url")
1040 peep := r.FormValue("peep")
1041 combos := r.FormValue("combos")
1042 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1043
1044 if honkerid > 0 {
1045 goodbye := r.FormValue("goodbye")
1046 if goodbye == "goodbye" {
1047 db := opendatabase()
1048 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1049 honkerid, u.UserID)
1050 var xid string
1051 err := row.Scan(&xid)
1052 if err != nil {
1053 log.Printf("can't get honker xid: %s", err)
1054 return
1055 }
1056 log.Printf("unsubscribing from %s", xid)
1057 user, _ := butwhatabout(u.Username)
1058 err = itakeitallback(user, xid)
1059 if err != nil {
1060 log.Printf("can't take it back: %s", err)
1061 } else {
1062 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1063 if err != nil {
1064 log.Printf("error updating honker: %s", err)
1065 return
1066 }
1067 }
1068
1069 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1070 return
1071 }
1072 combos = " " + strings.TrimSpace(combos) + " "
1073 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1074 if err != nil {
1075 log.Printf("update honker err: %s", err)
1076 return
1077 }
1078 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1079 }
1080
1081 flavor := "presub"
1082 if peep == "peep" {
1083 flavor = "peep"
1084 }
1085 if url == "" {
1086 return
1087 }
1088 if url[0] == '@' {
1089 url = gofish(url)
1090 }
1091 if url == "" {
1092 return
1093 }
1094 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1095 if err != nil {
1096 log.Print(err)
1097 return
1098 }
1099 if flavor == "presub" {
1100 user, _ := butwhatabout(u.Username)
1101 go subsub(user, url)
1102 }
1103 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1104}
1105
1106type Zonker struct {
1107 ID int64
1108 Name string
1109 Wherefore string
1110}
1111
1112func killzone(w http.ResponseWriter, r *http.Request) {
1113 db := opendatabase()
1114 userinfo := login.GetUserInfo(r)
1115 rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1116 if err != nil {
1117 log.Printf("err: %s", err)
1118 return
1119 }
1120 var zonkers []Zonker
1121 for rows.Next() {
1122 var z Zonker
1123 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1124 zonkers = append(zonkers, z)
1125 }
1126 templinfo := getInfo(r)
1127 templinfo["Zonkers"] = zonkers
1128 templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1129 err = readviews.Execute(w, "zonkers.html", templinfo)
1130 if err != nil {
1131 log.Print(err)
1132 }
1133}
1134
1135func killitwithfire(w http.ResponseWriter, r *http.Request) {
1136 userinfo := login.GetUserInfo(r)
1137 itsok := r.FormValue("itsok")
1138 if itsok == "iforgiveyou" {
1139 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1140 db := opendatabase()
1141 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1142 userinfo.UserID, zonkerid)
1143 bitethethumbs()
1144 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1145 return
1146 }
1147 wherefore := r.FormValue("wherefore")
1148 name := r.FormValue("name")
1149 if name == "" {
1150 return
1151 }
1152 switch wherefore {
1153 case "zonker":
1154 case "zurl":
1155 case "zonvoy":
1156 default:
1157 return
1158 }
1159 db := opendatabase()
1160 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1161 userinfo.UserID, name, wherefore)
1162 if wherefore == "zonker" || wherefore == "zurl" {
1163 bitethethumbs()
1164 }
1165
1166 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1167}
1168
1169func somedays() string {
1170 secs := 432000 + notrand.Int63n(432000)
1171 return fmt.Sprintf("%d", secs)
1172}
1173
1174func avatate(w http.ResponseWriter, r *http.Request) {
1175 n := r.FormValue("a")
1176 a := avatar(n)
1177 w.Header().Set("Cache-Control", "max-age="+somedays())
1178 w.Write(a)
1179}
1180
1181func servecss(w http.ResponseWriter, r *http.Request) {
1182 w.Header().Set("Cache-Control", "max-age=7776000")
1183 http.ServeFile(w, r, "views"+r.URL.Path)
1184}
1185func servehtml(w http.ResponseWriter, r *http.Request) {
1186 templinfo := getInfo(r)
1187 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1188 if err != nil {
1189 log.Print(err)
1190 }
1191}
1192func serveemu(w http.ResponseWriter, r *http.Request) {
1193 xid := mux.Vars(r)["xid"]
1194 w.Header().Set("Cache-Control", "max-age="+somedays())
1195 http.ServeFile(w, r, "emus/"+xid)
1196}
1197
1198func servefile(w http.ResponseWriter, r *http.Request) {
1199 xid := mux.Vars(r)["xid"]
1200 row := stmtFileData.QueryRow(xid)
1201 var media string
1202 var data []byte
1203 err := row.Scan(&media, &data)
1204 if err != nil {
1205 log.Printf("error loading file: %s", err)
1206 http.NotFound(w, r)
1207 return
1208 }
1209 w.Header().Set("Content-Type", media)
1210 w.Header().Set("X-Content-Type-Options", "nosniff")
1211 w.Header().Set("Cache-Control", "max-age="+somedays())
1212 w.Write(data)
1213}
1214
1215func serve() {
1216 db := opendatabase()
1217 login.Init(db)
1218
1219 listener, err := openListener()
1220 if err != nil {
1221 log.Fatal(err)
1222 }
1223 go redeliverator()
1224
1225 debug := false
1226 getconfig("debug", &debug)
1227 readviews = templates.Load(debug,
1228 "views/honkpage.html",
1229 "views/honkers.html",
1230 "views/zonkers.html",
1231 "views/combos.html",
1232 "views/honkform.html",
1233 "views/honk.html",
1234 "views/login.html",
1235 "views/header.html",
1236 )
1237 if !debug {
1238 s := "views/style.css"
1239 savedstyleparams[s] = getstyleparam(s)
1240 s = "views/local.css"
1241 savedstyleparams[s] = getstyleparam(s)
1242 }
1243
1244 bitethethumbs()
1245
1246 mux := mux.NewRouter()
1247 mux.Use(login.Checker)
1248
1249 posters := mux.Methods("POST").Subrouter()
1250 getters := mux.Methods("GET").Subrouter()
1251
1252 getters.HandleFunc("/", homepage)
1253 getters.HandleFunc("/rss", showrss)
1254 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1255 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1256 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1257 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1258 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1259 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1260 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1261 getters.HandleFunc("/a", avatate)
1262 getters.HandleFunc("/t", showconvoy)
1263 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1264 getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1265 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1266
1267 getters.HandleFunc("/style.css", servecss)
1268 getters.HandleFunc("/local.css", servecss)
1269 getters.HandleFunc("/login", servehtml)
1270 posters.HandleFunc("/dologin", login.LoginFunc)
1271 getters.HandleFunc("/logout", login.LogoutFunc)
1272
1273 loggedin := mux.NewRoute().Subrouter()
1274 loggedin.Use(login.Required)
1275 loggedin.HandleFunc("/atme", homepage)
1276 loggedin.HandleFunc("/killzone", killzone)
1277 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1278 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1279 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1280 loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1281 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1282 loggedin.HandleFunc("/honkers", showhonkers)
1283 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1284 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1285 loggedin.HandleFunc("/c", showcombos)
1286 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1287
1288 err = http.Serve(listener, mux)
1289 if err != nil {
1290 log.Fatal(err)
1291 }
1292}
1293
1294var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1295var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1296var stmtHonksForUser, stmtHonksForMe, stmtSaveDub *sql.Stmt
1297var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1298var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1299var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1300var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtSaveZonker *sql.Stmt
1301var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1302
1303func preparetodie(db *sql.DB, s string) *sql.Stmt {
1304 stmt, err := db.Prepare(s)
1305 if err != nil {
1306 log.Fatalf("error %s: %s", err, s)
1307 }
1308 return stmt
1309}
1310
1311func prepareStatements(db *sql.DB) {
1312 stmtHonkers = preparetodie(db, "select honkerid, userid, name, xid, flavor, combos from honkers where userid = ? and (flavor = 'sub' or flavor = 'peep' or flavor = 'unsub') order by name")
1313 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1314 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1315 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1316 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1317 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1318
1319 selecthonks := "select honkid, honks.userid, username, what, honker, honks.xid, rid, dt, url, audience, noise, convoy from honks join users on honks.userid = users.userid "
1320 limit := " order by honkid desc limit 250"
1321 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1322 stmtOneXonk = preparetodie(db, selecthonks+"where xid = ?")
1323 stmtPublicHonks = preparetodie(db, selecthonks+"where honker = '' and dt > ?"+limit)
1324 stmtUserHonks = preparetodie(db, selecthonks+"where honker = '' and username = ? and dt > ?"+limit)
1325 stmtHonksForUser = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ?"+butnotthose+limit)
1326 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1327 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1328 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1329 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or honker = '') and convoy = ?"+limit)
1330
1331 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1332 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1333 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1334 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1335 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1336 stmtFindFile = preparetodie(db, "select fileid from files where url = ?")
1337 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1338 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1339 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1340 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1341 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1342 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1343 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1344 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl')")
1345 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1346 stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1347 stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1348}
1349
1350func ElaborateUnitTests() {
1351}
1352
1353func finishusersetup() error {
1354 db := opendatabase()
1355 k, err := rsa.GenerateKey(rand.Reader, 2048)
1356 if err != nil {
1357 return err
1358 }
1359 pubkey, err := zem(&k.PublicKey)
1360 if err != nil {
1361 return err
1362 }
1363 seckey, err := zem(k)
1364 if err != nil {
1365 return err
1366 }
1367 _, err = db.Exec("update users set displayname = username, about = ?, pubkey = ?, seckey = ? where userid = 1", "what about me?", pubkey, seckey)
1368 if err != nil {
1369 return err
1370 }
1371 return nil
1372}
1373
1374func main() {
1375 cmd := "run"
1376 if len(os.Args) > 1 {
1377 cmd = os.Args[1]
1378 }
1379 switch cmd {
1380 case "init":
1381 initdb()
1382 case "upgrade":
1383 upgradedb()
1384 }
1385 db := opendatabase()
1386 dbversion := 0
1387 getconfig("dbversion", &dbversion)
1388 if dbversion != myVersion {
1389 log.Fatal("incorrect database version. run upgrade.")
1390 }
1391 getconfig("servername", &serverName)
1392 prepareStatements(db)
1393 switch cmd {
1394 case "ping":
1395 if len(os.Args) < 4 {
1396 fmt.Printf("usage: honk ping from to\n")
1397 return
1398 }
1399 name := os.Args[2]
1400 targ := os.Args[3]
1401 user, err := butwhatabout(name)
1402 if err != nil {
1403 log.Printf("unknown user")
1404 return
1405 }
1406 ping(user, targ)
1407 case "peep":
1408 peeppeep()
1409 case "run":
1410 serve()
1411 case "test":
1412 ElaborateUnitTests()
1413 default:
1414 log.Fatal("unknown command")
1415 }
1416}