all repos — honk @ 3dfb48563b05ab72f0c7ca7ce5ab30d50cfa3f80

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	"regexp"
 48	"strings"
 49
 50	"golang.org/x/crypto/bcrypt"
 51	_ "humungus.tedunangst.com/r/go-sqlite3"
 52	"humungus.tedunangst.com/r/webs/httpsig"
 53	"humungus.tedunangst.com/r/webs/login"
 54)
 55
 56var savedassetparams = make(map[string]string)
 57
 58func getassetparam(file string) string {
 59	if p, ok := savedassetparams[file]; ok {
 60		return p
 61	}
 62	data, err := ioutil.ReadFile(file)
 63	if err != nil {
 64		return ""
 65	}
 66	hasher := sha512.New()
 67	hasher.Write(data)
 68
 69	return fmt.Sprintf("?v=%.8x", hasher.Sum(nil))
 70}
 71
 72var dbtimeformat = "2006-01-02 15:04:05"
 73
 74var alreadyopendb *sql.DB
 75var stmtConfig *sql.Stmt
 76
 77func initdb() {
 78	dbname := dataDir + "/honk.db"
 79	_, err := os.Stat(dbname)
 80	if err == nil {
 81		log.Fatalf("%s already exists", dbname)
 82	}
 83	db, err := sql.Open("sqlite3", dbname)
 84	if err != nil {
 85		log.Fatal(err)
 86	}
 87	alreadyopendb = db
 88	defer func() {
 89		os.Remove(dbname)
 90		os.Exit(1)
 91	}()
 92	c := make(chan os.Signal)
 93	signal.Notify(c, os.Interrupt)
 94	go func() {
 95		<-c
 96		C.termecho(1)
 97		fmt.Printf("\n")
 98		os.Remove(dbname)
 99		os.Exit(1)
100	}()
101
102	for _, line := range strings.Split(sqlSchema, ";") {
103		_, err = db.Exec(line)
104		if err != nil {
105			log.Print(err)
106			return
107		}
108	}
109	r := bufio.NewReader(os.Stdin)
110
111	initblobdb()
112
113	prepareStatements(db)
114
115	err = createserveruser(db)
116	if err != nil {
117		log.Print(err)
118		return
119	}
120	err = createuser(db, r)
121	if err != nil {
122		log.Print(err)
123		return
124	}
125
126	fmt.Printf("listen address: ")
127	addr, err := r.ReadString('\n')
128	if err != nil {
129		log.Print(err)
130		return
131	}
132	addr = addr[:len(addr)-1]
133	if len(addr) < 1 {
134		log.Print("that's way too short")
135		return
136	}
137	setconfig("listenaddr", addr)
138	fmt.Printf("server name: ")
139	addr, err = r.ReadString('\n')
140	if err != nil {
141		log.Print(err)
142		return
143	}
144	addr = addr[:len(addr)-1]
145	if len(addr) < 1 {
146		log.Print("that's way too short")
147		return
148	}
149	setconfig("servername", addr)
150	var randbytes [16]byte
151	rand.Read(randbytes[:])
152	key := fmt.Sprintf("%x", randbytes)
153	setconfig("csrfkey", key)
154	setconfig("dbversion", myVersion)
155
156	setconfig("servermsg", "<h2>Things happen.</h2>")
157	setconfig("aboutmsg", "<h3>What is honk?</h3><p>Honk is amazing!")
158	setconfig("loginmsg", "<h2>login</h2>")
159	setconfig("debug", 0)
160
161	db.Close()
162	fmt.Printf("done.\n")
163	os.Exit(0)
164}
165
166func initblobdb() {
167	blobdbname := dataDir + "/blob.db"
168	_, err := os.Stat(blobdbname)
169	if err == nil {
170		log.Fatalf("%s already exists", blobdbname)
171	}
172	blobdb, err := sql.Open("sqlite3", blobdbname)
173	if err != nil {
174		log.Print(err)
175		return
176	}
177	_, err = blobdb.Exec("create table filedata (xid text, media text, hash text, content blob)")
178	if err != nil {
179		log.Print(err)
180		return
181	}
182	_, err = blobdb.Exec("create index idx_filexid on filedata(xid)")
183	if err != nil {
184		log.Print(err)
185		return
186	}
187	_, err = blobdb.Exec("create index idx_filehash on filedata(hash)")
188	if err != nil {
189		log.Print(err)
190		return
191	}
192	blobdb.Close()
193}
194
195func adduser() {
196	db := opendatabase()
197	defer func() {
198		os.Exit(1)
199	}()
200	c := make(chan os.Signal)
201	signal.Notify(c, os.Interrupt)
202	go func() {
203		<-c
204		C.termecho(1)
205		fmt.Printf("\n")
206		os.Exit(1)
207	}()
208
209	r := bufio.NewReader(os.Stdin)
210
211	err := createuser(db, r)
212	if err != nil {
213		log.Print(err)
214		return
215	}
216
217	os.Exit(0)
218}
219
220func deluser(username string) {
221	user, _ := butwhatabout(username)
222	if user == nil {
223		log.Printf("no userfound")
224		return
225	}
226	userid := user.ID
227	db := opendatabase()
228
229	where := " where honkid in (select honkid from honks where userid = ?)"
230	doordie(db, "delete from donks"+where, userid)
231	doordie(db, "delete from onts"+where, userid)
232	doordie(db, "delete from honkmeta"+where, userid)
233	where = " where chonkid in (select chonkid from chonks where userid = ?)"
234	doordie(db, "delete from donks"+where, userid)
235
236	doordie(db, "delete from honks where userid = ?", userid)
237	doordie(db, "delete from chonks where userid = ?", userid)
238	doordie(db, "delete from honkers where userid = ?", userid)
239	doordie(db, "delete from zonkers where userid = ?", userid)
240	doordie(db, "delete from doovers where userid = ?", userid)
241	doordie(db, "delete from hfcs where userid = ?", userid)
242	doordie(db, "delete from auth where userid = ?", userid)
243	doordie(db, "delete from users where userid = ?", userid)
244}
245
246func chpass() {
247	if len(os.Args) < 3 {
248		fmt.Printf("need a username\n")
249		os.Exit(1)
250	}
251	user, err := butwhatabout(os.Args[2])
252	if err != nil {
253		log.Fatal(err)
254	}
255	defer func() {
256		os.Exit(1)
257	}()
258	c := make(chan os.Signal)
259	signal.Notify(c, os.Interrupt)
260	go func() {
261		<-c
262		C.termecho(1)
263		fmt.Printf("\n")
264		os.Exit(1)
265	}()
266
267	db := opendatabase()
268	login.Init(db)
269
270	r := bufio.NewReader(os.Stdin)
271
272	pass, err := askpassword(r)
273	if err != nil {
274		log.Print(err)
275		return
276	}
277	err = login.SetPassword(user.ID, pass)
278	if err != nil {
279		log.Print(err)
280		return
281	}
282	fmt.Printf("done\n")
283	os.Exit(0)
284}
285
286func askpassword(r *bufio.Reader) (string, error) {
287	C.termecho(0)
288	fmt.Printf("password: ")
289	pass, err := r.ReadString('\n')
290	C.termecho(1)
291	fmt.Printf("\n")
292	if err != nil {
293		return "", err
294	}
295	pass = pass[:len(pass)-1]
296	if len(pass) < 6 {
297		return "", fmt.Errorf("that's way too short")
298	}
299	return pass, nil
300}
301
302func createuser(db *sql.DB, r *bufio.Reader) error {
303	fmt.Printf("username: ")
304	name, err := r.ReadString('\n')
305	if err != nil {
306		return err
307	}
308	name = name[:len(name)-1]
309	if len(name) < 1 {
310		return fmt.Errorf("that's way too short")
311	}
312	re_name := regexp.MustCompile("^[[:alnum:]]+$")
313	if !re_name.MatchString(name) {
314		return fmt.Errorf("alphanumeric only please")
315	}
316	if _, err := butwhatabout(name); err == nil {
317		return fmt.Errorf("user already exists")
318	}
319	pass, err := askpassword(r)
320	if err != nil {
321		return err
322	}
323	hash, err := bcrypt.GenerateFromPassword([]byte(pass), 12)
324	if err != nil {
325		return err
326	}
327	k, err := rsa.GenerateKey(rand.Reader, 2048)
328	if err != nil {
329		return err
330	}
331	pubkey, err := httpsig.EncodeKey(&k.PublicKey)
332	if err != nil {
333		return err
334	}
335	seckey, err := httpsig.EncodeKey(k)
336	if err != nil {
337		return err
338	}
339	about := "what about me?"
340	_, err = db.Exec("insert into users (username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?)", name, name, about, hash, pubkey, seckey, "{}")
341	if err != nil {
342		return err
343	}
344	return nil
345}
346
347func createserveruser(db *sql.DB) error {
348	k, err := rsa.GenerateKey(rand.Reader, 2048)
349	if err != nil {
350		return err
351	}
352	pubkey, err := httpsig.EncodeKey(&k.PublicKey)
353	if err != nil {
354		return err
355	}
356	seckey, err := httpsig.EncodeKey(k)
357	if err != nil {
358		return err
359	}
360	name := "server"
361	about := "server"
362	hash := "*"
363	_, err = db.Exec("insert into users (userid, username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?, ?)", serverUID, name, name, about, hash, pubkey, seckey, "")
364	if err != nil {
365		return err
366	}
367	return nil
368}
369
370func opendatabase() *sql.DB {
371	if alreadyopendb != nil {
372		return alreadyopendb
373	}
374	dbname := dataDir + "/honk.db"
375	_, err := os.Stat(dbname)
376	if err != nil {
377		log.Fatalf("unable to open database: %s", err)
378	}
379	db, err := sql.Open("sqlite3", dbname)
380	if err != nil {
381		log.Fatalf("unable to open database: %s", err)
382	}
383	stmtConfig, err = db.Prepare("select value from config where key = ?")
384	if err != nil {
385		log.Fatal(err)
386	}
387	alreadyopendb = db
388	return db
389}
390
391func openblobdb() *sql.DB {
392	blobdbname := dataDir + "/blob.db"
393	_, err := os.Stat(blobdbname)
394	if err != nil {
395		log.Fatalf("unable to open database: %s", err)
396	}
397	db, err := sql.Open("sqlite3", blobdbname)
398	if err != nil {
399		log.Fatalf("unable to open database: %s", err)
400	}
401	return db
402}
403
404func getconfig(key string, value interface{}) error {
405	m, ok := value.(*map[string]bool)
406	if ok {
407		rows, err := stmtConfig.Query(key)
408		if err != nil {
409			return err
410		}
411		defer rows.Close()
412		for rows.Next() {
413			var s string
414			err = rows.Scan(&s)
415			if err != nil {
416				return err
417			}
418			(*m)[s] = true
419		}
420		return nil
421	}
422	row := stmtConfig.QueryRow(key)
423	err := row.Scan(value)
424	if err == sql.ErrNoRows {
425		err = nil
426	}
427	return err
428}
429
430func setconfig(key string, val interface{}) error {
431	db := opendatabase()
432	db.Exec("delete from config where key = ?", key)
433	_, err := db.Exec("insert into config (key, value) values (?, ?)", key, val)
434	return err
435}
436
437func openListener() (net.Listener, error) {
438	var listenAddr string
439	err := getconfig("listenaddr", &listenAddr)
440	if err != nil {
441		return nil, err
442	}
443	if listenAddr == "" {
444		return nil, fmt.Errorf("must have listenaddr")
445	}
446	proto := "tcp"
447	if listenAddr[0] == '/' {
448		proto = "unix"
449		err := os.Remove(listenAddr)
450		if err != nil && !os.IsNotExist(err) {
451			log.Printf("unable to unlink socket: %s", err)
452		}
453	}
454	listener, err := net.Listen(proto, listenAddr)
455	if err != nil {
456		return nil, err
457	}
458	if proto == "unix" {
459		os.Chmod(listenAddr, 0777)
460	}
461	return listener, nil
462}