all repos — honk @ 8bbb398743bfe88b20d6b60ed81dbc1e7566a0f3

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