all repos — honk @ dda2fb700429f52bcf8d76badd51c45fd8f02423

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