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)
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 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}