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