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