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