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