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
978var handfull = make(map[string]string)
979var handlock sync.Mutex
980
981func gofish(name string) string {
982 if name[0] == '@' {
983 name = name[1:]
984 }
985 m := strings.Split(name, "@")
986 if len(m) != 2 {
987 log.Printf("bad fish name: %s", name)
988 return ""
989 }
990 handlock.Lock()
991 ref, ok := handfull[name]
992 handlock.Unlock()
993 if ok {
994 return ref
995 }
996 j, err := GetJunk(fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
997 handlock.Lock()
998 defer handlock.Unlock()
999 if err != nil {
1000 log.Printf("failed to go fish %s: %s", name, err)
1001 handfull[name] = ""
1002 return ""
1003 }
1004 links, _ := jsongetarray(j, "links")
1005 for _, l := range links {
1006 href, _ := jsongetstring(l, "href")
1007 rel, _ := jsongetstring(l, "rel")
1008 t, _ := jsongetstring(l, "type")
1009 if rel == "self" && friendorfoe(t) {
1010 handfull[name] = href
1011 return href
1012 }
1013 }
1014 handfull[name] = ""
1015 return ""
1016}
1017
1018func savehonker(w http.ResponseWriter, r *http.Request) {
1019 u := login.GetUserInfo(r)
1020 name := r.FormValue("name")
1021 url := r.FormValue("url")
1022 peep := r.FormValue("peep")
1023 combos := r.FormValue("combos")
1024 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1025
1026 if honkerid > 0 {
1027 goodbye := r.FormValue("goodbye")
1028 if goodbye == "goodbye" {
1029 db := opendatabase()
1030 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1031 honkerid, u.UserID)
1032 var xid string
1033 err := row.Scan(&xid)
1034 if err != nil {
1035 log.Printf("can't get honker xid: %s", err)
1036 return
1037 }
1038 log.Printf("unsubscribing from %s", xid)
1039 user, _ := butwhatabout(u.Username)
1040 err = itakeitallback(user, xid)
1041 if err != nil {
1042 log.Printf("can't take it back: %s", err)
1043 } else {
1044 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1045 if err != nil {
1046 log.Printf("error updating honker: %s", err)
1047 return
1048 }
1049 }
1050
1051 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1052 return
1053 }
1054 combos = " " + strings.TrimSpace(combos) + " "
1055 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1056 if err != nil {
1057 log.Printf("update honker err: %s", err)
1058 return
1059 }
1060 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1061 }
1062
1063 flavor := "presub"
1064 if peep == "peep" {
1065 flavor = "peep"
1066 }
1067 if url == "" {
1068 return
1069 }
1070 if url[0] == '@' {
1071 url = gofish(url)
1072 }
1073 if url == "" {
1074 return
1075 }
1076 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1077 if err != nil {
1078 log.Print(err)
1079 return
1080 }
1081 if flavor == "presub" {
1082 user, _ := butwhatabout(u.Username)
1083 go subsub(user, url)
1084 }
1085 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1086}
1087
1088type Zonker struct {
1089 ID int64
1090 Name string
1091 Wherefore string
1092}
1093
1094func killzone(w http.ResponseWriter, r *http.Request) {
1095 db := opendatabase()
1096 userinfo := login.GetUserInfo(r)
1097 rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1098 if err != nil {
1099 log.Printf("err: %s", err)
1100 return
1101 }
1102 var zonkers []Zonker
1103 for rows.Next() {
1104 var z Zonker
1105 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1106 zonkers = append(zonkers, z)
1107 }
1108 templinfo := getInfo(r)
1109 templinfo["Zonkers"] = zonkers
1110 templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1111 err = readviews.Execute(w, "zonkers.html", templinfo)
1112 if err != nil {
1113 log.Print(err)
1114 }
1115}
1116
1117func killitwithfire(w http.ResponseWriter, r *http.Request) {
1118 userinfo := login.GetUserInfo(r)
1119 itsok := r.FormValue("itsok")
1120 if itsok == "iforgiveyou" {
1121 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1122 db := opendatabase()
1123 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1124 userinfo.UserID, zonkerid)
1125 bitethethumbs()
1126 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1127 return
1128 }
1129 wherefore := r.FormValue("wherefore")
1130 name := r.FormValue("name")
1131 if name == "" {
1132 return
1133 }
1134 switch wherefore {
1135 case "zonker":
1136 case "zurl":
1137 case "zonvoy":
1138 default:
1139 return
1140 }
1141 db := opendatabase()
1142 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1143 userinfo.UserID, name, wherefore)
1144 if wherefore == "zonker" || wherefore == "zurl" {
1145 bitethethumbs()
1146 }
1147
1148 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1149}
1150
1151func somedays() string {
1152 secs := 432000 + notrand.Int63n(432000)
1153 return fmt.Sprintf("%d", secs)
1154}
1155
1156func avatate(w http.ResponseWriter, r *http.Request) {
1157 n := r.FormValue("a")
1158 a := avatar(n)
1159 w.Header().Set("Cache-Control", "max-age="+somedays())
1160 w.Write(a)
1161}
1162
1163func servecss(w http.ResponseWriter, r *http.Request) {
1164 w.Header().Set("Cache-Control", "max-age=7776000")
1165 http.ServeFile(w, r, "views"+r.URL.Path)
1166}
1167func servehtml(w http.ResponseWriter, r *http.Request) {
1168 templinfo := getInfo(r)
1169 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1170 if err != nil {
1171 log.Print(err)
1172 }
1173}
1174func serveemu(w http.ResponseWriter, r *http.Request) {
1175 xid := mux.Vars(r)["xid"]
1176 w.Header().Set("Cache-Control", "max-age="+somedays())
1177 http.ServeFile(w, r, "emus/"+xid)
1178}
1179
1180func servefile(w http.ResponseWriter, r *http.Request) {
1181 xid := mux.Vars(r)["xid"]
1182 row := stmtFileData.QueryRow(xid)
1183 var media string
1184 var data []byte
1185 err := row.Scan(&media, &data)
1186 if err != nil {
1187 log.Printf("error loading file: %s", err)
1188 http.NotFound(w, r)
1189 return
1190 }
1191 w.Header().Set("Content-Type", media)
1192 w.Header().Set("X-Content-Type-Options", "nosniff")
1193 w.Header().Set("Cache-Control", "max-age="+somedays())
1194 w.Write(data)
1195}
1196
1197func serve() {
1198 db := opendatabase()
1199 login.Init(db)
1200
1201 listener, err := openListener()
1202 if err != nil {
1203 log.Fatal(err)
1204 }
1205 go redeliverator()
1206
1207 debug := false
1208 getconfig("debug", &debug)
1209 readviews = templates.Load(debug,
1210 "views/honkpage.html",
1211 "views/honkers.html",
1212 "views/zonkers.html",
1213 "views/honkform.html",
1214 "views/honk.html",
1215 "views/login.html",
1216 "views/header.html",
1217 )
1218 if !debug {
1219 s := "views/style.css"
1220 savedstyleparams[s] = getstyleparam(s)
1221 s = "views/local.css"
1222 savedstyleparams[s] = getstyleparam(s)
1223 }
1224
1225 bitethethumbs()
1226
1227 mux := mux.NewRouter()
1228 mux.Use(login.Checker)
1229
1230 posters := mux.Methods("POST").Subrouter()
1231 getters := mux.Methods("GET").Subrouter()
1232
1233 getters.HandleFunc("/", homepage)
1234 getters.HandleFunc("/rss", showrss)
1235 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1236 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1237 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1238 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1239 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1240 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1241 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1242 getters.HandleFunc("/a", avatate)
1243 getters.HandleFunc("/t", showconvoy)
1244 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1245 getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1246 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1247
1248 getters.HandleFunc("/style.css", servecss)
1249 getters.HandleFunc("/local.css", servecss)
1250 getters.HandleFunc("/login", servehtml)
1251 posters.HandleFunc("/dologin", login.LoginFunc)
1252 getters.HandleFunc("/logout", login.LogoutFunc)
1253
1254 loggedin := mux.NewRoute().Subrouter()
1255 loggedin.Use(login.Required)
1256 loggedin.HandleFunc("/atme", homepage)
1257 loggedin.HandleFunc("/killzone", killzone)
1258 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1259 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1260 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1261 loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1262 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1263 loggedin.HandleFunc("/honkers", showhonkers)
1264 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1265 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1266 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1267
1268 err = http.Serve(listener, mux)
1269 if err != nil {
1270 log.Fatal(err)
1271 }
1272}
1273
1274var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1275var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1276var stmtHonksForUser, stmtHonksForMe, stmtDeleteHonk, stmtSaveDub *sql.Stmt
1277var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1278var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1279var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1280var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtSaveZonker *sql.Stmt
1281var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1282
1283func preparetodie(db *sql.DB, s string) *sql.Stmt {
1284 stmt, err := db.Prepare(s)
1285 if err != nil {
1286 log.Fatalf("error %s: %s", err, s)
1287 }
1288 return stmt
1289}
1290
1291func prepareStatements(db *sql.DB) {
1292 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")
1293 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1294 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1295 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1296 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1297 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1298
1299 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 "
1300 limit := " order by honkid desc limit 250"
1301 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1302 stmtOneXonk = preparetodie(db, selecthonks+"where xid = ?")
1303 stmtPublicHonks = preparetodie(db, selecthonks+"where honker = ''"+limit)
1304 stmtUserHonks = preparetodie(db, selecthonks+"where honker = '' and username = ?"+limit)
1305 stmtHonksForUser = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ?"+butnotthose+limit)
1306 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1307 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1308 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1309 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or honker = '') and convoy = ?"+limit)
1310
1311 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1312 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1313 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1314 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1315 stmtDeleteHonk = preparetodie(db, "update honks set what = 'zonk' where xid = ? and honker = ? and userid = ?")
1316 stmtFindFile = preparetodie(db, "select fileid from files where url = ?")
1317 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1318 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1319 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1320 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1321 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1322 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1323 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1324 stmtZonkIt = preparetodie(db, "update honks set what = 'zonk' where userid = ? and xid = ?")
1325 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl')")
1326 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1327 stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1328 stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1329}
1330
1331func ElaborateUnitTests() {
1332}
1333
1334func finishusersetup() error {
1335 db := opendatabase()
1336 k, err := rsa.GenerateKey(rand.Reader, 2048)
1337 if err != nil {
1338 return err
1339 }
1340 pubkey, err := zem(&k.PublicKey)
1341 if err != nil {
1342 return err
1343 }
1344 seckey, err := zem(k)
1345 if err != nil {
1346 return err
1347 }
1348 _, err = db.Exec("update users set displayname = username, about = ?, pubkey = ?, seckey = ? where userid = 1", "what about me?", pubkey, seckey)
1349 if err != nil {
1350 return err
1351 }
1352 return nil
1353}
1354
1355func main() {
1356 cmd := "run"
1357 if len(os.Args) > 1 {
1358 cmd = os.Args[1]
1359 }
1360 switch cmd {
1361 case "init":
1362 initdb()
1363 case "upgrade":
1364 upgradedb()
1365 }
1366 db := opendatabase()
1367 dbversion := 0
1368 getconfig("dbversion", &dbversion)
1369 if dbversion != myVersion {
1370 log.Fatal("incorrect database version. run upgrade.")
1371 }
1372 getconfig("servername", &serverName)
1373 prepareStatements(db)
1374 switch cmd {
1375 case "ping":
1376 if len(os.Args) < 4 {
1377 fmt.Printf("usage: honk ping from to\n")
1378 return
1379 }
1380 name := os.Args[2]
1381 targ := os.Args[3]
1382 user, err := butwhatabout(name)
1383 if err != nil {
1384 log.Printf("unknown user")
1385 return
1386 }
1387 ping(user, targ)
1388 case "peep":
1389 peeppeep()
1390 case "run":
1391 serve()
1392 case "test":
1393 ElaborateUnitTests()
1394 default:
1395 log.Fatal("unknown command")
1396 }
1397}