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