all repos — honk @ bda8b07461c62564112152649b4fd6474fcbf516

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