all repos — honk @ adc018a21387b12f9b488144825369a1119e3df1

my fork of honk

util.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
 18/*
 19#include <termios.h>
 20
 21void
 22termecho(int on)
 23{
 24	struct termios t;
 25	tcgetattr(1, &t);
 26	if (on)
 27		t.c_lflag |= ECHO;
 28	else
 29		t.c_lflag &= ~ECHO;
 30	tcsetattr(1, TCSADRAIN, &t);
 31}
 32*/
 33import "C"
 34
 35import (
 36	"bufio"
 37	"crypto/rand"
 38	"crypto/rsa"
 39	"crypto/sha512"
 40	"database/sql"
 41	"fmt"
 42	"io/ioutil"
 43	"log"
 44	"net"
 45	"os"
 46	"os/signal"
 47	"strings"
 48
 49	"golang.org/x/crypto/bcrypt"
 50	_ "humungus.tedunangst.com/r/go-sqlite3"
 51	"humungus.tedunangst.com/r/webs/httpsig"
 52	"humungus.tedunangst.com/r/webs/login"
 53)
 54
 55var savedassetparams = make(map[string]string)
 56
 57func getassetparam(file string) string {
 58	if p, ok := savedassetparams[file]; ok {
 59		return p
 60	}
 61	data, err := ioutil.ReadFile(file)
 62	if err != nil {
 63		return ""
 64	}
 65	hasher := sha512.New()
 66	hasher.Write(data)
 67
 68	return fmt.Sprintf("?v=%.8x", hasher.Sum(nil))
 69}
 70
 71var dbtimeformat = "2006-01-02 15:04:05"
 72
 73var alreadyopendb *sql.DB
 74var stmtConfig *sql.Stmt
 75
 76func initdb() {
 77	dbname := dataDir + "/honk.db"
 78	_, err := os.Stat(dbname)
 79	if err == nil {
 80		log.Fatalf("%s already exists", dbname)
 81	}
 82	db, err := sql.Open("sqlite3", dbname)
 83	if err != nil {
 84		log.Fatal(err)
 85	}
 86	alreadyopendb = db
 87	defer func() {
 88		os.Remove(dbname)
 89		os.Exit(1)
 90	}()
 91	c := make(chan os.Signal)
 92	signal.Notify(c, os.Interrupt)
 93	go func() {
 94		<-c
 95		C.termecho(1)
 96		fmt.Printf("\n")
 97		os.Remove(dbname)
 98		os.Exit(1)
 99	}()
100
101	for _, line := range strings.Split(sqlSchema, ";") {
102		_, err = db.Exec(line)
103		if err != nil {
104			log.Print(err)
105			return
106		}
107	}
108	r := bufio.NewReader(os.Stdin)
109
110	err = createuser(db, r)
111	if err != nil {
112		log.Print(err)
113		return
114	}
115	err = createserveruser(db)
116	if err != nil {
117		log.Print(err)
118		return
119	}
120
121	fmt.Printf("listen address: ")
122	addr, err := r.ReadString('\n')
123	if err != nil {
124		log.Print(err)
125		return
126	}
127	addr = addr[:len(addr)-1]
128	if len(addr) < 1 {
129		log.Print("that's way too short")
130		return
131	}
132	setconfig("listenaddr", addr)
133	fmt.Printf("server name: ")
134	addr, err = r.ReadString('\n')
135	if err != nil {
136		log.Print(err)
137		return
138	}
139	addr = addr[:len(addr)-1]
140	if len(addr) < 1 {
141		log.Print("that's way too short")
142		return
143	}
144	setconfig("servername", addr)
145	var randbytes [16]byte
146	rand.Read(randbytes[:])
147	key := fmt.Sprintf("%x", randbytes)
148	setconfig("csrfkey", key)
149	setconfig("dbversion", myVersion)
150
151	setconfig("servermsg", "<h2>Things happen.</h2>")
152	setconfig("aboutmsg", "<h3>What is honk?</h3><p>Honk is amazing!")
153	setconfig("loginmsg", "<h2>login</h2>")
154	setconfig("debug", 0)
155
156	initblobdb()
157
158	prepareStatements(db)
159	db.Close()
160	fmt.Printf("done.\n")
161	os.Exit(0)
162}
163
164func initblobdb() {
165	blobdbname := dataDir + "/blob.db"
166	_, err := os.Stat(blobdbname)
167	if err == nil {
168		log.Fatalf("%s already exists", blobdbname)
169	}
170	blobdb, err := sql.Open("sqlite3", blobdbname)
171	if err != nil {
172		log.Print(err)
173		return
174	}
175	_, err = blobdb.Exec("create table filedata (xid text, media text, content blob)")
176	if err != nil {
177		log.Print(err)
178		return
179	}
180	_, err = blobdb.Exec("create index idx_filexid on filedata(xid)")
181	if err != nil {
182		log.Print(err)
183		return
184	}
185	blobdb.Close()
186}
187
188func adduser() {
189	db := opendatabase()
190	defer func() {
191		os.Exit(1)
192	}()
193	c := make(chan os.Signal)
194	signal.Notify(c, os.Interrupt)
195	go func() {
196		<-c
197		C.termecho(1)
198		fmt.Printf("\n")
199		os.Exit(1)
200	}()
201
202	r := bufio.NewReader(os.Stdin)
203
204	err := createuser(db, r)
205	if err != nil {
206		log.Print(err)
207		return
208	}
209
210	os.Exit(0)
211}
212
213func deluser(username string) {
214	user, _ := butwhatabout(username)
215	if user == nil {
216		log.Printf("no userfound")
217		return
218	}
219	userid := user.ID
220	db := opendatabase()
221
222	where := " where honkid in (select honkid from honks where userid = ?)"
223	doordie(db, "delete from donks"+where, userid)
224	doordie(db, "delete from onts"+where, userid)
225	doordie(db, "delete from honkmeta"+where, userid)
226
227	doordie(db, "delete from honks where userid = ?", userid)
228	doordie(db, "delete from honkers where userid = ?", userid)
229	doordie(db, "delete from zonkers where userid = ?", userid)
230	doordie(db, "delete from doovers where userid = ?", userid)
231	doordie(db, "delete from hfcs where userid = ?", userid)
232	doordie(db, "delete from auth where userid = ?", userid)
233	doordie(db, "delete from users where userid = ?", userid)
234}
235
236func chpass() {
237	if len(os.Args) < 3 {
238		fmt.Printf("need a username\n")
239		os.Exit(1)
240	}
241	user, err := butwhatabout(os.Args[2])
242	if err != nil {
243		log.Fatal(err)
244	}
245	defer func() {
246		os.Exit(1)
247	}()
248	c := make(chan os.Signal)
249	signal.Notify(c, os.Interrupt)
250	go func() {
251		<-c
252		C.termecho(1)
253		fmt.Printf("\n")
254		os.Exit(1)
255	}()
256
257	db := opendatabase()
258	login.Init(db)
259
260	r := bufio.NewReader(os.Stdin)
261
262	pass, err := askpassword(r)
263	if err != nil {
264		log.Print(err)
265		return
266	}
267	err = login.SetPassword(user.ID, pass)
268	if err != nil {
269		log.Print(err)
270		return
271	}
272	fmt.Printf("done\n")
273	os.Exit(0)
274}
275
276func askpassword(r *bufio.Reader) (string, error) {
277	C.termecho(0)
278	fmt.Printf("password: ")
279	pass, err := r.ReadString('\n')
280	C.termecho(1)
281	fmt.Printf("\n")
282	if err != nil {
283		return "", err
284	}
285	pass = pass[:len(pass)-1]
286	if len(pass) < 6 {
287		return "", fmt.Errorf("that's way too short")
288	}
289	return pass, nil
290}
291
292func createuser(db *sql.DB, r *bufio.Reader) error {
293	fmt.Printf("username: ")
294	name, err := r.ReadString('\n')
295	if err != nil {
296		return err
297	}
298	name = name[:len(name)-1]
299	if len(name) < 1 {
300		return fmt.Errorf("that's way too short")
301	}
302	pass, err := askpassword(r)
303	if err != nil {
304		return err
305	}
306	hash, err := bcrypt.GenerateFromPassword([]byte(pass), 12)
307	if err != nil {
308		return err
309	}
310	k, err := rsa.GenerateKey(rand.Reader, 2048)
311	if err != nil {
312		return err
313	}
314	pubkey, err := httpsig.EncodeKey(&k.PublicKey)
315	if err != nil {
316		return err
317	}
318	seckey, err := httpsig.EncodeKey(k)
319	if err != nil {
320		return err
321	}
322	about := "what about me?"
323	_, err = db.Exec("insert into users (username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?)", name, name, about, hash, pubkey, seckey, "{}")
324	if err != nil {
325		return err
326	}
327	return nil
328}
329
330func createserveruser(db *sql.DB) error {
331	k, err := rsa.GenerateKey(rand.Reader, 2048)
332	if err != nil {
333		return err
334	}
335	pubkey, err := httpsig.EncodeKey(&k.PublicKey)
336	if err != nil {
337		return err
338	}
339	seckey, err := httpsig.EncodeKey(k)
340	if err != nil {
341		return err
342	}
343	name := "server"
344	about := "server"
345	hash := "*"
346	_, err = db.Exec("insert into users (userid, username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?, ?)", serverUID, name, name, about, hash, pubkey, seckey, "")
347	if err != nil {
348		return err
349	}
350	return nil
351}
352
353func opendatabase() *sql.DB {
354	if alreadyopendb != nil {
355		return alreadyopendb
356	}
357	dbname := dataDir + "/honk.db"
358	_, err := os.Stat(dbname)
359	if err != nil {
360		log.Fatalf("unable to open database: %s", err)
361	}
362	db, err := sql.Open("sqlite3", dbname)
363	if err != nil {
364		log.Fatalf("unable to open database: %s", err)
365	}
366	stmtConfig, err = db.Prepare("select value from config where key = ?")
367	if err != nil {
368		log.Fatal(err)
369	}
370	alreadyopendb = db
371	return db
372}
373
374func openblobdb() *sql.DB {
375	blobdbname := dataDir + "/blob.db"
376	_, err := os.Stat(blobdbname)
377	if err != nil {
378		log.Fatalf("unable to open database: %s", err)
379	}
380	db, err := sql.Open("sqlite3", blobdbname)
381	if err != nil {
382		log.Fatalf("unable to open database: %s", err)
383	}
384	return db
385}
386
387func getconfig(key string, value interface{}) error {
388	m, ok := value.(*map[string]bool)
389	if ok {
390		rows, err := stmtConfig.Query(key)
391		if err != nil {
392			return err
393		}
394		defer rows.Close()
395		for rows.Next() {
396			var s string
397			err = rows.Scan(&s)
398			if err != nil {
399				return err
400			}
401			(*m)[s] = true
402		}
403		return nil
404	}
405	row := stmtConfig.QueryRow(key)
406	err := row.Scan(value)
407	if err == sql.ErrNoRows {
408		err = nil
409	}
410	return err
411}
412
413func setconfig(key string, val interface{}) error {
414	db := opendatabase()
415	db.Exec("delete from config where key = ?", key)
416	_, err := db.Exec("insert into config (key, value) values (?, ?)", key, val)
417	return err
418}
419
420func openListener() (net.Listener, error) {
421	var listenAddr string
422	err := getconfig("listenaddr", &listenAddr)
423	if err != nil {
424		return nil, err
425	}
426	if listenAddr == "" {
427		return nil, fmt.Errorf("must have listenaddr")
428	}
429	proto := "tcp"
430	if listenAddr[0] == '/' {
431		proto = "unix"
432		err := os.Remove(listenAddr)
433		if err != nil && !os.IsNotExist(err) {
434			log.Printf("unable to unlink socket: %s", err)
435		}
436	}
437	listener, err := net.Listen(proto, listenAddr)
438	if err != nil {
439		return nil, err
440	}
441	if proto == "unix" {
442		os.Chmod(listenAddr, 0777)
443	}
444	return listener, nil
445}