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(user.ID, 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(userid int64, xid string) *Honk {
622 var h Honk
623 var dt, aud string
624 row := stmtOneXonk.QueryRow(userid, 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 h.Date, _ = time.Parse(dbtimeformat, dt)
634 h.Audience = strings.Split(aud, " ")
635 donksforhonks([]*Honk{&h})
636 return &h
637}
638
639func getpublichonks() []*Honk {
640 dt := time.Now().UTC().Add(-2 * 24 * time.Hour).Format(dbtimeformat)
641 rows, err := stmtPublicHonks.Query(dt)
642 return getsomehonks(rows, err)
643}
644func gethonksbyuser(name string) []*Honk {
645 dt := time.Now().UTC().Add(-2 * 24 * time.Hour).Format(dbtimeformat)
646 rows, err := stmtUserHonks.Query(name, dt)
647 return getsomehonks(rows, err)
648}
649func gethonksforuser(userid int64) []*Honk {
650 dt := time.Now().UTC().Add(-2 * 24 * time.Hour).Format(dbtimeformat)
651 rows, err := stmtHonksForUser.Query(userid, dt, userid)
652 return getsomehonks(rows, err)
653}
654func gethonksforme(userid int64) []*Honk {
655 dt := time.Now().UTC().Add(-4 * 24 * time.Hour).Format(dbtimeformat)
656 rows, err := stmtHonksForMe.Query(userid, dt, userid)
657 return getsomehonks(rows, err)
658}
659func gethonksbyhonker(userid int64, honker string) []*Honk {
660 rows, err := stmtHonksByHonker.Query(userid, honker, userid)
661 return getsomehonks(rows, err)
662}
663func gethonksbycombo(userid int64, combo string) []*Honk {
664 combo = "% " + combo + " %"
665 rows, err := stmtHonksByCombo.Query(userid, combo, userid)
666 return getsomehonks(rows, err)
667}
668func gethonksbyconvoy(userid int64, convoy string) []*Honk {
669 rows, err := stmtHonksByConvoy.Query(userid, convoy)
670 return getsomehonks(rows, err)
671}
672
673func getsomehonks(rows *sql.Rows, err error) []*Honk {
674 if err != nil {
675 log.Printf("error querying honks: %s", err)
676 return nil
677 }
678 defer rows.Close()
679 var honks []*Honk
680 for rows.Next() {
681 var h Honk
682 var dt, aud string
683 err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.XID, &h.RID,
684 &dt, &h.URL, &aud, &h.Noise, &h.Convoy)
685 if err != nil {
686 log.Printf("error scanning honks: %s", err)
687 return nil
688 }
689 h.Date, _ = time.Parse(dbtimeformat, dt)
690 h.Audience = strings.Split(aud, " ")
691 honks = append(honks, &h)
692 }
693 rows.Close()
694 donksforhonks(honks)
695 return honks
696}
697
698func donksforhonks(honks []*Honk) {
699 db := opendatabase()
700 var ids []string
701 hmap := make(map[int64]*Honk)
702 for _, h := range honks {
703 ids = append(ids, fmt.Sprintf("%d", h.ID))
704 hmap[h.ID] = h
705 }
706 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, ","))
707 rows, err := db.Query(q)
708 if err != nil {
709 log.Printf("error querying donks: %s", err)
710 return
711 }
712 defer rows.Close()
713 for rows.Next() {
714 var hid int64
715 var d Donk
716 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media)
717 if err != nil {
718 log.Printf("error scanning donk: %s", err)
719 continue
720 }
721 h := hmap[hid]
722 h.Donks = append(h.Donks, &d)
723 }
724}
725
726func savebonk(w http.ResponseWriter, r *http.Request) {
727 xid := r.FormValue("xid")
728 userinfo := login.GetUserInfo(r)
729
730 log.Printf("bonking %s", xid)
731
732 xonk := getxonk(userinfo.UserID, xid)
733 if xonk == nil {
734 return
735 }
736 if xonk.Honker == "" {
737 xonk.XID = fmt.Sprintf("https://%s/u/%s/h/%s", serverName, xonk.Username, xonk.XID)
738 }
739 convoy := xonk.Convoy
740
741 dt := time.Now().UTC()
742 bonk := Honk{
743 UserID: userinfo.UserID,
744 Username: userinfo.Username,
745 Honker: xonk.Honker,
746 What: "bonk",
747 XID: xonk.XID,
748 Date: dt,
749 Noise: xonk.Noise,
750 Convoy: convoy,
751 Donks: xonk.Donks,
752 Audience: oneofakind(prepend(thewholeworld, xonk.Audience)),
753 }
754
755 user, _ := butwhatabout(userinfo.Username)
756
757 aud := strings.Join(bonk.Audience, " ")
758 whofore := 0
759 if strings.Contains(aud, user.URL) {
760 whofore = 1
761 }
762 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", "", xid, "",
763 dt.Format(dbtimeformat), "", aud, bonk.Noise, bonk.Convoy, whofore)
764 if err != nil {
765 log.Printf("error saving bonk: %s", err)
766 return
767 }
768 bonk.ID, _ = res.LastInsertId()
769 for _, d := range bonk.Donks {
770 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
771 if err != nil {
772 log.Printf("err saving donk: %s", err)
773 return
774 }
775 }
776
777 go honkworldwide(user, &bonk)
778
779}
780
781func zonkit(w http.ResponseWriter, r *http.Request) {
782 wherefore := r.FormValue("wherefore")
783 var what string
784 switch wherefore {
785 case "this honk":
786 what = r.FormValue("honk")
787 wherefore = "zonk"
788 case "this honker":
789 what = r.FormValue("honker")
790 wherefore = "zonker"
791 case "this convoy":
792 what = r.FormValue("convoy")
793 wherefore = "zonvoy"
794 }
795 if what == "" {
796 return
797 }
798
799 log.Printf("zonking %s %s", wherefore, what)
800 userinfo := login.GetUserInfo(r)
801 if wherefore == "zonk" {
802 xonk := getxonk(userinfo.UserID, what)
803 if xonk != nil {
804 stmtZonkDonks.Exec(xonk.ID)
805 stmtZonkIt.Exec(userinfo.UserID, what)
806 if xonk.Honker == "" {
807 zonk := Honk{
808 What: "zonk",
809 XID: xonk.XID,
810 Date: time.Now().UTC(),
811 Audience: oneofakind(xonk.Audience),
812 }
813
814 user, _ := butwhatabout(userinfo.Username)
815 log.Printf("announcing deleted honk: %s", what)
816 go honkworldwide(user, &zonk)
817 }
818 }
819 } else {
820 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
821 if err != nil {
822 log.Printf("error saving zonker: %s", err)
823 return
824 }
825 }
826}
827
828func savehonk(w http.ResponseWriter, r *http.Request) {
829 rid := r.FormValue("rid")
830 noise := r.FormValue("noise")
831
832 userinfo := login.GetUserInfo(r)
833
834 dt := time.Now().UTC()
835 xid := xfiltrate()
836 what := "honk"
837 if rid != "" {
838 what = "tonk"
839 }
840 honk := Honk{
841 UserID: userinfo.UserID,
842 Username: userinfo.Username,
843 What: "honk",
844 XID: xid,
845 Date: dt,
846 }
847 if noise != "" && noise[0] == '@' {
848 honk.Audience = append(grapevine(noise), thewholeworld)
849 } else {
850 honk.Audience = prepend(thewholeworld, grapevine(noise))
851 }
852 var convoy string
853 if rid != "" {
854 xonk := getxonk(userinfo.UserID, rid)
855 if xonk != nil {
856 if xonk.Honker == "" {
857 rid = "https://" + serverName + "/u/" + xonk.Username + "/h/" + rid
858 }
859 honk.Audience = append(honk.Audience, xonk.Audience...)
860 convoy = xonk.Convoy
861 } else {
862 xonkaud, c := whosthere(rid)
863 honk.Audience = append(honk.Audience, xonkaud...)
864 convoy = c
865 }
866 honk.RID = rid
867 }
868 if convoy == "" {
869 convoy = "data:,electrichonkytonk-" + xfiltrate()
870 }
871 butnottooloud(honk.Audience)
872 honk.Audience = oneofakind(honk.Audience)
873 noise = obfusbreak(noise)
874 honk.Noise = noise
875 honk.Convoy = convoy
876
877 file, filehdr, err := r.FormFile("donk")
878 if err == nil {
879 var buf bytes.Buffer
880 io.Copy(&buf, file)
881 file.Close()
882 data := buf.Bytes()
883 xid := xfiltrate()
884 var media, name string
885 img, err := image.Vacuum(&buf)
886 if err == nil {
887 data = img.Data
888 format := img.Format
889 media = "image/" + format
890 if format == "jpeg" {
891 format = "jpg"
892 }
893 name = xid + "." + format
894 xid = name
895 } else {
896 maxsize := 100000
897 if len(data) > maxsize {
898 log.Printf("bad image: %s too much text: %d", err, len(data))
899 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
900 return
901 }
902 for i := 0; i < len(data); i++ {
903 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
904 log.Printf("bad image: %s not text: %d", err, data[i])
905 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
906 return
907 }
908 }
909 media = "text/plain"
910 name = filehdr.Filename
911 if name == "" {
912 name = xid + ".txt"
913 }
914 xid += ".txt"
915 }
916 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
917 res, err := stmtSaveFile.Exec(xid, name, url, media, data)
918 if err != nil {
919 log.Printf("unable to save image: %s", err)
920 return
921 }
922 var d Donk
923 d.FileID, _ = res.LastInsertId()
924 d.XID = name
925 d.Name = name
926 d.Media = media
927 d.URL = url
928 honk.Donks = append(honk.Donks, &d)
929 }
930 herd := herdofemus(honk.Noise)
931 for _, e := range herd {
932 donk := savedonk(e.ID, e.Name, "image/png")
933 if donk != nil {
934 donk.Name = e.Name
935 honk.Donks = append(honk.Donks, donk)
936 }
937 }
938
939 user, _ := butwhatabout(userinfo.Username)
940
941 aud := strings.Join(honk.Audience, " ")
942 whofore := 0
943 if strings.Contains(aud, user.URL) {
944 whofore = 1
945 }
946 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, "", xid, rid,
947 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore)
948 if err != nil {
949 log.Printf("error saving honk: %s", err)
950 return
951 }
952 honk.ID, _ = res.LastInsertId()
953 for _, d := range honk.Donks {
954 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
955 if err != nil {
956 log.Printf("err saving donk: %s", err)
957 return
958 }
959 }
960
961 go honkworldwide(user, &honk)
962
963 http.Redirect(w, r, "/", http.StatusSeeOther)
964}
965
966func showhonkers(w http.ResponseWriter, r *http.Request) {
967 userinfo := login.GetUserInfo(r)
968 templinfo := getInfo(r)
969 templinfo["Honkers"] = gethonkers(userinfo.UserID)
970 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
971 err := readviews.Execute(w, "honkers.html", templinfo)
972 if err != nil {
973 log.Print(err)
974 }
975}
976
977func showcombos(w http.ResponseWriter, r *http.Request) {
978 userinfo := login.GetUserInfo(r)
979 templinfo := getInfo(r)
980 honkers := gethonkers(userinfo.UserID)
981 var combos []string
982 for _, h := range honkers {
983 combos = append(combos, h.Combos...)
984 }
985 combos = oneofakind(combos)
986 sort.Strings(combos)
987 templinfo["Combos"] = combos
988 err := readviews.Execute(w, "combos.html", templinfo)
989 if err != nil {
990 log.Print(err)
991 }
992}
993
994var handfull = make(map[string]string)
995var handlock sync.Mutex
996
997func gofish(name string) string {
998 if name[0] == '@' {
999 name = name[1:]
1000 }
1001 m := strings.Split(name, "@")
1002 if len(m) != 2 {
1003 log.Printf("bad fish name: %s", name)
1004 return ""
1005 }
1006 handlock.Lock()
1007 ref, ok := handfull[name]
1008 handlock.Unlock()
1009 if ok {
1010 return ref
1011 }
1012 j, err := GetJunk(fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
1013 handlock.Lock()
1014 defer handlock.Unlock()
1015 if err != nil {
1016 log.Printf("failed to go fish %s: %s", name, err)
1017 handfull[name] = ""
1018 return ""
1019 }
1020 links, _ := jsongetarray(j, "links")
1021 for _, l := range links {
1022 href, _ := jsongetstring(l, "href")
1023 rel, _ := jsongetstring(l, "rel")
1024 t, _ := jsongetstring(l, "type")
1025 if rel == "self" && friendorfoe(t) {
1026 handfull[name] = href
1027 return href
1028 }
1029 }
1030 handfull[name] = ""
1031 return ""
1032}
1033
1034func savehonker(w http.ResponseWriter, r *http.Request) {
1035 u := login.GetUserInfo(r)
1036 name := r.FormValue("name")
1037 url := r.FormValue("url")
1038 peep := r.FormValue("peep")
1039 combos := r.FormValue("combos")
1040 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1041
1042 if honkerid > 0 {
1043 goodbye := r.FormValue("goodbye")
1044 if goodbye == "goodbye" {
1045 db := opendatabase()
1046 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1047 honkerid, u.UserID)
1048 var xid string
1049 err := row.Scan(&xid)
1050 if err != nil {
1051 log.Printf("can't get honker xid: %s", err)
1052 return
1053 }
1054 log.Printf("unsubscribing from %s", xid)
1055 user, _ := butwhatabout(u.Username)
1056 err = itakeitallback(user, xid)
1057 if err != nil {
1058 log.Printf("can't take it back: %s", err)
1059 } else {
1060 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1061 if err != nil {
1062 log.Printf("error updating honker: %s", err)
1063 return
1064 }
1065 }
1066
1067 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1068 return
1069 }
1070 combos = " " + strings.TrimSpace(combos) + " "
1071 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1072 if err != nil {
1073 log.Printf("update honker err: %s", err)
1074 return
1075 }
1076 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1077 }
1078
1079 flavor := "presub"
1080 if peep == "peep" {
1081 flavor = "peep"
1082 }
1083 if url == "" {
1084 return
1085 }
1086 if url[0] == '@' {
1087 url = gofish(url)
1088 }
1089 if url == "" {
1090 return
1091 }
1092 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1093 if err != nil {
1094 log.Print(err)
1095 return
1096 }
1097 if flavor == "presub" {
1098 user, _ := butwhatabout(u.Username)
1099 go subsub(user, url)
1100 }
1101 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1102}
1103
1104type Zonker struct {
1105 ID int64
1106 Name string
1107 Wherefore string
1108}
1109
1110func killzone(w http.ResponseWriter, r *http.Request) {
1111 db := opendatabase()
1112 userinfo := login.GetUserInfo(r)
1113 rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1114 if err != nil {
1115 log.Printf("err: %s", err)
1116 return
1117 }
1118 var zonkers []Zonker
1119 for rows.Next() {
1120 var z Zonker
1121 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1122 zonkers = append(zonkers, z)
1123 }
1124 templinfo := getInfo(r)
1125 templinfo["Zonkers"] = zonkers
1126 templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1127 err = readviews.Execute(w, "zonkers.html", templinfo)
1128 if err != nil {
1129 log.Print(err)
1130 }
1131}
1132
1133func killitwithfire(w http.ResponseWriter, r *http.Request) {
1134 userinfo := login.GetUserInfo(r)
1135 itsok := r.FormValue("itsok")
1136 if itsok == "iforgiveyou" {
1137 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1138 db := opendatabase()
1139 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1140 userinfo.UserID, zonkerid)
1141 bitethethumbs()
1142 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1143 return
1144 }
1145 wherefore := r.FormValue("wherefore")
1146 name := r.FormValue("name")
1147 if name == "" {
1148 return
1149 }
1150 switch wherefore {
1151 case "zonker":
1152 case "zurl":
1153 case "zonvoy":
1154 default:
1155 return
1156 }
1157 db := opendatabase()
1158 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1159 userinfo.UserID, name, wherefore)
1160 if wherefore == "zonker" || wherefore == "zurl" {
1161 bitethethumbs()
1162 }
1163
1164 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1165}
1166
1167func somedays() string {
1168 secs := 432000 + notrand.Int63n(432000)
1169 return fmt.Sprintf("%d", secs)
1170}
1171
1172func avatate(w http.ResponseWriter, r *http.Request) {
1173 n := r.FormValue("a")
1174 a := avatar(n)
1175 w.Header().Set("Cache-Control", "max-age="+somedays())
1176 w.Write(a)
1177}
1178
1179func servecss(w http.ResponseWriter, r *http.Request) {
1180 w.Header().Set("Cache-Control", "max-age=7776000")
1181 http.ServeFile(w, r, "views"+r.URL.Path)
1182}
1183func servehtml(w http.ResponseWriter, r *http.Request) {
1184 templinfo := getInfo(r)
1185 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1186 if err != nil {
1187 log.Print(err)
1188 }
1189}
1190func serveemu(w http.ResponseWriter, r *http.Request) {
1191 xid := mux.Vars(r)["xid"]
1192 w.Header().Set("Cache-Control", "max-age="+somedays())
1193 http.ServeFile(w, r, "emus/"+xid)
1194}
1195
1196func servefile(w http.ResponseWriter, r *http.Request) {
1197 xid := mux.Vars(r)["xid"]
1198 row := stmtFileData.QueryRow(xid)
1199 var media string
1200 var data []byte
1201 err := row.Scan(&media, &data)
1202 if err != nil {
1203 log.Printf("error loading file: %s", err)
1204 http.NotFound(w, r)
1205 return
1206 }
1207 w.Header().Set("Content-Type", media)
1208 w.Header().Set("X-Content-Type-Options", "nosniff")
1209 w.Header().Set("Cache-Control", "max-age="+somedays())
1210 w.Write(data)
1211}
1212
1213func serve() {
1214 db := opendatabase()
1215 login.Init(db)
1216
1217 listener, err := openListener()
1218 if err != nil {
1219 log.Fatal(err)
1220 }
1221 go redeliverator()
1222
1223 debug := false
1224 getconfig("debug", &debug)
1225 readviews = templates.Load(debug,
1226 "views/honkpage.html",
1227 "views/honkers.html",
1228 "views/zonkers.html",
1229 "views/combos.html",
1230 "views/honkform.html",
1231 "views/honk.html",
1232 "views/login.html",
1233 "views/header.html",
1234 )
1235 if !debug {
1236 s := "views/style.css"
1237 savedstyleparams[s] = getstyleparam(s)
1238 s = "views/local.css"
1239 savedstyleparams[s] = getstyleparam(s)
1240 }
1241
1242 bitethethumbs()
1243
1244 mux := mux.NewRouter()
1245 mux.Use(login.Checker)
1246
1247 posters := mux.Methods("POST").Subrouter()
1248 getters := mux.Methods("GET").Subrouter()
1249
1250 getters.HandleFunc("/", homepage)
1251 getters.HandleFunc("/rss", showrss)
1252 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1253 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1254 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1255 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1256 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1257 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1258 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1259 getters.HandleFunc("/a", avatate)
1260 getters.HandleFunc("/t", showconvoy)
1261 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1262 getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1263 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1264
1265 getters.HandleFunc("/style.css", servecss)
1266 getters.HandleFunc("/local.css", servecss)
1267 getters.HandleFunc("/login", servehtml)
1268 posters.HandleFunc("/dologin", login.LoginFunc)
1269 getters.HandleFunc("/logout", login.LogoutFunc)
1270
1271 loggedin := mux.NewRoute().Subrouter()
1272 loggedin.Use(login.Required)
1273 loggedin.HandleFunc("/atme", homepage)
1274 loggedin.HandleFunc("/killzone", killzone)
1275 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1276 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1277 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1278 loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1279 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1280 loggedin.HandleFunc("/honkers", showhonkers)
1281 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1282 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1283 loggedin.HandleFunc("/c", showcombos)
1284 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1285
1286 err = http.Serve(listener, mux)
1287 if err != nil {
1288 log.Fatal(err)
1289 }
1290}
1291
1292var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1293var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1294var stmtHonksForUser, stmtHonksForMe, stmtSaveDub *sql.Stmt
1295var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1296var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1297var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1298var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1299var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1300
1301func preparetodie(db *sql.DB, s string) *sql.Stmt {
1302 stmt, err := db.Prepare(s)
1303 if err != nil {
1304 log.Fatalf("error %s: %s", err, s)
1305 }
1306 return stmt
1307}
1308
1309func prepareStatements(db *sql.DB) {
1310 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")
1311 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1312 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1313 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1314 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1315 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1316
1317 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 "
1318 limit := " order by honkid desc limit 250"
1319 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1320 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1321 stmtPublicHonks = preparetodie(db, selecthonks+"where honker = '' and dt > ?"+limit)
1322 stmtUserHonks = preparetodie(db, selecthonks+"where honker = '' and username = ? and dt > ?"+limit)
1323 stmtHonksForUser = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ?"+butnotthose+limit)
1324 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1325 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1326 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1327 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or honker = '') and convoy = ?"+limit)
1328
1329 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1330 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1331 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1332 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1333 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1334 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1335 stmtFindFile = preparetodie(db, "select fileid from files where url = ?")
1336 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1337 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1338 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1339 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1340 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1341 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1342 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1343 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl')")
1344 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1345 stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1346 stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1347}
1348
1349func ElaborateUnitTests() {
1350}
1351
1352func finishusersetup() error {
1353 db := opendatabase()
1354 k, err := rsa.GenerateKey(rand.Reader, 2048)
1355 if err != nil {
1356 return err
1357 }
1358 pubkey, err := zem(&k.PublicKey)
1359 if err != nil {
1360 return err
1361 }
1362 seckey, err := zem(k)
1363 if err != nil {
1364 return err
1365 }
1366 _, err = db.Exec("update users set displayname = username, about = ?, pubkey = ?, seckey = ? where userid = 1", "what about me?", pubkey, seckey)
1367 if err != nil {
1368 return err
1369 }
1370 return nil
1371}
1372
1373func main() {
1374 cmd := "run"
1375 if len(os.Args) > 1 {
1376 cmd = os.Args[1]
1377 }
1378 switch cmd {
1379 case "init":
1380 initdb()
1381 case "upgrade":
1382 upgradedb()
1383 }
1384 db := opendatabase()
1385 dbversion := 0
1386 getconfig("dbversion", &dbversion)
1387 if dbversion != myVersion {
1388 log.Fatal("incorrect database version. run upgrade.")
1389 }
1390 getconfig("servername", &serverName)
1391 prepareStatements(db)
1392 switch cmd {
1393 case "ping":
1394 if len(os.Args) < 4 {
1395 fmt.Printf("usage: honk ping from to\n")
1396 return
1397 }
1398 name := os.Args[2]
1399 targ := os.Args[3]
1400 user, err := butwhatabout(name)
1401 if err != nil {
1402 log.Printf("unknown user")
1403 return
1404 }
1405 ping(user, targ)
1406 case "peep":
1407 peeppeep()
1408 case "run":
1409 serve()
1410 case "test":
1411 ElaborateUnitTests()
1412 default:
1413 log.Fatal("unknown command")
1414 }
1415}