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