all repos — honk @ 1dab6623e5cfb202c33a976d3dd63f6f89f6473a

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