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 rows, err := stmtPublicHonks.Query()
654 return getsomehonks(rows, err)
655}
656func gethonksbyuser(name string) []*Honk {
657 rows, err := stmtUserHonks.Query(name)
658 return getsomehonks(rows, err)
659}
660func gethonksforuser(userid int64) []*Honk {
661 dt := time.Now().UTC().Add(-2 * 24 * time.Hour)
662 rows, err := stmtHonksForUser.Query(userid, dt.Format(dbtimeformat), userid)
663 return getsomehonks(rows, err)
664}
665func gethonksforme(userid int64) []*Honk {
666 dt := time.Now().UTC().Add(-4 * 24 * time.Hour)
667 rows, err := stmtHonksForMe.Query(userid, dt.Format(dbtimeformat), userid)
668 return getsomehonks(rows, err)
669}
670func gethonksbyhonker(userid int64, honker string) []*Honk {
671 rows, err := stmtHonksByHonker.Query(userid, honker, userid)
672 return getsomehonks(rows, err)
673}
674func gethonksbycombo(userid int64, combo string) []*Honk {
675 combo = "% " + combo + " %"
676 rows, err := stmtHonksByCombo.Query(userid, combo, userid)
677 return getsomehonks(rows, err)
678}
679func gethonksbyconvoy(userid int64, convoy string) []*Honk {
680 rows, err := stmtHonksByConvoy.Query(userid, convoy)
681 return getsomehonks(rows, err)
682}
683
684func getsomehonks(rows *sql.Rows, err error) []*Honk {
685 if err != nil {
686 log.Printf("error querying honks: %s", err)
687 return nil
688 }
689 defer rows.Close()
690 var honks []*Honk
691 for rows.Next() {
692 var h Honk
693 var dt, aud string
694 err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.XID, &h.RID,
695 &dt, &h.URL, &aud, &h.Noise, &h.Convoy)
696 if err != nil {
697 log.Printf("error scanning honks: %s", err)
698 return nil
699 }
700 h.Date, _ = time.Parse(dbtimeformat, dt)
701 h.Audience = strings.Split(aud, " ")
702 honks = append(honks, &h)
703 }
704 rows.Close()
705 donksforhonks(honks)
706 return honks
707}
708
709func donksforhonks(honks []*Honk) {
710 db := opendatabase()
711 var ids []string
712 hmap := make(map[int64]*Honk)
713 for _, h := range honks {
714 if h.What == "zonk" {
715 continue
716 }
717 ids = append(ids, fmt.Sprintf("%d", h.ID))
718 hmap[h.ID] = h
719 }
720 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, ","))
721 rows, err := db.Query(q)
722 if err != nil {
723 log.Printf("error querying donks: %s", err)
724 return
725 }
726 defer rows.Close()
727 for rows.Next() {
728 var hid int64
729 var d Donk
730 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media)
731 if err != nil {
732 log.Printf("error scanning donk: %s", err)
733 continue
734 }
735 h := hmap[hid]
736 h.Donks = append(h.Donks, &d)
737 }
738}
739
740func savebonk(w http.ResponseWriter, r *http.Request) {
741 xid := r.FormValue("xid")
742
743 log.Printf("bonking %s", xid)
744
745 xonk := getxonk("", xid)
746 if xonk == nil {
747 return
748 }
749 if xonk.Honker == "" {
750 xonk.XID = fmt.Sprintf("https://%s/u/%s/h/%s", serverName, xonk.Username, xonk.XID)
751 }
752 convoy := xonk.Convoy
753
754 userinfo := login.GetUserInfo(r)
755
756 dt := time.Now().UTC()
757 bonk := Honk{
758 UserID: userinfo.UserID,
759 Username: userinfo.Username,
760 Honker: xonk.Honker,
761 What: "bonk",
762 XID: xonk.XID,
763 Date: dt,
764 Noise: xonk.Noise,
765 Convoy: convoy,
766 Donks: xonk.Donks,
767 Audience: oneofakind(prepend(thewholeworld, xonk.Audience)),
768 }
769
770 user, _ := butwhatabout(userinfo.Username)
771
772 aud := strings.Join(bonk.Audience, " ")
773 whofore := 0
774 if strings.Contains(aud, user.URL) {
775 whofore = 1
776 }
777 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", "", xid, "",
778 dt.Format(dbtimeformat), "", aud, bonk.Noise, bonk.Convoy, whofore)
779 if err != nil {
780 log.Printf("error saving bonk: %s", err)
781 return
782 }
783 bonk.ID, _ = res.LastInsertId()
784 for _, d := range bonk.Donks {
785 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
786 if err != nil {
787 log.Printf("err saving donk: %s", err)
788 return
789 }
790 }
791
792 go honkworldwide(user, &bonk)
793
794}
795
796func zonkit(w http.ResponseWriter, r *http.Request) {
797 wherefore := r.FormValue("wherefore")
798 var what string
799 switch wherefore {
800 case "this honk":
801 what = r.FormValue("honk")
802 wherefore = "zonk"
803 case "this honker":
804 what = r.FormValue("honker")
805 wherefore = "zonker"
806 case "this convoy":
807 what = r.FormValue("convoy")
808 wherefore = "zonvoy"
809 }
810 if what == "" {
811 return
812 }
813
814 log.Printf("zonking %s %s", wherefore, what)
815 userinfo := login.GetUserInfo(r)
816 if wherefore == "zonk" {
817 stmtZonkIt.Exec(userinfo.UserID, what)
818 } else {
819 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
820 if err != nil {
821 log.Printf("error saving zonker: %s", err)
822 return
823 }
824 }
825}
826
827func savehonk(w http.ResponseWriter, r *http.Request) {
828 rid := r.FormValue("rid")
829 noise := r.FormValue("noise")
830
831 userinfo := login.GetUserInfo(r)
832
833 dt := time.Now().UTC()
834 xid := xfiltrate()
835 what := "honk"
836 if rid != "" {
837 what = "tonk"
838 }
839 honk := Honk{
840 UserID: userinfo.UserID,
841 Username: userinfo.Username,
842 What: "honk",
843 XID: xid,
844 Date: dt,
845 }
846 if noise[0] == '@' {
847 honk.Audience = append(grapevine(noise), thewholeworld)
848 } else {
849 honk.Audience = prepend(thewholeworld, grapevine(noise))
850 }
851 var convoy string
852 if rid != "" {
853 xonk := getxonk("", rid)
854 if xonk != nil {
855 if xonk.Honker == "" {
856 rid = "https://" + serverName + "/u/" + xonk.Username + "/h/" + rid
857 }
858 honk.Audience = append(honk.Audience, xonk.Audience...)
859 convoy = xonk.Convoy
860 } else {
861 xonkaud, c := whosthere(rid)
862 honk.Audience = append(honk.Audience, xonkaud...)
863 convoy = c
864 }
865 honk.RID = rid
866 }
867 if convoy == "" {
868 convoy = "data:,electrichonkytonk-" + xfiltrate()
869 }
870 honk.Audience = oneofakind(honk.Audience)
871 noise = obfusbreak(noise)
872 honk.Noise = noise
873 honk.Convoy = convoy
874
875 file, filehdr, err := r.FormFile("donk")
876 if err == nil {
877 var buf bytes.Buffer
878 io.Copy(&buf, file)
879 file.Close()
880 data := buf.Bytes()
881 xid := xfiltrate()
882 var media, name string
883 img, format, err := image.Decode(&buf)
884 if err == nil {
885 data, format, err = vacuumwrap(img, format)
886 if err != nil {
887 log.Printf("can't vacuum image: %s", err)
888 return
889 }
890 media = "image/" + format
891 if format == "jpeg" {
892 format = "jpg"
893 }
894 name = xid + "." + format
895 xid = name
896 } else {
897 maxsize := 100000
898 if len(data) > maxsize {
899 log.Printf("bad image: %s too much text: %d", err, len(data))
900 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
901 return
902 }
903 for i := 0; i < len(data); i++ {
904 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
905 log.Printf("bad image: %s not text: %d", err, data[i])
906 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
907 return
908 }
909 }
910 media = "text/plain"
911 name = filehdr.Filename
912 if name == "" {
913 name = xid + ".txt"
914 }
915 xid += ".txt"
916 }
917 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
918 res, err := stmtSaveFile.Exec(xid, name, url, media, data)
919 if err != nil {
920 log.Printf("unable to save image: %s", err)
921 return
922 }
923 var d Donk
924 d.FileID, _ = res.LastInsertId()
925 d.XID = name
926 d.Name = name
927 d.Media = media
928 d.URL = url
929 honk.Donks = append(honk.Donks, &d)
930 }
931 herd := herdofemus(honk.Noise)
932 for _, e := range herd {
933 donk := savedonk(e.ID, e.Name, "image/png")
934 if donk != nil {
935 donk.Name = e.Name
936 honk.Donks = append(honk.Donks, donk)
937 }
938 }
939
940 user, _ := butwhatabout(userinfo.Username)
941
942 aud := strings.Join(honk.Audience, " ")
943 whofore := 0
944 if strings.Contains(aud, user.URL) {
945 whofore = 1
946 }
947 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, "", xid, rid,
948 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore)
949 if err != nil {
950 log.Printf("error saving honk: %s", err)
951 return
952 }
953 honk.ID, _ = res.LastInsertId()
954 for _, d := range honk.Donks {
955 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
956 if err != nil {
957 log.Printf("err saving donk: %s", err)
958 return
959 }
960 }
961
962 go honkworldwide(user, &honk)
963
964 http.Redirect(w, r, "/", http.StatusSeeOther)
965}
966
967func showhonkers(w http.ResponseWriter, r *http.Request) {
968 userinfo := login.GetUserInfo(r)
969 templinfo := getInfo(r)
970 templinfo["Honkers"] = gethonkers(userinfo.UserID)
971 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
972 err := readviews.Execute(w, "honkers.html", templinfo)
973 if err != nil {
974 log.Print(err)
975 }
976}
977
978func showcombos(w http.ResponseWriter, r *http.Request) {
979 userinfo := login.GetUserInfo(r)
980 templinfo := getInfo(r)
981 honkers := gethonkers(userinfo.UserID)
982 var combos []string
983 for _, h := range honkers {
984 combos = append(combos, h.Combos...)
985 }
986 combos = oneofakind(combos)
987 sort.Strings(combos)
988 templinfo["Combos"] = combos
989 err := readviews.Execute(w, "combos.html", templinfo)
990 if err != nil {
991 log.Print(err)
992 }
993}
994
995var handfull = make(map[string]string)
996var handlock sync.Mutex
997
998func gofish(name string) string {
999 if name[0] == '@' {
1000 name = name[1:]
1001 }
1002 m := strings.Split(name, "@")
1003 if len(m) != 2 {
1004 log.Printf("bad fish name: %s", name)
1005 return ""
1006 }
1007 handlock.Lock()
1008 ref, ok := handfull[name]
1009 handlock.Unlock()
1010 if ok {
1011 return ref
1012 }
1013 j, err := GetJunk(fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
1014 handlock.Lock()
1015 defer handlock.Unlock()
1016 if err != nil {
1017 log.Printf("failed to go fish %s: %s", name, err)
1018 handfull[name] = ""
1019 return ""
1020 }
1021 links, _ := jsongetarray(j, "links")
1022 for _, l := range links {
1023 href, _ := jsongetstring(l, "href")
1024 rel, _ := jsongetstring(l, "rel")
1025 t, _ := jsongetstring(l, "type")
1026 if rel == "self" && friendorfoe(t) {
1027 handfull[name] = href
1028 return href
1029 }
1030 }
1031 handfull[name] = ""
1032 return ""
1033}
1034
1035func savehonker(w http.ResponseWriter, r *http.Request) {
1036 u := login.GetUserInfo(r)
1037 name := r.FormValue("name")
1038 url := r.FormValue("url")
1039 peep := r.FormValue("peep")
1040 combos := r.FormValue("combos")
1041 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1042
1043 if honkerid > 0 {
1044 goodbye := r.FormValue("goodbye")
1045 if goodbye == "goodbye" {
1046 db := opendatabase()
1047 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1048 honkerid, u.UserID)
1049 var xid string
1050 err := row.Scan(&xid)
1051 if err != nil {
1052 log.Printf("can't get honker xid: %s", err)
1053 return
1054 }
1055 log.Printf("unsubscribing from %s", xid)
1056 user, _ := butwhatabout(u.Username)
1057 err = itakeitallback(user, xid)
1058 if err != nil {
1059 log.Printf("can't take it back: %s", err)
1060 } else {
1061 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1062 if err != nil {
1063 log.Printf("error updating honker: %s", err)
1064 return
1065 }
1066 }
1067
1068 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1069 return
1070 }
1071 combos = " " + strings.TrimSpace(combos) + " "
1072 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1073 if err != nil {
1074 log.Printf("update honker err: %s", err)
1075 return
1076 }
1077 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1078 }
1079
1080 flavor := "presub"
1081 if peep == "peep" {
1082 flavor = "peep"
1083 }
1084 if url == "" {
1085 return
1086 }
1087 if url[0] == '@' {
1088 url = gofish(url)
1089 }
1090 if url == "" {
1091 return
1092 }
1093 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1094 if err != nil {
1095 log.Print(err)
1096 return
1097 }
1098 if flavor == "presub" {
1099 user, _ := butwhatabout(u.Username)
1100 go subsub(user, url)
1101 }
1102 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1103}
1104
1105type Zonker struct {
1106 ID int64
1107 Name string
1108 Wherefore string
1109}
1110
1111func killzone(w http.ResponseWriter, r *http.Request) {
1112 db := opendatabase()
1113 userinfo := login.GetUserInfo(r)
1114 rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1115 if err != nil {
1116 log.Printf("err: %s", err)
1117 return
1118 }
1119 var zonkers []Zonker
1120 for rows.Next() {
1121 var z Zonker
1122 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1123 zonkers = append(zonkers, z)
1124 }
1125 templinfo := getInfo(r)
1126 templinfo["Zonkers"] = zonkers
1127 templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1128 err = readviews.Execute(w, "zonkers.html", templinfo)
1129 if err != nil {
1130 log.Print(err)
1131 }
1132}
1133
1134func killitwithfire(w http.ResponseWriter, r *http.Request) {
1135 userinfo := login.GetUserInfo(r)
1136 itsok := r.FormValue("itsok")
1137 if itsok == "iforgiveyou" {
1138 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1139 db := opendatabase()
1140 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1141 userinfo.UserID, zonkerid)
1142 bitethethumbs()
1143 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1144 return
1145 }
1146 wherefore := r.FormValue("wherefore")
1147 name := r.FormValue("name")
1148 if name == "" {
1149 return
1150 }
1151 switch wherefore {
1152 case "zonker":
1153 case "zurl":
1154 case "zonvoy":
1155 default:
1156 return
1157 }
1158 db := opendatabase()
1159 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1160 userinfo.UserID, name, wherefore)
1161 if wherefore == "zonker" || wherefore == "zurl" {
1162 bitethethumbs()
1163 }
1164
1165 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1166}
1167
1168func somedays() string {
1169 secs := 432000 + notrand.Int63n(432000)
1170 return fmt.Sprintf("%d", secs)
1171}
1172
1173func avatate(w http.ResponseWriter, r *http.Request) {
1174 n := r.FormValue("a")
1175 a := avatar(n)
1176 w.Header().Set("Cache-Control", "max-age="+somedays())
1177 w.Write(a)
1178}
1179
1180func servecss(w http.ResponseWriter, r *http.Request) {
1181 w.Header().Set("Cache-Control", "max-age=7776000")
1182 http.ServeFile(w, r, "views"+r.URL.Path)
1183}
1184func servehtml(w http.ResponseWriter, r *http.Request) {
1185 templinfo := getInfo(r)
1186 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1187 if err != nil {
1188 log.Print(err)
1189 }
1190}
1191func serveemu(w http.ResponseWriter, r *http.Request) {
1192 xid := mux.Vars(r)["xid"]
1193 w.Header().Set("Cache-Control", "max-age="+somedays())
1194 http.ServeFile(w, r, "emus/"+xid)
1195}
1196
1197func servefile(w http.ResponseWriter, r *http.Request) {
1198 xid := mux.Vars(r)["xid"]
1199 row := stmtFileData.QueryRow(xid)
1200 var media string
1201 var data []byte
1202 err := row.Scan(&media, &data)
1203 if err != nil {
1204 log.Printf("error loading file: %s", err)
1205 http.NotFound(w, r)
1206 return
1207 }
1208 w.Header().Set("Content-Type", media)
1209 w.Header().Set("X-Content-Type-Options", "nosniff")
1210 w.Header().Set("Cache-Control", "max-age="+somedays())
1211 w.Write(data)
1212}
1213
1214func serve() {
1215 db := opendatabase()
1216 login.Init(db)
1217
1218 listener, err := openListener()
1219 if err != nil {
1220 log.Fatal(err)
1221 }
1222 go redeliverator()
1223
1224 debug := false
1225 getconfig("debug", &debug)
1226 readviews = templates.Load(debug,
1227 "views/honkpage.html",
1228 "views/honkers.html",
1229 "views/zonkers.html",
1230 "views/combos.html",
1231 "views/honkform.html",
1232 "views/honk.html",
1233 "views/login.html",
1234 "views/header.html",
1235 )
1236 if !debug {
1237 s := "views/style.css"
1238 savedstyleparams[s] = getstyleparam(s)
1239 s = "views/local.css"
1240 savedstyleparams[s] = getstyleparam(s)
1241 }
1242
1243 bitethethumbs()
1244
1245 mux := mux.NewRouter()
1246 mux.Use(login.Checker)
1247
1248 posters := mux.Methods("POST").Subrouter()
1249 getters := mux.Methods("GET").Subrouter()
1250
1251 getters.HandleFunc("/", homepage)
1252 getters.HandleFunc("/rss", showrss)
1253 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1254 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1255 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1256 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1257 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1258 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1259 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1260 getters.HandleFunc("/a", avatate)
1261 getters.HandleFunc("/t", showconvoy)
1262 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1263 getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1264 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1265
1266 getters.HandleFunc("/style.css", servecss)
1267 getters.HandleFunc("/local.css", servecss)
1268 getters.HandleFunc("/login", servehtml)
1269 posters.HandleFunc("/dologin", login.LoginFunc)
1270 getters.HandleFunc("/logout", login.LogoutFunc)
1271
1272 loggedin := mux.NewRoute().Subrouter()
1273 loggedin.Use(login.Required)
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, stmtDeleteHonk, 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, 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, honks.xid, rid, dt, url, audience, noise, 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 xid = ?")
1322 stmtPublicHonks = preparetodie(db, selecthonks+"where honker = ''"+limit)
1323 stmtUserHonks = preparetodie(db, selecthonks+"where honker = '' and username = ?"+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) 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 stmtDeleteHonk = preparetodie(db, "update honks set what = 'zonk' where xid = ? and honker = ? and userid = ?")
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 stmtZonkIt = preparetodie(db, "update honks set what = 'zonk' where userid = ? and xid = ?")
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}