all repos — honk @ a615b9b14a5b5ab64cc9b478fda7acb970790974

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(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	about := "what about me?"
331	_, err = db.Exec("insert into users (username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?)", name, name, about, hash, pubkey, seckey, "{}")
332	if err != nil {
333		return err
334	}
335	return nil
336}
337
338func createserveruser(db *sql.DB) error {
339	k, err := rsa.GenerateKey(rand.Reader, 2048)
340	if err != nil {
341		return err
342	}
343	pubkey, err := httpsig.EncodeKey(&k.PublicKey)
344	if err != nil {
345		return err
346	}
347	seckey, err := httpsig.EncodeKey(k)
348	if err != nil {
349		return err
350	}
351	name := "server"
352	about := "server"
353	hash := "*"
354	_, err = db.Exec("insert into users (userid, username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?, ?)", serverUID, name, name, about, hash, pubkey, seckey, "")
355	if err != nil {
356		return err
357	}
358	return nil
359}
360
361func opendatabase() *sql.DB {
362	if alreadyopendb != nil {
363		return alreadyopendb
364	}
365	dbname := dataDir + "/honk.db"
366	_, err := os.Stat(dbname)
367	if err != nil {
368		elog.Fatalf("unable to open database: %s", err)
369	}
370	db, err := sql.Open("sqlite3", dbname)
371	if err != nil {
372		elog.Fatalf("unable to open database: %s", err)
373	}
374	stmtConfig, err = db.Prepare("select value from config where key = ?")
375	if err != nil {
376		elog.Fatal(err)
377	}
378	alreadyopendb = db
379	return db
380}
381
382func openblobdb() *sql.DB {
383	blobdbname := dataDir + "/blob.db"
384	_, err := os.Stat(blobdbname)
385	if err != nil {
386		elog.Fatalf("unable to open database: %s", err)
387	}
388	db, err := sql.Open("sqlite3", blobdbname)
389	if err != nil {
390		elog.Fatalf("unable to open database: %s", err)
391	}
392	return db
393}
394
395func getconfig(key string, value interface{}) error {
396	m, ok := value.(*map[string]bool)
397	if ok {
398		rows, err := stmtConfig.Query(key)
399		if err != nil {
400			return err
401		}
402		defer rows.Close()
403		for rows.Next() {
404			var s string
405			err = rows.Scan(&s)
406			if err != nil {
407				return err
408			}
409			(*m)[s] = true
410		}
411		return nil
412	}
413	row := stmtConfig.QueryRow(key)
414	err := row.Scan(value)
415	if err == sql.ErrNoRows {
416		err = nil
417	}
418	return err
419}
420
421func setconfig(key string, val interface{}) error {
422	db := opendatabase()
423	db.Exec("delete from config where key = ?", key)
424	_, err := db.Exec("insert into config (key, value) values (?, ?)", key, val)
425	return err
426}
427
428func openListener() (net.Listener, error) {
429	var listenAddr string
430	err := getconfig("listenaddr", &listenAddr)
431	if err != nil {
432		return nil, err
433	}
434	if strings.HasPrefix(listenAddr, "fcgi:") {
435		listenAddr = listenAddr[5:]
436		usefcgi = true
437	}
438	if listenAddr == "" {
439		return nil, fmt.Errorf("must have listenaddr")
440	}
441	proto := "tcp"
442	if listenAddr[0] == '/' {
443		proto = "unix"
444		err := os.Remove(listenAddr)
445		if err != nil && !os.IsNotExist(err) {
446			elog.Printf("unable to unlink socket: %s", err)
447		}
448	}
449	listener, err := net.Listen(proto, listenAddr)
450	if err != nil {
451		return nil, err
452	}
453	if proto == "unix" {
454		os.Chmod(listenAddr, 0777)
455	}
456	listenSocket = listener
457	return listener, nil
458}