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 donksforhonks([]*Honk{h})
528 if friendorfoe(r.Header.Get("Accept")) {
529 _, j := jonkjonk(user, h)
530 j["@context"] = itiswhatitis
531 w.Header().Set("Cache-Control", "max-age=3600")
532 w.Header().Set("Content-Type", theonetruename)
533 WriteJunk(w, j)
534 return
535 }
536 u := login.GetUserInfo(r)
537 honkpage(w, r, u, nil, []*Honk{h}, "one honk")
538}
539
540func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
541 honks []*Honk, infomsg string) {
542 reverbolate(honks)
543 templinfo := getInfo(r)
544 if u != nil {
545 if user != nil && u.Username == user.Name {
546 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
547 }
548 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
549 }
550 if u == nil {
551 w.Header().Set("Cache-Control", "max-age=60")
552 }
553 if user != nil {
554 templinfo["Name"] = user.Name
555 whatabout := user.About
556 templinfo["RawWhatAbout"] = whatabout
557 whatabout = obfusbreak(whatabout)
558 templinfo["WhatAbout"] = cleanstring(whatabout)
559 }
560 templinfo["Honks"] = honks
561 templinfo["ServerMessage"] = infomsg
562 err := readviews.Execute(w, "honkpage.html", templinfo)
563 if err != nil {
564 log.Print(err)
565 }
566}
567
568func saveuser(w http.ResponseWriter, r *http.Request) {
569 whatabout := r.FormValue("whatabout")
570 u := login.GetUserInfo(r)
571 db := opendatabase()
572 _, err := db.Exec("update users set about = ? where username = ?", whatabout, u.Username)
573 if err != nil {
574 log.Printf("error bouting what: %s", err)
575 }
576
577 http.Redirect(w, r, "/u/"+u.Username, http.StatusSeeOther)
578}
579
580func gethonkers(userid int64) []*Honker {
581 rows, err := stmtHonkers.Query(userid)
582 if err != nil {
583 log.Printf("error querying honkers: %s", err)
584 return nil
585 }
586 defer rows.Close()
587 var honkers []*Honker
588 for rows.Next() {
589 var f Honker
590 var combos string
591 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
592 f.Combos = strings.Split(strings.TrimSpace(combos), " ")
593 if err != nil {
594 log.Printf("error scanning honker: %s", err)
595 return nil
596 }
597 honkers = append(honkers, &f)
598 }
599 return honkers
600}
601
602func getdubs(userid int64) []*Honker {
603 rows, err := stmtDubbers.Query(userid)
604 if err != nil {
605 log.Printf("error querying dubs: %s", err)
606 return nil
607 }
608 defer rows.Close()
609 var honkers []*Honker
610 for rows.Next() {
611 var f Honker
612 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
613 if err != nil {
614 log.Printf("error scanning honker: %s", err)
615 return nil
616 }
617 honkers = append(honkers, &f)
618 }
619 return honkers
620}
621
622func getxonk(userid int64, xid string) *Honk {
623 var h Honk
624 var dt, aud string
625 row := stmtOneXonk.QueryRow(userid, xid)
626 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.XID, &h.RID,
627 &dt, &h.URL, &aud, &h.Noise, &h.Convoy)
628 if err != nil {
629 if err != sql.ErrNoRows {
630 log.Printf("error scanning xonk: %s", err)
631 }
632 return nil
633 }
634 h.Date, _ = time.Parse(dbtimeformat, dt)
635 h.Audience = strings.Split(aud, " ")
636 return &h
637}
638
639func getpublichonks() []*Honk {
640 dt := time.Now().UTC().Add(-7 * 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(-7 * 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(-7 * 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(-7 * 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: []string{thewholeworld},
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
780func zonkit(w http.ResponseWriter, r *http.Request) {
781 wherefore := r.FormValue("wherefore")
782 var what string
783 switch wherefore {
784 case "this honk":
785 what = r.FormValue("honk")
786 wherefore = "zonk"
787 case "this honker":
788 what = r.FormValue("honker")
789 wherefore = "zonker"
790 case "this convoy":
791 what = r.FormValue("convoy")
792 wherefore = "zonvoy"
793 }
794 if what == "" {
795 return
796 }
797
798 log.Printf("zonking %s %s", wherefore, what)
799 userinfo := login.GetUserInfo(r)
800 if wherefore == "zonk" {
801 xonk := getxonk(userinfo.UserID, what)
802 if xonk != nil {
803 stmtZonkDonks.Exec(xonk.ID)
804 stmtZonkIt.Exec(userinfo.UserID, what)
805 if xonk.Honker == "" {
806 zonk := Honk{
807 What: "zonk",
808 XID: xonk.XID,
809 Date: time.Now().UTC(),
810 Audience: oneofakind(xonk.Audience),
811 }
812
813 user, _ := butwhatabout(userinfo.Username)
814 log.Printf("announcing deleted honk: %s", what)
815 go honkworldwide(user, &zonk)
816 }
817 }
818 } else {
819 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
820 if err != nil {
821 log.Printf("error saving zonker: %s", err)
822 return
823 }
824 }
825}
826
827func savehonk(w http.ResponseWriter, r *http.Request) {
828 rid := r.FormValue("rid")
829 noise := r.FormValue("noise")
830
831 userinfo := login.GetUserInfo(r)
832
833 dt := time.Now().UTC()
834 xid := xfiltrate()
835 what := "honk"
836 if rid != "" {
837 what = "tonk"
838 }
839 honk := Honk{
840 UserID: userinfo.UserID,
841 Username: userinfo.Username,
842 What: "honk",
843 XID: xid,
844 Date: dt,
845 }
846 if noise != "" && noise[0] == '@' {
847 honk.Audience = append(grapevine(noise), thewholeworld)
848 } else {
849 honk.Audience = prepend(thewholeworld, grapevine(noise))
850 }
851 var convoy string
852 if rid != "" {
853 xonk := getxonk(userinfo.UserID, rid)
854 if xonk != nil {
855 if xonk.Honker == "" {
856 rid = "https://" + serverName + "/u/" + xonk.Username + "/h/" + rid
857 }
858 honk.Audience = append(honk.Audience, xonk.Audience...)
859 convoy = xonk.Convoy
860 } else {
861 xonkaud, c := whosthere(rid)
862 honk.Audience = append(honk.Audience, xonkaud...)
863 convoy = c
864 }
865 honk.RID = rid
866 }
867 if convoy == "" {
868 convoy = "data:,electrichonkytonk-" + xfiltrate()
869 }
870 butnottooloud(honk.Audience)
871 honk.Audience = oneofakind(honk.Audience)
872 noise = obfusbreak(noise)
873 honk.Noise = noise
874 honk.Convoy = convoy
875
876 file, filehdr, err := r.FormFile("donk")
877 if err == nil {
878 var buf bytes.Buffer
879 io.Copy(&buf, file)
880 file.Close()
881 data := buf.Bytes()
882 xid := xfiltrate()
883 var media, name string
884 img, err := image.Vacuum(&buf)
885 if err == nil {
886 data = img.Data
887 format := img.Format
888 media = "image/" + format
889 if format == "jpeg" {
890 format = "jpg"
891 }
892 name = xid + "." + format
893 xid = name
894 } else {
895 maxsize := 100000
896 if len(data) > maxsize {
897 log.Printf("bad image: %s too much text: %d", err, len(data))
898 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
899 return
900 }
901 for i := 0; i < len(data); i++ {
902 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
903 log.Printf("bad image: %s not text: %d", err, data[i])
904 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
905 return
906 }
907 }
908 media = "text/plain"
909 name = filehdr.Filename
910 if name == "" {
911 name = xid + ".txt"
912 }
913 xid += ".txt"
914 }
915 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
916 res, err := stmtSaveFile.Exec(xid, name, url, media, data)
917 if err != nil {
918 log.Printf("unable to save image: %s", err)
919 return
920 }
921 var d Donk
922 d.FileID, _ = res.LastInsertId()
923 d.XID = name
924 d.Name = name
925 d.Media = media
926 d.URL = url
927 honk.Donks = append(honk.Donks, &d)
928 }
929 herd := herdofemus(honk.Noise)
930 for _, e := range herd {
931 donk := savedonk(e.ID, e.Name, "image/png")
932 if donk != nil {
933 donk.Name = e.Name
934 honk.Donks = append(honk.Donks, donk)
935 }
936 }
937
938 user, _ := butwhatabout(userinfo.Username)
939
940 aud := strings.Join(honk.Audience, " ")
941 whofore := 0
942 if strings.Contains(aud, user.URL) {
943 whofore = 1
944 }
945 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, "", xid, rid,
946 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore)
947 if err != nil {
948 log.Printf("error saving honk: %s", err)
949 return
950 }
951 honk.ID, _ = res.LastInsertId()
952 for _, d := range honk.Donks {
953 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
954 if err != nil {
955 log.Printf("err saving donk: %s", err)
956 return
957 }
958 }
959
960 go honkworldwide(user, &honk)
961
962 http.Redirect(w, r, "/", http.StatusSeeOther)
963}
964
965func showhonkers(w http.ResponseWriter, r *http.Request) {
966 userinfo := login.GetUserInfo(r)
967 templinfo := getInfo(r)
968 templinfo["Honkers"] = gethonkers(userinfo.UserID)
969 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
970 err := readviews.Execute(w, "honkers.html", templinfo)
971 if err != nil {
972 log.Print(err)
973 }
974}
975
976func showcombos(w http.ResponseWriter, r *http.Request) {
977 userinfo := login.GetUserInfo(r)
978 templinfo := getInfo(r)
979 honkers := gethonkers(userinfo.UserID)
980 var combos []string
981 for _, h := range honkers {
982 combos = append(combos, h.Combos...)
983 }
984 combos = oneofakind(combos)
985 sort.Strings(combos)
986 templinfo["Combos"] = combos
987 err := readviews.Execute(w, "combos.html", templinfo)
988 if err != nil {
989 log.Print(err)
990 }
991}
992
993var handfull = make(map[string]string)
994var handlock sync.Mutex
995
996func gofish(name string) string {
997 if name[0] == '@' {
998 name = name[1:]
999 }
1000 m := strings.Split(name, "@")
1001 if len(m) != 2 {
1002 log.Printf("bad fish name: %s", name)
1003 return ""
1004 }
1005 handlock.Lock()
1006 ref, ok := handfull[name]
1007 handlock.Unlock()
1008 if ok {
1009 return ref
1010 }
1011 j, err := GetJunk(fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
1012 handlock.Lock()
1013 defer handlock.Unlock()
1014 if err != nil {
1015 log.Printf("failed to go fish %s: %s", name, err)
1016 handfull[name] = ""
1017 return ""
1018 }
1019 links, _ := jsongetarray(j, "links")
1020 for _, l := range links {
1021 href, _ := jsongetstring(l, "href")
1022 rel, _ := jsongetstring(l, "rel")
1023 t, _ := jsongetstring(l, "type")
1024 if rel == "self" && friendorfoe(t) {
1025 handfull[name] = href
1026 return href
1027 }
1028 }
1029 handfull[name] = ""
1030 return ""
1031}
1032
1033func savehonker(w http.ResponseWriter, r *http.Request) {
1034 u := login.GetUserInfo(r)
1035 name := r.FormValue("name")
1036 url := r.FormValue("url")
1037 peep := r.FormValue("peep")
1038 combos := r.FormValue("combos")
1039 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1040
1041 if honkerid > 0 {
1042 goodbye := r.FormValue("goodbye")
1043 if goodbye == "goodbye" {
1044 db := opendatabase()
1045 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1046 honkerid, u.UserID)
1047 var xid string
1048 err := row.Scan(&xid)
1049 if err != nil {
1050 log.Printf("can't get honker xid: %s", err)
1051 return
1052 }
1053 log.Printf("unsubscribing from %s", xid)
1054 user, _ := butwhatabout(u.Username)
1055 err = itakeitallback(user, xid)
1056 if err != nil {
1057 log.Printf("can't take it back: %s", err)
1058 } else {
1059 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1060 if err != nil {
1061 log.Printf("error updating honker: %s", err)
1062 return
1063 }
1064 }
1065
1066 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1067 return
1068 }
1069 combos = " " + strings.TrimSpace(combos) + " "
1070 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1071 if err != nil {
1072 log.Printf("update honker err: %s", err)
1073 return
1074 }
1075 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1076 }
1077
1078 flavor := "presub"
1079 if peep == "peep" {
1080 flavor = "peep"
1081 }
1082 if url == "" {
1083 return
1084 }
1085 if url[0] == '@' {
1086 url = gofish(url)
1087 }
1088 if url == "" {
1089 return
1090 }
1091 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1092 if err != nil {
1093 log.Print(err)
1094 return
1095 }
1096 if flavor == "presub" {
1097 user, _ := butwhatabout(u.Username)
1098 go subsub(user, url)
1099 }
1100 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1101}
1102
1103type Zonker struct {
1104 ID int64
1105 Name string
1106 Wherefore string
1107}
1108
1109func killzone(w http.ResponseWriter, r *http.Request) {
1110 db := opendatabase()
1111 userinfo := login.GetUserInfo(r)
1112 rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1113 if err != nil {
1114 log.Printf("err: %s", err)
1115 return
1116 }
1117 var zonkers []Zonker
1118 for rows.Next() {
1119 var z Zonker
1120 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1121 zonkers = append(zonkers, z)
1122 }
1123 templinfo := getInfo(r)
1124 templinfo["Zonkers"] = zonkers
1125 templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1126 err = readviews.Execute(w, "zonkers.html", templinfo)
1127 if err != nil {
1128 log.Print(err)
1129 }
1130}
1131
1132func killitwithfire(w http.ResponseWriter, r *http.Request) {
1133 userinfo := login.GetUserInfo(r)
1134 itsok := r.FormValue("itsok")
1135 if itsok == "iforgiveyou" {
1136 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1137 db := opendatabase()
1138 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1139 userinfo.UserID, zonkerid)
1140 bitethethumbs()
1141 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1142 return
1143 }
1144 wherefore := r.FormValue("wherefore")
1145 name := r.FormValue("name")
1146 if name == "" {
1147 return
1148 }
1149 switch wherefore {
1150 case "zonker":
1151 case "zurl":
1152 case "zonvoy":
1153 default:
1154 return
1155 }
1156 db := opendatabase()
1157 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1158 userinfo.UserID, name, wherefore)
1159 if wherefore == "zonker" || wherefore == "zurl" {
1160 bitethethumbs()
1161 }
1162
1163 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1164}
1165
1166func somedays() string {
1167 secs := 432000 + notrand.Int63n(432000)
1168 return fmt.Sprintf("%d", secs)
1169}
1170
1171func avatate(w http.ResponseWriter, r *http.Request) {
1172 n := r.FormValue("a")
1173 a := avatar(n)
1174 w.Header().Set("Cache-Control", "max-age="+somedays())
1175 w.Write(a)
1176}
1177
1178func servecss(w http.ResponseWriter, r *http.Request) {
1179 w.Header().Set("Cache-Control", "max-age=7776000")
1180 http.ServeFile(w, r, "views"+r.URL.Path)
1181}
1182func servehtml(w http.ResponseWriter, r *http.Request) {
1183 templinfo := getInfo(r)
1184 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1185 if err != nil {
1186 log.Print(err)
1187 }
1188}
1189func serveemu(w http.ResponseWriter, r *http.Request) {
1190 xid := mux.Vars(r)["xid"]
1191 w.Header().Set("Cache-Control", "max-age="+somedays())
1192 http.ServeFile(w, r, "emus/"+xid)
1193}
1194
1195func servefile(w http.ResponseWriter, r *http.Request) {
1196 xid := mux.Vars(r)["xid"]
1197 row := stmtFileData.QueryRow(xid)
1198 var media string
1199 var data []byte
1200 err := row.Scan(&media, &data)
1201 if err != nil {
1202 log.Printf("error loading file: %s", err)
1203 http.NotFound(w, r)
1204 return
1205 }
1206 w.Header().Set("Content-Type", media)
1207 w.Header().Set("X-Content-Type-Options", "nosniff")
1208 w.Header().Set("Cache-Control", "max-age="+somedays())
1209 w.Write(data)
1210}
1211
1212func serve() {
1213 db := opendatabase()
1214 login.Init(db)
1215
1216 listener, err := openListener()
1217 if err != nil {
1218 log.Fatal(err)
1219 }
1220 go redeliverator()
1221
1222 debug := false
1223 getconfig("debug", &debug)
1224 readviews = templates.Load(debug,
1225 "views/honkpage.html",
1226 "views/honkers.html",
1227 "views/zonkers.html",
1228 "views/combos.html",
1229 "views/honkform.html",
1230 "views/honk.html",
1231 "views/login.html",
1232 "views/header.html",
1233 )
1234 if !debug {
1235 s := "views/style.css"
1236 savedstyleparams[s] = getstyleparam(s)
1237 s = "views/local.css"
1238 savedstyleparams[s] = getstyleparam(s)
1239 }
1240
1241 bitethethumbs()
1242
1243 mux := mux.NewRouter()
1244 mux.Use(login.Checker)
1245
1246 posters := mux.Methods("POST").Subrouter()
1247 getters := mux.Methods("GET").Subrouter()
1248
1249 getters.HandleFunc("/", homepage)
1250 getters.HandleFunc("/rss", showrss)
1251 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1252 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1253 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1254 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1255 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1256 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1257 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1258 getters.HandleFunc("/a", avatate)
1259 getters.HandleFunc("/t", showconvoy)
1260 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1261 getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1262 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1263
1264 getters.HandleFunc("/style.css", servecss)
1265 getters.HandleFunc("/local.css", servecss)
1266 getters.HandleFunc("/login", servehtml)
1267 posters.HandleFunc("/dologin", login.LoginFunc)
1268 getters.HandleFunc("/logout", login.LogoutFunc)
1269
1270 loggedin := mux.NewRoute().Subrouter()
1271 loggedin.Use(login.Required)
1272 loggedin.HandleFunc("/atme", homepage)
1273 loggedin.HandleFunc("/killzone", killzone)
1274 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1275 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1276 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1277 loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1278 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1279 loggedin.HandleFunc("/honkers", showhonkers)
1280 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1281 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1282 loggedin.HandleFunc("/c", showcombos)
1283 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1284
1285 err = http.Serve(listener, mux)
1286 if err != nil {
1287 log.Fatal(err)
1288 }
1289}
1290
1291var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1292var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1293var stmtHonksForUser, stmtHonksForMe, stmtSaveDub *sql.Stmt
1294var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1295var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1296var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1297var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1298var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1299
1300func preparetodie(db *sql.DB, s string) *sql.Stmt {
1301 stmt, err := db.Prepare(s)
1302 if err != nil {
1303 log.Fatalf("error %s: %s", err, s)
1304 }
1305 return stmt
1306}
1307
1308func prepareStatements(db *sql.DB) {
1309 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")
1310 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1311 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1312 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1313 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1314 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1315
1316 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 "
1317 limit := " order by honkid desc limit 250"
1318 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1319 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1320 stmtPublicHonks = preparetodie(db, selecthonks+"where honker = '' and dt > ?"+limit)
1321 stmtUserHonks = preparetodie(db, selecthonks+"where honker = '' and username = ? and dt > ?"+limit)
1322 stmtHonksForUser = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ?"+butnotthose+limit)
1323 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1324 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1325 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1326 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or honker = '') and convoy = ?"+limit)
1327
1328 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1329 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1330 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1331 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1332 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1333 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1334 stmtFindFile = preparetodie(db, "select fileid from files where url = ?")
1335 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1336 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1337 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1338 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1339 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1340 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1341 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1342 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl')")
1343 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1344 stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1345 stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1346}
1347
1348func ElaborateUnitTests() {
1349}
1350
1351func finishusersetup() error {
1352 db := opendatabase()
1353 k, err := rsa.GenerateKey(rand.Reader, 2048)
1354 if err != nil {
1355 return err
1356 }
1357 pubkey, err := zem(&k.PublicKey)
1358 if err != nil {
1359 return err
1360 }
1361 seckey, err := zem(k)
1362 if err != nil {
1363 return err
1364 }
1365 _, err = db.Exec("update users set displayname = username, about = ?, pubkey = ?, seckey = ? where userid = 1", "what about me?", pubkey, seckey)
1366 if err != nil {
1367 return err
1368 }
1369 return nil
1370}
1371
1372func main() {
1373 cmd := "run"
1374 if len(os.Args) > 1 {
1375 cmd = os.Args[1]
1376 }
1377 switch cmd {
1378 case "init":
1379 initdb()
1380 case "upgrade":
1381 upgradedb()
1382 }
1383 db := opendatabase()
1384 dbversion := 0
1385 getconfig("dbversion", &dbversion)
1386 if dbversion != myVersion {
1387 log.Fatal("incorrect database version. run upgrade.")
1388 }
1389 getconfig("servername", &serverName)
1390 prepareStatements(db)
1391 switch cmd {
1392 case "ping":
1393 if len(os.Args) < 4 {
1394 fmt.Printf("usage: honk ping from to\n")
1395 return
1396 }
1397 name := os.Args[2]
1398 targ := os.Args[3]
1399 user, err := butwhatabout(name)
1400 if err != nil {
1401 log.Printf("unknown user")
1402 return
1403 }
1404 ping(user, targ)
1405 case "peep":
1406 peeppeep()
1407 case "run":
1408 serve()
1409 case "test":
1410 ElaborateUnitTests()
1411 default:
1412 log.Fatal("unknown command")
1413 }
1414}