all repos — honk @ 5250aaddd39473a422fa1fa32047003298973b03

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