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 honk.Audience = oneofakind(honk.Audience)
881 noise = obfusbreak(noise)
882 honk.Noise = noise
883 honk.Convoy = convoy
884
885 file, filehdr, err := r.FormFile("donk")
886 if err == nil {
887 var buf bytes.Buffer
888 io.Copy(&buf, file)
889 file.Close()
890 data := buf.Bytes()
891 xid := xfiltrate()
892 var media, name string
893 img, format, err := image.Decode(&buf)
894 if err == nil {
895 data, format, err = vacuumwrap(img, format)
896 if err != nil {
897 log.Printf("can't vacuum image: %s", err)
898 return
899 }
900 media = "image/" + format
901 if format == "jpeg" {
902 format = "jpg"
903 }
904 name = xid + "." + format
905 xid = name
906 } else {
907 maxsize := 100000
908 if len(data) > maxsize {
909 log.Printf("bad image: %s too much text: %d", err, len(data))
910 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
911 return
912 }
913 for i := 0; i < len(data); i++ {
914 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
915 log.Printf("bad image: %s not text: %d", err, data[i])
916 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
917 return
918 }
919 }
920 media = "text/plain"
921 name = filehdr.Filename
922 if name == "" {
923 name = xid + ".txt"
924 }
925 xid += ".txt"
926 }
927 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
928 res, err := stmtSaveFile.Exec(xid, name, url, media, data)
929 if err != nil {
930 log.Printf("unable to save image: %s", err)
931 return
932 }
933 var d Donk
934 d.FileID, _ = res.LastInsertId()
935 d.XID = name
936 d.Name = name
937 d.Media = media
938 d.URL = url
939 honk.Donks = append(honk.Donks, &d)
940 }
941 herd := herdofemus(honk.Noise)
942 for _, e := range herd {
943 donk := savedonk(e.ID, e.Name, "image/png")
944 if donk != nil {
945 donk.Name = e.Name
946 honk.Donks = append(honk.Donks, donk)
947 }
948 }
949
950 user, _ := butwhatabout(userinfo.Username)
951
952 aud := strings.Join(honk.Audience, " ")
953 whofore := 0
954 if strings.Contains(aud, user.URL) {
955 whofore = 1
956 }
957 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, "", xid, rid,
958 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore)
959 if err != nil {
960 log.Printf("error saving honk: %s", err)
961 return
962 }
963 honk.ID, _ = res.LastInsertId()
964 for _, d := range honk.Donks {
965 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
966 if err != nil {
967 log.Printf("err saving donk: %s", err)
968 return
969 }
970 }
971
972 go honkworldwide(user, &honk)
973
974 http.Redirect(w, r, "/", http.StatusSeeOther)
975}
976
977func showhonkers(w http.ResponseWriter, r *http.Request) {
978 userinfo := login.GetUserInfo(r)
979 templinfo := getInfo(r)
980 templinfo["Honkers"] = gethonkers(userinfo.UserID)
981 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
982 err := readviews.Execute(w, "honkers.html", templinfo)
983 if err != nil {
984 log.Print(err)
985 }
986}
987
988func showcombos(w http.ResponseWriter, r *http.Request) {
989 userinfo := login.GetUserInfo(r)
990 templinfo := getInfo(r)
991 honkers := gethonkers(userinfo.UserID)
992 var combos []string
993 for _, h := range honkers {
994 combos = append(combos, h.Combos...)
995 }
996 combos = oneofakind(combos)
997 sort.Strings(combos)
998 templinfo["Combos"] = combos
999 err := readviews.Execute(w, "combos.html", templinfo)
1000 if err != nil {
1001 log.Print(err)
1002 }
1003}
1004
1005var handfull = make(map[string]string)
1006var handlock sync.Mutex
1007
1008func gofish(name string) string {
1009 if name[0] == '@' {
1010 name = name[1:]
1011 }
1012 m := strings.Split(name, "@")
1013 if len(m) != 2 {
1014 log.Printf("bad fish name: %s", name)
1015 return ""
1016 }
1017 handlock.Lock()
1018 ref, ok := handfull[name]
1019 handlock.Unlock()
1020 if ok {
1021 return ref
1022 }
1023 j, err := GetJunk(fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
1024 handlock.Lock()
1025 defer handlock.Unlock()
1026 if err != nil {
1027 log.Printf("failed to go fish %s: %s", name, err)
1028 handfull[name] = ""
1029 return ""
1030 }
1031 links, _ := jsongetarray(j, "links")
1032 for _, l := range links {
1033 href, _ := jsongetstring(l, "href")
1034 rel, _ := jsongetstring(l, "rel")
1035 t, _ := jsongetstring(l, "type")
1036 if rel == "self" && friendorfoe(t) {
1037 handfull[name] = href
1038 return href
1039 }
1040 }
1041 handfull[name] = ""
1042 return ""
1043}
1044
1045func savehonker(w http.ResponseWriter, r *http.Request) {
1046 u := login.GetUserInfo(r)
1047 name := r.FormValue("name")
1048 url := r.FormValue("url")
1049 peep := r.FormValue("peep")
1050 combos := r.FormValue("combos")
1051 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1052
1053 if honkerid > 0 {
1054 goodbye := r.FormValue("goodbye")
1055 if goodbye == "goodbye" {
1056 db := opendatabase()
1057 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1058 honkerid, u.UserID)
1059 var xid string
1060 err := row.Scan(&xid)
1061 if err != nil {
1062 log.Printf("can't get honker xid: %s", err)
1063 return
1064 }
1065 log.Printf("unsubscribing from %s", xid)
1066 user, _ := butwhatabout(u.Username)
1067 err = itakeitallback(user, xid)
1068 if err != nil {
1069 log.Printf("can't take it back: %s", err)
1070 } else {
1071 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1072 if err != nil {
1073 log.Printf("error updating honker: %s", err)
1074 return
1075 }
1076 }
1077
1078 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1079 return
1080 }
1081 combos = " " + strings.TrimSpace(combos) + " "
1082 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1083 if err != nil {
1084 log.Printf("update honker err: %s", err)
1085 return
1086 }
1087 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1088 }
1089
1090 flavor := "presub"
1091 if peep == "peep" {
1092 flavor = "peep"
1093 }
1094 if url == "" {
1095 return
1096 }
1097 if url[0] == '@' {
1098 url = gofish(url)
1099 }
1100 if url == "" {
1101 return
1102 }
1103 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1104 if err != nil {
1105 log.Print(err)
1106 return
1107 }
1108 if flavor == "presub" {
1109 user, _ := butwhatabout(u.Username)
1110 go subsub(user, url)
1111 }
1112 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1113}
1114
1115type Zonker struct {
1116 ID int64
1117 Name string
1118 Wherefore string
1119}
1120
1121func killzone(w http.ResponseWriter, r *http.Request) {
1122 db := opendatabase()
1123 userinfo := login.GetUserInfo(r)
1124 rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1125 if err != nil {
1126 log.Printf("err: %s", err)
1127 return
1128 }
1129 var zonkers []Zonker
1130 for rows.Next() {
1131 var z Zonker
1132 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1133 zonkers = append(zonkers, z)
1134 }
1135 templinfo := getInfo(r)
1136 templinfo["Zonkers"] = zonkers
1137 templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1138 err = readviews.Execute(w, "zonkers.html", templinfo)
1139 if err != nil {
1140 log.Print(err)
1141 }
1142}
1143
1144func killitwithfire(w http.ResponseWriter, r *http.Request) {
1145 userinfo := login.GetUserInfo(r)
1146 itsok := r.FormValue("itsok")
1147 if itsok == "iforgiveyou" {
1148 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1149 db := opendatabase()
1150 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1151 userinfo.UserID, zonkerid)
1152 bitethethumbs()
1153 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1154 return
1155 }
1156 wherefore := r.FormValue("wherefore")
1157 name := r.FormValue("name")
1158 if name == "" {
1159 return
1160 }
1161 switch wherefore {
1162 case "zonker":
1163 case "zurl":
1164 case "zonvoy":
1165 default:
1166 return
1167 }
1168 db := opendatabase()
1169 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1170 userinfo.UserID, name, wherefore)
1171 if wherefore == "zonker" || wherefore == "zurl" {
1172 bitethethumbs()
1173 }
1174
1175 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1176}
1177
1178func somedays() string {
1179 secs := 432000 + notrand.Int63n(432000)
1180 return fmt.Sprintf("%d", secs)
1181}
1182
1183func avatate(w http.ResponseWriter, r *http.Request) {
1184 n := r.FormValue("a")
1185 a := avatar(n)
1186 w.Header().Set("Cache-Control", "max-age="+somedays())
1187 w.Write(a)
1188}
1189
1190func servecss(w http.ResponseWriter, r *http.Request) {
1191 w.Header().Set("Cache-Control", "max-age=7776000")
1192 http.ServeFile(w, r, "views"+r.URL.Path)
1193}
1194func servehtml(w http.ResponseWriter, r *http.Request) {
1195 templinfo := getInfo(r)
1196 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1197 if err != nil {
1198 log.Print(err)
1199 }
1200}
1201func serveemu(w http.ResponseWriter, r *http.Request) {
1202 xid := mux.Vars(r)["xid"]
1203 w.Header().Set("Cache-Control", "max-age="+somedays())
1204 http.ServeFile(w, r, "emus/"+xid)
1205}
1206
1207func servefile(w http.ResponseWriter, r *http.Request) {
1208 xid := mux.Vars(r)["xid"]
1209 row := stmtFileData.QueryRow(xid)
1210 var media string
1211 var data []byte
1212 err := row.Scan(&media, &data)
1213 if err != nil {
1214 log.Printf("error loading file: %s", err)
1215 http.NotFound(w, r)
1216 return
1217 }
1218 w.Header().Set("Content-Type", media)
1219 w.Header().Set("X-Content-Type-Options", "nosniff")
1220 w.Header().Set("Cache-Control", "max-age="+somedays())
1221 w.Write(data)
1222}
1223
1224func serve() {
1225 db := opendatabase()
1226 login.Init(db)
1227
1228 listener, err := openListener()
1229 if err != nil {
1230 log.Fatal(err)
1231 }
1232 go redeliverator()
1233
1234 debug := false
1235 getconfig("debug", &debug)
1236 readviews = templates.Load(debug,
1237 "views/honkpage.html",
1238 "views/honkers.html",
1239 "views/zonkers.html",
1240 "views/combos.html",
1241 "views/honkform.html",
1242 "views/honk.html",
1243 "views/login.html",
1244 "views/header.html",
1245 )
1246 if !debug {
1247 s := "views/style.css"
1248 savedstyleparams[s] = getstyleparam(s)
1249 s = "views/local.css"
1250 savedstyleparams[s] = getstyleparam(s)
1251 }
1252
1253 bitethethumbs()
1254
1255 mux := mux.NewRouter()
1256 mux.Use(login.Checker)
1257
1258 posters := mux.Methods("POST").Subrouter()
1259 getters := mux.Methods("GET").Subrouter()
1260
1261 getters.HandleFunc("/", homepage)
1262 getters.HandleFunc("/rss", showrss)
1263 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1264 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1265 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1266 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1267 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1268 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1269 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1270 getters.HandleFunc("/a", avatate)
1271 getters.HandleFunc("/t", showconvoy)
1272 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1273 getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1274 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1275
1276 getters.HandleFunc("/style.css", servecss)
1277 getters.HandleFunc("/local.css", servecss)
1278 getters.HandleFunc("/login", servehtml)
1279 posters.HandleFunc("/dologin", login.LoginFunc)
1280 getters.HandleFunc("/logout", login.LogoutFunc)
1281
1282 loggedin := mux.NewRoute().Subrouter()
1283 loggedin.Use(login.Required)
1284 loggedin.HandleFunc("/atme", homepage)
1285 loggedin.HandleFunc("/killzone", killzone)
1286 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1287 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1288 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1289 loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1290 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1291 loggedin.HandleFunc("/honkers", showhonkers)
1292 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1293 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1294 loggedin.HandleFunc("/c", showcombos)
1295 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1296
1297 err = http.Serve(listener, mux)
1298 if err != nil {
1299 log.Fatal(err)
1300 }
1301}
1302
1303var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1304var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1305var stmtHonksForUser, stmtHonksForMe, stmtDeleteHonk, stmtSaveDub *sql.Stmt
1306var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1307var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1308var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1309var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtSaveZonker *sql.Stmt
1310var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1311
1312func preparetodie(db *sql.DB, s string) *sql.Stmt {
1313 stmt, err := db.Prepare(s)
1314 if err != nil {
1315 log.Fatalf("error %s: %s", err, s)
1316 }
1317 return stmt
1318}
1319
1320func prepareStatements(db *sql.DB) {
1321 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")
1322 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1323 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1324 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1325 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1326 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1327
1328 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 "
1329 limit := " order by honkid desc limit 250"
1330 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1331 stmtOneXonk = preparetodie(db, selecthonks+"where xid = ?")
1332 stmtPublicHonks = preparetodie(db, selecthonks+"where honker = ''"+limit)
1333 stmtUserHonks = preparetodie(db, selecthonks+"where honker = '' and username = ?"+limit)
1334 stmtHonksForUser = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ?"+butnotthose+limit)
1335 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1336 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1337 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1338 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or honker = '') and convoy = ?"+limit)
1339
1340 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1341 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1342 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1343 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1344 stmtDeleteHonk = preparetodie(db, "delete from honks where xid = ? and honker = ? and userid = ?")
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}