all repos — honk @ db51b6abc0f394f197be015cfc0e5fc3a31a6342

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