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