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