all repos — honk @ v1.3.1

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