all repos — honk @ 8c9d0a87faca2df7932405aab5bd3a15983f42d4

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 chpass() {
214	if len(os.Args) < 3 {
215		fmt.Printf("need a username\n")
216		os.Exit(1)
217	}
218	user, err := butwhatabout(os.Args[2])
219	if err != nil {
220		log.Fatal(err)
221	}
222	defer func() {
223		os.Exit(1)
224	}()
225	c := make(chan os.Signal)
226	signal.Notify(c, os.Interrupt)
227	go func() {
228		<-c
229		C.termecho(1)
230		fmt.Printf("\n")
231		os.Exit(1)
232	}()
233
234	db := opendatabase()
235	login.Init(db)
236
237	r := bufio.NewReader(os.Stdin)
238
239	pass, err := askpassword(r)
240	if err != nil {
241		log.Print(err)
242		return
243	}
244	err = login.SetPassword(user.ID, pass)
245	if err != nil {
246		log.Print(err)
247		return
248	}
249	fmt.Printf("done\n")
250	os.Exit(0)
251}
252
253func askpassword(r *bufio.Reader) (string, error) {
254	C.termecho(0)
255	fmt.Printf("password: ")
256	pass, err := r.ReadString('\n')
257	C.termecho(1)
258	fmt.Printf("\n")
259	if err != nil {
260		return "", err
261	}
262	pass = pass[:len(pass)-1]
263	if len(pass) < 6 {
264		return "", fmt.Errorf("that's way too short")
265	}
266	return pass, nil
267}
268
269func createuser(db *sql.DB, r *bufio.Reader) error {
270	fmt.Printf("username: ")
271	name, err := r.ReadString('\n')
272	if err != nil {
273		return err
274	}
275	name = name[:len(name)-1]
276	if len(name) < 1 {
277		return fmt.Errorf("that's way too short")
278	}
279	pass, err := askpassword(r)
280	if err != nil {
281		return err
282	}
283	hash, err := bcrypt.GenerateFromPassword([]byte(pass), 12)
284	if err != nil {
285		return err
286	}
287	k, err := rsa.GenerateKey(rand.Reader, 2048)
288	if err != nil {
289		return err
290	}
291	pubkey, err := httpsig.EncodeKey(&k.PublicKey)
292	if err != nil {
293		return err
294	}
295	seckey, err := httpsig.EncodeKey(k)
296	if err != nil {
297		return err
298	}
299	about := "what about me?"
300	_, err = db.Exec("insert into users (username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?)", name, name, about, hash, pubkey, seckey, "{}")
301	if err != nil {
302		return err
303	}
304	return nil
305}
306
307func createserveruser(db *sql.DB) error {
308	k, err := rsa.GenerateKey(rand.Reader, 2048)
309	if err != nil {
310		return err
311	}
312	pubkey, err := httpsig.EncodeKey(&k.PublicKey)
313	if err != nil {
314		return err
315	}
316	seckey, err := httpsig.EncodeKey(k)
317	if err != nil {
318		return err
319	}
320	name := "server"
321	about := "server"
322	hash := "*"
323	_, err = db.Exec("insert into users (userid, username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?, ?)", serverUID, name, name, about, hash, pubkey, seckey, "")
324	if err != nil {
325		return err
326	}
327	return nil
328}
329
330func opendatabase() *sql.DB {
331	if alreadyopendb != nil {
332		return alreadyopendb
333	}
334	dbname := dataDir + "/honk.db"
335	_, err := os.Stat(dbname)
336	if err != nil {
337		log.Fatalf("unable to open database: %s", err)
338	}
339	db, err := sql.Open("sqlite3", dbname)
340	if err != nil {
341		log.Fatalf("unable to open database: %s", err)
342	}
343	stmtConfig, err = db.Prepare("select value from config where key = ?")
344	if err != nil {
345		log.Fatal(err)
346	}
347	alreadyopendb = db
348	return db
349}
350
351func openblobdb() *sql.DB {
352	blobdbname := dataDir + "/blob.db"
353	_, err := os.Stat(blobdbname)
354	if err != nil {
355		log.Fatalf("unable to open database: %s", err)
356	}
357	db, err := sql.Open("sqlite3", blobdbname)
358	if err != nil {
359		log.Fatalf("unable to open database: %s", err)
360	}
361	return db
362}
363
364func getconfig(key string, value interface{}) error {
365	m, ok := value.(*map[string]bool)
366	if ok {
367		rows, err := stmtConfig.Query(key)
368		if err != nil {
369			return err
370		}
371		defer rows.Close()
372		for rows.Next() {
373			var s string
374			err = rows.Scan(&s)
375			if err != nil {
376				return err
377			}
378			(*m)[s] = true
379		}
380		return nil
381	}
382	row := stmtConfig.QueryRow(key)
383	err := row.Scan(value)
384	if err == sql.ErrNoRows {
385		err = nil
386	}
387	return err
388}
389
390func setconfig(key string, val interface{}) error {
391	db := opendatabase()
392	_, err := db.Exec("insert into config (key, value) values (?, ?)", key, val)
393	return err
394}
395
396func updateconfig(key string, val interface{}) error {
397	db := opendatabase()
398	_, err := db.Exec("update config set value = ? where key = ?", val, key)
399	return err
400}
401
402func openListener() (net.Listener, error) {
403	var listenAddr string
404	err := getconfig("listenaddr", &listenAddr)
405	if err != nil {
406		return nil, err
407	}
408	if listenAddr == "" {
409		return nil, fmt.Errorf("must have listenaddr")
410	}
411	proto := "tcp"
412	if listenAddr[0] == '/' {
413		proto = "unix"
414		err := os.Remove(listenAddr)
415		if err != nil && !os.IsNotExist(err) {
416			log.Printf("unable to unlink socket: %s", err)
417		}
418	}
419	listener, err := net.Listen(proto, listenAddr)
420	if err != nil {
421		return nil, err
422	}
423	if proto == "unix" {
424		os.Chmod(listenAddr, 0777)
425	}
426	return listener, nil
427}