all repos — honk @ aaa5c334a99e501f801ad5f0364678fd81707988

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