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