all repos — honk @ 2ea80d2abb9685c3f66de2a56e02ccf2186c8d3e

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