all repos — honk @ 50eeded945eb5c64cb2dd3255fbca0484ea6d339

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