all repos — honk @ v0.9.3

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