honk.go (view raw)
1//
2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
3//
4// Permission to use, copy, modify, and distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16package main
17
18import (
19 "bytes"
20 "crypto/rand"
21 "crypto/rsa"
22 "database/sql"
23 "fmt"
24 "html"
25 "html/template"
26 "image"
27 "io"
28 "log"
29 notrand "math/rand"
30 "net/http"
31 "os"
32 "sort"
33 "strconv"
34 "strings"
35 "sync"
36 "time"
37
38 "github.com/gorilla/mux"
39 "humungus.tedunangst.com/r/webs/login"
40 "humungus.tedunangst.com/r/webs/templates"
41)
42
43type WhatAbout struct {
44 ID int64
45 Name string
46 Display string
47 About string
48 Key string
49 URL string
50}
51
52type Honk struct {
53 ID int64
54 UserID int64
55 Username string
56 What string
57 Honker string
58 XID string
59 RID string
60 Date time.Time
61 URL string
62 Noise string
63 Convoy string
64 Audience []string
65 Privacy string
66 HTML template.HTML
67 Donks []*Donk
68}
69
70type Donk struct {
71 FileID int64
72 XID string
73 Name string
74 URL string
75 Media string
76 Content []byte
77}
78
79type Honker struct {
80 ID int64
81 UserID int64
82 Name string
83 XID string
84 Flavor string
85 Combos []string
86}
87
88var serverName string
89var iconName = "icon.png"
90
91var readviews *templates.Template
92
93func getInfo(r *http.Request) map[string]interface{} {
94 templinfo := make(map[string]interface{})
95 templinfo["StyleParam"] = getstyleparam("views/style.css")
96 templinfo["LocalStyleParam"] = getstyleparam("views/local.css")
97 templinfo["ServerName"] = serverName
98 templinfo["IconName"] = iconName
99 templinfo["UserInfo"] = login.GetUserInfo(r)
100 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
101 return templinfo
102}
103
104func homepage(w http.ResponseWriter, r *http.Request) {
105 templinfo := getInfo(r)
106 u := login.GetUserInfo(r)
107 var honks []*Honk
108 if u != nil {
109 if r.URL.Path == "/atme" {
110 honks = gethonksforme(u.UserID)
111 } else {
112 honks = gethonksforuser(u.UserID)
113 }
114 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
115 } else {
116 honks = getpublichonks()
117 }
118
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[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, format, err := image.Decode(&buf)
887 if err == nil {
888 data, format, err = vacuumwrap(img, format)
889 if err != nil {
890 log.Printf("can't vacuum image: %s", err)
891 return
892 }
893 media = "image/" + format
894 if format == "jpeg" {
895 format = "jpg"
896 }
897 name = xid + "." + format
898 xid = name
899 } else {
900 maxsize := 100000
901 if len(data) > maxsize {
902 log.Printf("bad image: %s too much text: %d", err, len(data))
903 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
904 return
905 }
906 for i := 0; i < len(data); i++ {
907 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
908 log.Printf("bad image: %s not text: %d", err, data[i])
909 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
910 return
911 }
912 }
913 media = "text/plain"
914 name = filehdr.Filename
915 if name == "" {
916 name = xid + ".txt"
917 }
918 xid += ".txt"
919 }
920 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
921 res, err := stmtSaveFile.Exec(xid, name, url, media, data)
922 if err != nil {
923 log.Printf("unable to save image: %s", err)
924 return
925 }
926 var d Donk
927 d.FileID, _ = res.LastInsertId()
928 d.XID = name
929 d.Name = name
930 d.Media = media
931 d.URL = url
932 honk.Donks = append(honk.Donks, &d)
933 }
934 herd := herdofemus(honk.Noise)
935 for _, e := range herd {
936 donk := savedonk(e.ID, e.Name, "image/png")
937 if donk != nil {
938 donk.Name = e.Name
939 honk.Donks = append(honk.Donks, donk)
940 }
941 }
942
943 user, _ := butwhatabout(userinfo.Username)
944
945 aud := strings.Join(honk.Audience, " ")
946 whofore := 0
947 if strings.Contains(aud, user.URL) {
948 whofore = 1
949 }
950 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, "", xid, rid,
951 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore)
952 if err != nil {
953 log.Printf("error saving honk: %s", err)
954 return
955 }
956 honk.ID, _ = res.LastInsertId()
957 for _, d := range honk.Donks {
958 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
959 if err != nil {
960 log.Printf("err saving donk: %s", err)
961 return
962 }
963 }
964
965 go honkworldwide(user, &honk)
966
967 http.Redirect(w, r, "/", http.StatusSeeOther)
968}
969
970func showhonkers(w http.ResponseWriter, r *http.Request) {
971 userinfo := login.GetUserInfo(r)
972 templinfo := getInfo(r)
973 templinfo["Honkers"] = gethonkers(userinfo.UserID)
974 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
975 err := readviews.Execute(w, "honkers.html", templinfo)
976 if err != nil {
977 log.Print(err)
978 }
979}
980
981func showcombos(w http.ResponseWriter, r *http.Request) {
982 userinfo := login.GetUserInfo(r)
983 templinfo := getInfo(r)
984 honkers := gethonkers(userinfo.UserID)
985 var combos []string
986 for _, h := range honkers {
987 combos = append(combos, h.Combos...)
988 }
989 combos = oneofakind(combos)
990 sort.Strings(combos)
991 templinfo["Combos"] = combos
992 err := readviews.Execute(w, "combos.html", templinfo)
993 if err != nil {
994 log.Print(err)
995 }
996}
997
998var handfull = make(map[string]string)
999var handlock sync.Mutex
1000
1001func gofish(name string) string {
1002 if name[0] == '@' {
1003 name = name[1:]
1004 }
1005 m := strings.Split(name, "@")
1006 if len(m) != 2 {
1007 log.Printf("bad fish name: %s", name)
1008 return ""
1009 }
1010 handlock.Lock()
1011 ref, ok := handfull[name]
1012 handlock.Unlock()
1013 if ok {
1014 return ref
1015 }
1016 j, err := GetJunk(fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
1017 handlock.Lock()
1018 defer handlock.Unlock()
1019 if err != nil {
1020 log.Printf("failed to go fish %s: %s", name, err)
1021 handfull[name] = ""
1022 return ""
1023 }
1024 links, _ := jsongetarray(j, "links")
1025 for _, l := range links {
1026 href, _ := jsongetstring(l, "href")
1027 rel, _ := jsongetstring(l, "rel")
1028 t, _ := jsongetstring(l, "type")
1029 if rel == "self" && friendorfoe(t) {
1030 handfull[name] = href
1031 return href
1032 }
1033 }
1034 handfull[name] = ""
1035 return ""
1036}
1037
1038func savehonker(w http.ResponseWriter, r *http.Request) {
1039 u := login.GetUserInfo(r)
1040 name := r.FormValue("name")
1041 url := r.FormValue("url")
1042 peep := r.FormValue("peep")
1043 combos := r.FormValue("combos")
1044 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1045
1046 if honkerid > 0 {
1047 goodbye := r.FormValue("goodbye")
1048 if goodbye == "goodbye" {
1049 db := opendatabase()
1050 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1051 honkerid, u.UserID)
1052 var xid string
1053 err := row.Scan(&xid)
1054 if err != nil {
1055 log.Printf("can't get honker xid: %s", err)
1056 return
1057 }
1058 log.Printf("unsubscribing from %s", xid)
1059 user, _ := butwhatabout(u.Username)
1060 err = itakeitallback(user, xid)
1061 if err != nil {
1062 log.Printf("can't take it back: %s", err)
1063 } else {
1064 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1065 if err != nil {
1066 log.Printf("error updating honker: %s", err)
1067 return
1068 }
1069 }
1070
1071 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1072 return
1073 }
1074 combos = " " + strings.TrimSpace(combos) + " "
1075 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1076 if err != nil {
1077 log.Printf("update honker err: %s", err)
1078 return
1079 }
1080 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1081 }
1082
1083 flavor := "presub"
1084 if peep == "peep" {
1085 flavor = "peep"
1086 }
1087 if url == "" {
1088 return
1089 }
1090 if url[0] == '@' {
1091 url = gofish(url)
1092 }
1093 if url == "" {
1094 return
1095 }
1096 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1097 if err != nil {
1098 log.Print(err)
1099 return
1100 }
1101 if flavor == "presub" {
1102 user, _ := butwhatabout(u.Username)
1103 go subsub(user, url)
1104 }
1105 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1106}
1107
1108type Zonker struct {
1109 ID int64
1110 Name string
1111 Wherefore string
1112}
1113
1114func killzone(w http.ResponseWriter, r *http.Request) {
1115 db := opendatabase()
1116 userinfo := login.GetUserInfo(r)
1117 rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1118 if err != nil {
1119 log.Printf("err: %s", err)
1120 return
1121 }
1122 var zonkers []Zonker
1123 for rows.Next() {
1124 var z Zonker
1125 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1126 zonkers = append(zonkers, z)
1127 }
1128 templinfo := getInfo(r)
1129 templinfo["Zonkers"] = zonkers
1130 templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1131 err = readviews.Execute(w, "zonkers.html", templinfo)
1132 if err != nil {
1133 log.Print(err)
1134 }
1135}
1136
1137func killitwithfire(w http.ResponseWriter, r *http.Request) {
1138 userinfo := login.GetUserInfo(r)
1139 itsok := r.FormValue("itsok")
1140 if itsok == "iforgiveyou" {
1141 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1142 db := opendatabase()
1143 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1144 userinfo.UserID, zonkerid)
1145 bitethethumbs()
1146 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1147 return
1148 }
1149 wherefore := r.FormValue("wherefore")
1150 name := r.FormValue("name")
1151 if name == "" {
1152 return
1153 }
1154 switch wherefore {
1155 case "zonker":
1156 case "zurl":
1157 case "zonvoy":
1158 default:
1159 return
1160 }
1161 db := opendatabase()
1162 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1163 userinfo.UserID, name, wherefore)
1164 if wherefore == "zonker" || wherefore == "zurl" {
1165 bitethethumbs()
1166 }
1167
1168 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1169}
1170
1171func somedays() string {
1172 secs := 432000 + notrand.Int63n(432000)
1173 return fmt.Sprintf("%d", secs)
1174}
1175
1176func avatate(w http.ResponseWriter, r *http.Request) {
1177 n := r.FormValue("a")
1178 a := avatar(n)
1179 w.Header().Set("Cache-Control", "max-age="+somedays())
1180 w.Write(a)
1181}
1182
1183func servecss(w http.ResponseWriter, r *http.Request) {
1184 w.Header().Set("Cache-Control", "max-age=7776000")
1185 http.ServeFile(w, r, "views"+r.URL.Path)
1186}
1187func servehtml(w http.ResponseWriter, r *http.Request) {
1188 templinfo := getInfo(r)
1189 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1190 if err != nil {
1191 log.Print(err)
1192 }
1193}
1194func serveemu(w http.ResponseWriter, r *http.Request) {
1195 xid := mux.Vars(r)["xid"]
1196 w.Header().Set("Cache-Control", "max-age="+somedays())
1197 http.ServeFile(w, r, "emus/"+xid)
1198}
1199
1200func servefile(w http.ResponseWriter, r *http.Request) {
1201 xid := mux.Vars(r)["xid"]
1202 row := stmtFileData.QueryRow(xid)
1203 var media string
1204 var data []byte
1205 err := row.Scan(&media, &data)
1206 if err != nil {
1207 log.Printf("error loading file: %s", err)
1208 http.NotFound(w, r)
1209 return
1210 }
1211 w.Header().Set("Content-Type", media)
1212 w.Header().Set("X-Content-Type-Options", "nosniff")
1213 w.Header().Set("Cache-Control", "max-age="+somedays())
1214 w.Write(data)
1215}
1216
1217func serve() {
1218 db := opendatabase()
1219 login.Init(db)
1220
1221 listener, err := openListener()
1222 if err != nil {
1223 log.Fatal(err)
1224 }
1225 go redeliverator()
1226
1227 debug := false
1228 getconfig("debug", &debug)
1229 readviews = templates.Load(debug,
1230 "views/honkpage.html",
1231 "views/honkers.html",
1232 "views/zonkers.html",
1233 "views/combos.html",
1234 "views/honkform.html",
1235 "views/honk.html",
1236 "views/login.html",
1237 "views/header.html",
1238 )
1239 if !debug {
1240 s := "views/style.css"
1241 savedstyleparams[s] = getstyleparam(s)
1242 s = "views/local.css"
1243 savedstyleparams[s] = getstyleparam(s)
1244 }
1245
1246 bitethethumbs()
1247
1248 mux := mux.NewRouter()
1249 mux.Use(login.Checker)
1250
1251 posters := mux.Methods("POST").Subrouter()
1252 getters := mux.Methods("GET").Subrouter()
1253
1254 getters.HandleFunc("/", homepage)
1255 getters.HandleFunc("/rss", showrss)
1256 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1257 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1258 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1259 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1260 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1261 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1262 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1263 getters.HandleFunc("/a", avatate)
1264 getters.HandleFunc("/t", showconvoy)
1265 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1266 getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1267 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1268
1269 getters.HandleFunc("/style.css", servecss)
1270 getters.HandleFunc("/local.css", servecss)
1271 getters.HandleFunc("/login", servehtml)
1272 posters.HandleFunc("/dologin", login.LoginFunc)
1273 getters.HandleFunc("/logout", login.LogoutFunc)
1274
1275 loggedin := mux.NewRoute().Subrouter()
1276 loggedin.Use(login.Required)
1277 loggedin.HandleFunc("/atme", homepage)
1278 loggedin.HandleFunc("/killzone", killzone)
1279 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1280 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1281 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1282 loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1283 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1284 loggedin.HandleFunc("/honkers", showhonkers)
1285 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1286 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1287 loggedin.HandleFunc("/c", showcombos)
1288 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1289
1290 err = http.Serve(listener, mux)
1291 if err != nil {
1292 log.Fatal(err)
1293 }
1294}
1295
1296var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1297var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1298var stmtHonksForUser, stmtHonksForMe, stmtSaveDub *sql.Stmt
1299var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1300var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1301var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1302var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtSaveZonker *sql.Stmt
1303var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1304
1305func preparetodie(db *sql.DB, s string) *sql.Stmt {
1306 stmt, err := db.Prepare(s)
1307 if err != nil {
1308 log.Fatalf("error %s: %s", err, s)
1309 }
1310 return stmt
1311}
1312
1313func prepareStatements(db *sql.DB) {
1314 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")
1315 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1316 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1317 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1318 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1319 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1320
1321 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 "
1322 limit := " order by honkid desc limit 250"
1323 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1324 stmtOneXonk = preparetodie(db, selecthonks+"where xid = ?")
1325 stmtPublicHonks = preparetodie(db, selecthonks+"where honker = '' and dt > ?"+limit)
1326 stmtUserHonks = preparetodie(db, selecthonks+"where honker = '' and username = ? and dt > ?"+limit)
1327 stmtHonksForUser = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ?"+butnotthose+limit)
1328 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1329 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1330 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1331 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or honker = '') and convoy = ?"+limit)
1332
1333 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1334 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1335 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1336 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1337 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1338 stmtFindFile = preparetodie(db, "select fileid from files where url = ?")
1339 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1340 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1341 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1342 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1343 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1344 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1345 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1346 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl')")
1347 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1348 stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1349 stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1350}
1351
1352func ElaborateUnitTests() {
1353}
1354
1355func finishusersetup() error {
1356 db := opendatabase()
1357 k, err := rsa.GenerateKey(rand.Reader, 2048)
1358 if err != nil {
1359 return err
1360 }
1361 pubkey, err := zem(&k.PublicKey)
1362 if err != nil {
1363 return err
1364 }
1365 seckey, err := zem(k)
1366 if err != nil {
1367 return err
1368 }
1369 _, err = db.Exec("update users set displayname = username, about = ?, pubkey = ?, seckey = ? where userid = 1", "what about me?", pubkey, seckey)
1370 if err != nil {
1371 return err
1372 }
1373 return nil
1374}
1375
1376func main() {
1377 cmd := "run"
1378 if len(os.Args) > 1 {
1379 cmd = os.Args[1]
1380 }
1381 switch cmd {
1382 case "init":
1383 initdb()
1384 case "upgrade":
1385 upgradedb()
1386 }
1387 db := opendatabase()
1388 dbversion := 0
1389 getconfig("dbversion", &dbversion)
1390 if dbversion != myVersion {
1391 log.Fatal("incorrect database version. run upgrade.")
1392 }
1393 getconfig("servername", &serverName)
1394 prepareStatements(db)
1395 switch cmd {
1396 case "ping":
1397 if len(os.Args) < 4 {
1398 fmt.Printf("usage: honk ping from to\n")
1399 return
1400 }
1401 name := os.Args[2]
1402 targ := os.Args[3]
1403 user, err := butwhatabout(name)
1404 if err != nil {
1405 log.Printf("unknown user")
1406 return
1407 }
1408 ping(user, targ)
1409 case "peep":
1410 peeppeep()
1411 case "run":
1412 serve()
1413 case "test":
1414 ElaborateUnitTests()
1415 default:
1416 log.Fatal("unknown command")
1417 }
1418}