all repos — honk @ ca80bc9b156c05c19933488c8f17e1e07822e314

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, 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.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, 1)
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(username string) {
259	user, err := butwhatabout(username)
260	if err != nil {
261		elog.Fatal(err)
262	}
263	defer func() {
264		os.Exit(1)
265	}()
266	c := make(chan os.Signal, 1)
267	signal.Notify(c, os.Interrupt)
268	go func() {
269		<-c
270		C.termecho(1)
271		fmt.Printf("\n")
272		os.Exit(1)
273	}()
274
275	db := opendatabase()
276	login.Init(login.InitArgs{Db: db, Logger: ilog})
277
278	r := bufio.NewReader(os.Stdin)
279
280	pass, err := askpassword(r)
281	if err != nil {
282		elog.Print(err)
283		return
284	}
285	err = login.SetPassword(user.ID, pass)
286	if err != nil {
287		elog.Print(err)
288		return
289	}
290	fmt.Printf("done\n")
291	os.Exit(0)
292}
293
294func askpassword(r *bufio.Reader) (string, error) {
295	C.termecho(0)
296	fmt.Printf("password: ")
297	pass, err := r.ReadString('\n')
298	C.termecho(1)
299	fmt.Printf("\n")
300	if err != nil {
301		return "", err
302	}
303	pass = pass[:len(pass)-1]
304	if len(pass) < 6 {
305		return "", fmt.Errorf("that's way too short")
306	}
307	return pass, nil
308}
309
310func createuser(db *sql.DB, r *bufio.Reader) error {
311	fmt.Printf("username: ")
312	name, err := r.ReadString('\n')
313	if err != nil {
314		return err
315	}
316	name = name[:len(name)-1]
317	if len(name) < 1 {
318		return fmt.Errorf("that's way too short")
319	}
320	if !re_plainname.MatchString(name) {
321		return fmt.Errorf("alphanumeric only please")
322	}
323	if _, err := butwhatabout(name); err == nil {
324		return fmt.Errorf("user already exists")
325	}
326	pass, err := askpassword(r)
327	if err != nil {
328		return err
329	}
330	hash, err := bcrypt.GenerateFromPassword([]byte(pass), 12)
331	if err != nil {
332		return err
333	}
334	k, err := rsa.GenerateKey(rand.Reader, 2048)
335	if err != nil {
336		return err
337	}
338	pubkey, err := httpsig.EncodeKey(&k.PublicKey)
339	if err != nil {
340		return err
341	}
342	seckey, err := httpsig.EncodeKey(k)
343	if err != nil {
344		return err
345	}
346	about := "what about me?"
347	_, err = db.Exec("insert into users (username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?)", name, name, about, hash, pubkey, seckey, "{}")
348	if err != nil {
349		return err
350	}
351	return nil
352}
353
354func createserveruser(db *sql.DB) error {
355	k, err := rsa.GenerateKey(rand.Reader, 2048)
356	if err != nil {
357		return err
358	}
359	pubkey, err := httpsig.EncodeKey(&k.PublicKey)
360	if err != nil {
361		return err
362	}
363	seckey, err := httpsig.EncodeKey(k)
364	if err != nil {
365		return err
366	}
367	name := "server"
368	about := "server"
369	hash := "*"
370	_, err = db.Exec("insert into users (userid, username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?, ?)", serverUID, name, name, about, hash, pubkey, seckey, "")
371	if err != nil {
372		return err
373	}
374	return nil
375}
376
377func opendatabase() *sql.DB {
378	if alreadyopendb != nil {
379		return alreadyopendb
380	}
381	dbname := dataDir + "/honk.db"
382	_, err := os.Stat(dbname)
383	if err != nil {
384		elog.Fatalf("unable to open database: %s", err)
385	}
386	db, err := sql.Open("sqlite3", dbname)
387	if err != nil {
388		elog.Fatalf("unable to open database: %s", err)
389	}
390	stmtConfig, err = db.Prepare("select value from config where key = ?")
391	if err != nil {
392		elog.Fatal(err)
393	}
394	alreadyopendb = db
395	return db
396}
397
398func openblobdb() *sql.DB {
399	blobdbname := dataDir + "/blob.db"
400	_, err := os.Stat(blobdbname)
401	if err != nil {
402		elog.Fatalf("unable to open database: %s", err)
403	}
404	db, err := sql.Open("sqlite3", blobdbname)
405	if err != nil {
406		elog.Fatalf("unable to open database: %s", err)
407	}
408	return db
409}
410
411func getconfig(key string, value interface{}) error {
412	m, ok := value.(*map[string]bool)
413	if ok {
414		rows, err := stmtConfig.Query(key)
415		if err != nil {
416			return err
417		}
418		defer rows.Close()
419		for rows.Next() {
420			var s string
421			err = rows.Scan(&s)
422			if err != nil {
423				return err
424			}
425			(*m)[s] = true
426		}
427		return nil
428	}
429	row := stmtConfig.QueryRow(key)
430	err := row.Scan(value)
431	if err == sql.ErrNoRows {
432		err = nil
433	}
434	return err
435}
436
437func setconfig(key string, val interface{}) error {
438	db := opendatabase()
439	db.Exec("delete from config where key = ?", key)
440	_, err := db.Exec("insert into config (key, value) values (?, ?)", key, val)
441	return err
442}
443
444func openListener() (net.Listener, error) {
445	var listenAddr string
446	err := getconfig("listenaddr", &listenAddr)
447	if err != nil {
448		return nil, err
449	}
450	if listenAddr == "" {
451		return nil, fmt.Errorf("must have listenaddr")
452	}
453	proto := "tcp"
454	if listenAddr[0] == '/' {
455		proto = "unix"
456		err := os.Remove(listenAddr)
457		if err != nil && !os.IsNotExist(err) {
458			elog.Printf("unable to unlink socket: %s", err)
459		}
460	}
461	listener, err := net.Listen(proto, listenAddr)
462	if err != nil {
463		return nil, err
464	}
465	if proto == "unix" {
466		os.Chmod(listenAddr, 0777)
467	}
468	return listener, nil
469}