all repos — honk @ 48c56d0c881f3ee0a46fb18598ab135406f6580a

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