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