all repos — honk @ 7a3b92b6a139da843f09599735a9b80e4c5d385d

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