all repos — honk @ 4c589271d0cc6f2076a3ec0d21dcce75e71a0078

my fork of honk

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