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