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