all repos — honk @ d9043d898ed2654a70442ed584ed76c8a236b5df

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