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.Oonker = xonk.Honker
834 honk.RID = rid
835 }
836 if convoy == "" {
837 convoy = "data:,electrichonkytonk-" + xfiltrate()
838 }
839 butnottooloud(honk.Audience)
840 honk.Audience = oneofakind(honk.Audience)
841 noise = obfusbreak(noise)
842 honk.Noise = noise
843 honk.Convoy = convoy
844
845 file, filehdr, err := r.FormFile("donk")
846 if err == nil {
847 var buf bytes.Buffer
848 io.Copy(&buf, file)
849 file.Close()
850 data := buf.Bytes()
851 xid := xfiltrate()
852 var media, name string
853 img, err := image.Vacuum(&buf)
854 if err == nil {
855 data = img.Data
856 format := img.Format
857 media = "image/" + format
858 if format == "jpeg" {
859 format = "jpg"
860 }
861 name = xid + "." + format
862 xid = name
863 } else {
864 maxsize := 100000
865 if len(data) > maxsize {
866 log.Printf("bad image: %s too much text: %d", err, len(data))
867 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
868 return
869 }
870 for i := 0; i < len(data); i++ {
871 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
872 log.Printf("bad image: %s not text: %d", err, data[i])
873 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
874 return
875 }
876 }
877 media = "text/plain"
878 name = filehdr.Filename
879 if name == "" {
880 name = xid + ".txt"
881 }
882 xid += ".txt"
883 }
884 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
885 res, err := stmtSaveFile.Exec(xid, name, url, media, data)
886 if err != nil {
887 log.Printf("unable to save image: %s", err)
888 return
889 }
890 var d Donk
891 d.FileID, _ = res.LastInsertId()
892 d.XID = name
893 d.Name = name
894 d.Media = media
895 d.URL = url
896 honk.Donks = append(honk.Donks, &d)
897 }
898 herd := herdofemus(honk.Noise)
899 for _, e := range herd {
900 donk := savedonk(e.ID, e.Name, "image/png")
901 if donk != nil {
902 donk.Name = e.Name
903 honk.Donks = append(honk.Donks, donk)
904 }
905 }
906
907 user, _ := butwhatabout(userinfo.Username)
908
909 aud := strings.Join(honk.Audience, " ")
910 whofore := 0
911 if strings.Contains(aud, user.URL) {
912 whofore = 1
913 }
914 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, "", xid, rid,
915 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore, "html", honk.Precis, honk.Oonker)
916 if err != nil {
917 log.Printf("error saving honk: %s", err)
918 return
919 }
920 honk.ID, _ = res.LastInsertId()
921 for _, d := range honk.Donks {
922 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
923 if err != nil {
924 log.Printf("err saving donk: %s", err)
925 return
926 }
927 }
928
929 go honkworldwide(user, &honk)
930
931 http.Redirect(w, r, "/", http.StatusSeeOther)
932}
933
934func showhonkers(w http.ResponseWriter, r *http.Request) {
935 userinfo := login.GetUserInfo(r)
936 templinfo := getInfo(r)
937 templinfo["Honkers"] = gethonkers(userinfo.UserID)
938 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
939 err := readviews.Execute(w, "honkers.html", templinfo)
940 if err != nil {
941 log.Print(err)
942 }
943}
944
945func showcombos(w http.ResponseWriter, r *http.Request) {
946 userinfo := login.GetUserInfo(r)
947 templinfo := getInfo(r)
948 honkers := gethonkers(userinfo.UserID)
949 var combos []string
950 for _, h := range honkers {
951 combos = append(combos, h.Combos...)
952 }
953 combos = oneofakind(combos)
954 sort.Strings(combos)
955 templinfo["Combos"] = combos
956 err := readviews.Execute(w, "combos.html", templinfo)
957 if err != nil {
958 log.Print(err)
959 }
960}
961
962func savehonker(w http.ResponseWriter, r *http.Request) {
963 u := login.GetUserInfo(r)
964 name := r.FormValue("name")
965 url := r.FormValue("url")
966 peep := r.FormValue("peep")
967 combos := r.FormValue("combos")
968 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
969
970 if honkerid > 0 {
971 goodbye := r.FormValue("goodbye")
972 if goodbye == "goodbye" {
973 db := opendatabase()
974 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
975 honkerid, u.UserID)
976 var xid string
977 err := row.Scan(&xid)
978 if err != nil {
979 log.Printf("can't get honker xid: %s", err)
980 return
981 }
982 log.Printf("unsubscribing from %s", xid)
983 user, _ := butwhatabout(u.Username)
984 err = itakeitallback(user, xid)
985 if err != nil {
986 log.Printf("can't take it back: %s", err)
987 } else {
988 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
989 if err != nil {
990 log.Printf("error updating honker: %s", err)
991 return
992 }
993 }
994
995 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
996 return
997 }
998 combos = " " + strings.TrimSpace(combos) + " "
999 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1000 if err != nil {
1001 log.Printf("update honker err: %s", err)
1002 return
1003 }
1004 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1005 }
1006
1007 flavor := "presub"
1008 if peep == "peep" {
1009 flavor = "peep"
1010 }
1011 if url == "" {
1012 return
1013 }
1014 if url[0] == '@' {
1015 url = gofish(url)
1016 }
1017 if url == "" {
1018 return
1019 }
1020 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1021 if err != nil {
1022 log.Print(err)
1023 return
1024 }
1025 if flavor == "presub" {
1026 user, _ := butwhatabout(u.Username)
1027 go subsub(user, url)
1028 }
1029 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1030}
1031
1032type Zonker struct {
1033 ID int64
1034 Name string
1035 Wherefore string
1036}
1037
1038func killzone(w http.ResponseWriter, r *http.Request) {
1039 db := opendatabase()
1040 userinfo := login.GetUserInfo(r)
1041 rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1042 if err != nil {
1043 log.Printf("err: %s", err)
1044 return
1045 }
1046 var zonkers []Zonker
1047 for rows.Next() {
1048 var z Zonker
1049 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1050 zonkers = append(zonkers, z)
1051 }
1052 templinfo := getInfo(r)
1053 templinfo["Zonkers"] = zonkers
1054 templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1055 err = readviews.Execute(w, "zonkers.html", templinfo)
1056 if err != nil {
1057 log.Print(err)
1058 }
1059}
1060
1061func killitwithfire(w http.ResponseWriter, r *http.Request) {
1062 userinfo := login.GetUserInfo(r)
1063 itsok := r.FormValue("itsok")
1064 if itsok == "iforgiveyou" {
1065 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1066 db := opendatabase()
1067 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1068 userinfo.UserID, zonkerid)
1069 bitethethumbs()
1070 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1071 return
1072 }
1073 wherefore := r.FormValue("wherefore")
1074 name := r.FormValue("name")
1075 if name == "" {
1076 return
1077 }
1078 switch wherefore {
1079 case "zonker":
1080 case "zurl":
1081 case "zonvoy":
1082 default:
1083 return
1084 }
1085 db := opendatabase()
1086 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1087 userinfo.UserID, name, wherefore)
1088 if wherefore == "zonker" || wherefore == "zurl" {
1089 bitethethumbs()
1090 }
1091
1092 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1093}
1094
1095func accountpage(w http.ResponseWriter, r *http.Request) {
1096 u := login.GetUserInfo(r)
1097 user, _ := butwhatabout(u.Username)
1098 templinfo := getInfo(r)
1099 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1100 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1101 templinfo["WhatAbout"] = user.About
1102 err := readviews.Execute(w, "account.html", templinfo)
1103 if err != nil {
1104 log.Print(err)
1105 }
1106}
1107
1108func dochpass(w http.ResponseWriter, r *http.Request) {
1109 err := login.ChangePassword(w, r)
1110 if err != nil {
1111 log.Printf("error changing password: %s", err)
1112 }
1113 http.Redirect(w, r, "/account", http.StatusSeeOther)
1114}
1115
1116func fingerlicker(w http.ResponseWriter, r *http.Request) {
1117 orig := r.FormValue("resource")
1118
1119 log.Printf("finger lick: %s", orig)
1120
1121 if strings.HasPrefix(orig, "acct:") {
1122 orig = orig[5:]
1123 }
1124
1125 name := orig
1126 idx := strings.LastIndexByte(name, '/')
1127 if idx != -1 {
1128 name = name[idx+1:]
1129 if "https://"+serverName+"/u/"+name != orig {
1130 log.Printf("foreign request rejected")
1131 name = ""
1132 }
1133 } else {
1134 idx = strings.IndexByte(name, '@')
1135 if idx != -1 {
1136 name = name[:idx]
1137 if name+"@"+serverName != orig {
1138 log.Printf("foreign request rejected")
1139 name = ""
1140 }
1141 }
1142 }
1143 user, err := butwhatabout(name)
1144 if err != nil {
1145 http.NotFound(w, r)
1146 return
1147 }
1148
1149 j := NewJunk()
1150 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1151 j["aliases"] = []string{user.URL}
1152 var links []map[string]interface{}
1153 l := NewJunk()
1154 l["rel"] = "self"
1155 l["type"] = `application/activity+json`
1156 l["href"] = user.URL
1157 links = append(links, l)
1158 j["links"] = links
1159
1160 w.Header().Set("Cache-Control", "max-age=3600")
1161 w.Header().Set("Content-Type", "application/jrd+json")
1162 WriteJunk(w, j)
1163}
1164
1165func somedays() string {
1166 secs := 432000 + notrand.Int63n(432000)
1167 return fmt.Sprintf("%d", secs)
1168}
1169
1170func avatate(w http.ResponseWriter, r *http.Request) {
1171 n := r.FormValue("a")
1172 a := avatar(n)
1173 w.Header().Set("Cache-Control", "max-age="+somedays())
1174 w.Write(a)
1175}
1176
1177func servecss(w http.ResponseWriter, r *http.Request) {
1178 w.Header().Set("Cache-Control", "max-age=7776000")
1179 http.ServeFile(w, r, "views"+r.URL.Path)
1180}
1181func servehtml(w http.ResponseWriter, r *http.Request) {
1182 templinfo := getInfo(r)
1183 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1184 if err != nil {
1185 log.Print(err)
1186 }
1187}
1188func serveemu(w http.ResponseWriter, r *http.Request) {
1189 xid := mux.Vars(r)["xid"]
1190 w.Header().Set("Cache-Control", "max-age="+somedays())
1191 http.ServeFile(w, r, "emus/"+xid)
1192}
1193
1194func servefile(w http.ResponseWriter, r *http.Request) {
1195 xid := mux.Vars(r)["xid"]
1196 row := stmtFileData.QueryRow(xid)
1197 var media string
1198 var data []byte
1199 err := row.Scan(&media, &data)
1200 if err != nil {
1201 log.Printf("error loading file: %s", err)
1202 http.NotFound(w, r)
1203 return
1204 }
1205 w.Header().Set("Content-Type", media)
1206 w.Header().Set("X-Content-Type-Options", "nosniff")
1207 w.Header().Set("Cache-Control", "max-age="+somedays())
1208 w.Write(data)
1209}
1210
1211func serve() {
1212 db := opendatabase()
1213 login.Init(db)
1214
1215 listener, err := openListener()
1216 if err != nil {
1217 log.Fatal(err)
1218 }
1219 go redeliverator()
1220
1221 debug := false
1222 getconfig("debug", &debug)
1223 readviews = templates.Load(debug,
1224 "views/honkpage.html",
1225 "views/honkers.html",
1226 "views/zonkers.html",
1227 "views/combos.html",
1228 "views/honkform.html",
1229 "views/honk.html",
1230 "views/account.html",
1231 "views/login.html",
1232 "views/header.html",
1233 )
1234 if !debug {
1235 s := "views/style.css"
1236 savedstyleparams[s] = getstyleparam(s)
1237 s = "views/local.css"
1238 savedstyleparams[s] = getstyleparam(s)
1239 }
1240
1241 bitethethumbs()
1242
1243 mux := mux.NewRouter()
1244 mux.Use(login.Checker)
1245
1246 posters := mux.Methods("POST").Subrouter()
1247 getters := mux.Methods("GET").Subrouter()
1248
1249 getters.HandleFunc("/", homepage)
1250 getters.HandleFunc("/rss", showrss)
1251 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1252 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1253 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1254 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1255 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1256 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1257 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1258 getters.HandleFunc("/a", avatate)
1259 getters.HandleFunc("/t", showconvoy)
1260 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1261 getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1262 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1263
1264 getters.HandleFunc("/style.css", servecss)
1265 getters.HandleFunc("/local.css", servecss)
1266 getters.HandleFunc("/login", servehtml)
1267 posters.HandleFunc("/dologin", login.LoginFunc)
1268 getters.HandleFunc("/logout", login.LogoutFunc)
1269
1270 loggedin := mux.NewRoute().Subrouter()
1271 loggedin.Use(login.Required)
1272 loggedin.HandleFunc("/account", accountpage)
1273 loggedin.HandleFunc("/chpass", dochpass)
1274 loggedin.HandleFunc("/atme", homepage)
1275 loggedin.HandleFunc("/killzone", killzone)
1276 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1277 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1278 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1279 loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1280 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1281 loggedin.HandleFunc("/honkers", showhonkers)
1282 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1283 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1284 loggedin.HandleFunc("/c", showcombos)
1285 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1286
1287 err = http.Serve(listener, mux)
1288 if err != nil {
1289 log.Fatal(err)
1290 }
1291}
1292
1293var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1294var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1295var stmtHonksForUser, stmtHonksForMe, stmtSaveDub *sql.Stmt
1296var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1297var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1298var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1299var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1300var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1301
1302func preparetodie(db *sql.DB, s string) *sql.Stmt {
1303 stmt, err := db.Prepare(s)
1304 if err != nil {
1305 log.Fatalf("error %s: %s", err, s)
1306 }
1307 return stmt
1308}
1309
1310func prepareStatements(db *sql.DB) {
1311 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")
1312 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1313 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1314 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1315 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1316 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1317
1318 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 "
1319 limit := " order by honkid desc limit 250"
1320 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1321 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1322 stmtPublicHonks = preparetodie(db, selecthonks+"where honker = '' and dt > ?"+limit)
1323 stmtUserHonks = preparetodie(db, selecthonks+"where honker = '' and username = ? and dt > ?"+limit)
1324 stmtHonksForUser = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ?"+butnotthose+limit)
1325 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1326 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1327 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1328 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or honker = '') and convoy = ?"+limit)
1329
1330 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1331 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1332 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1333 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1334 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1335 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1336 stmtFindFile = preparetodie(db, "select fileid from files where url = ?")
1337 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1338 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1339 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1340 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1341 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1342 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1343 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1344 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl')")
1345 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1346 stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1347 stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1348}
1349
1350func ElaborateUnitTests() {
1351}
1352
1353func finishusersetup() error {
1354 db := opendatabase()
1355 k, err := rsa.GenerateKey(rand.Reader, 2048)
1356 if err != nil {
1357 return err
1358 }
1359 pubkey, err := zem(&k.PublicKey)
1360 if err != nil {
1361 return err
1362 }
1363 seckey, err := zem(k)
1364 if err != nil {
1365 return err
1366 }
1367 _, err = db.Exec("update users set displayname = username, about = ?, pubkey = ?, seckey = ? where userid = 1", "what about me?", pubkey, seckey)
1368 if err != nil {
1369 return err
1370 }
1371 return nil
1372}
1373
1374func main() {
1375 cmd := "run"
1376 if len(os.Args) > 1 {
1377 cmd = os.Args[1]
1378 }
1379 switch cmd {
1380 case "init":
1381 initdb()
1382 case "upgrade":
1383 upgradedb()
1384 }
1385 db := opendatabase()
1386 dbversion := 0
1387 getconfig("dbversion", &dbversion)
1388 if dbversion != myVersion {
1389 log.Fatal("incorrect database version. run upgrade.")
1390 }
1391 getconfig("servername", &serverName)
1392 prepareStatements(db)
1393 switch cmd {
1394 case "ping":
1395 if len(os.Args) < 4 {
1396 fmt.Printf("usage: honk ping from to\n")
1397 return
1398 }
1399 name := os.Args[2]
1400 targ := os.Args[3]
1401 user, err := butwhatabout(name)
1402 if err != nil {
1403 log.Printf("unknown user")
1404 return
1405 }
1406 ping(user, targ)
1407 case "peep":
1408 peeppeep()
1409 case "run":
1410 serve()
1411 case "test":
1412 ElaborateUnitTests()
1413 default:
1414 log.Fatal("unknown command")
1415 }
1416}