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