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