all repos — honk @ cb63e31a9375376cd52d06639c40ce073fc08a62

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