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(username string) {
259 user, err := butwhatabout(username)
260 if err != nil {
261 elog.Fatal(err)
262 }
263 defer func() {
264 os.Exit(1)
265 }()
266 c := make(chan os.Signal)
267 signal.Notify(c, os.Interrupt)
268 go func() {
269 <-c
270 C.termecho(1)
271 fmt.Printf("\n")
272 os.Exit(1)
273 }()
274
275 db := opendatabase()
276 login.Init(login.InitArgs{Db: db, Logger: ilog})
277
278 r := bufio.NewReader(os.Stdin)
279
280 pass, err := askpassword(r)
281 if err != nil {
282 elog.Print(err)
283 return
284 }
285 err = login.SetPassword(user.ID, pass)
286 if err != nil {
287 elog.Print(err)
288 return
289 }
290 fmt.Printf("done\n")
291 os.Exit(0)
292}
293
294func askpassword(r *bufio.Reader) (string, error) {
295 C.termecho(0)
296 fmt.Printf("password: ")
297 pass, err := r.ReadString('\n')
298 C.termecho(1)
299 fmt.Printf("\n")
300 if err != nil {
301 return "", err
302 }
303 pass = pass[:len(pass)-1]
304 if len(pass) < 6 {
305 return "", fmt.Errorf("that's way too short")
306 }
307 return pass, nil
308}
309
310func createuser(db *sql.DB, r *bufio.Reader) error {
311 fmt.Printf("username: ")
312 name, err := r.ReadString('\n')
313 if err != nil {
314 return err
315 }
316 name = name[:len(name)-1]
317 if len(name) < 1 {
318 return fmt.Errorf("that's way too short")
319 }
320 if !re_plainname.MatchString(name) {
321 return fmt.Errorf("alphanumeric only please")
322 }
323 if _, err := butwhatabout(name); err == nil {
324 return fmt.Errorf("user already exists")
325 }
326 pass, err := askpassword(r)
327 if err != nil {
328 return err
329 }
330 hash, err := bcrypt.GenerateFromPassword([]byte(pass), 12)
331 if err != nil {
332 return err
333 }
334 k, err := rsa.GenerateKey(rand.Reader, 2048)
335 if err != nil {
336 return err
337 }
338 pubkey, err := httpsig.EncodeKey(&k.PublicKey)
339 if err != nil {
340 return err
341 }
342 seckey, err := httpsig.EncodeKey(k)
343 if err != nil {
344 return err
345 }
346 about := "what about me?"
347 _, err = db.Exec("insert into users (username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?)", name, name, about, hash, pubkey, seckey, "{}")
348 if err != nil {
349 return err
350 }
351 return nil
352}
353
354func createserveruser(db *sql.DB) error {
355 k, err := rsa.GenerateKey(rand.Reader, 2048)
356 if err != nil {
357 return err
358 }
359 pubkey, err := httpsig.EncodeKey(&k.PublicKey)
360 if err != nil {
361 return err
362 }
363 seckey, err := httpsig.EncodeKey(k)
364 if err != nil {
365 return err
366 }
367 name := "server"
368 about := "server"
369 hash := "*"
370 _, err = db.Exec("insert into users (userid, username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?, ?)", serverUID, name, name, about, hash, pubkey, seckey, "")
371 if err != nil {
372 return err
373 }
374 return nil
375}
376
377func opendatabase() *sql.DB {
378 if alreadyopendb != nil {
379 return alreadyopendb
380 }
381 dbname := dataDir + "/honk.db"
382 _, err := os.Stat(dbname)
383 if err != nil {
384 elog.Fatalf("unable to open database: %s", err)
385 }
386 db, err := sql.Open("sqlite3", dbname)
387 if err != nil {
388 elog.Fatalf("unable to open database: %s", err)
389 }
390 stmtConfig, err = db.Prepare("select value from config where key = ?")
391 if err != nil {
392 elog.Fatal(err)
393 }
394 alreadyopendb = db
395 return db
396}
397
398func openblobdb() *sql.DB {
399 blobdbname := dataDir + "/blob.db"
400 _, err := os.Stat(blobdbname)
401 if err != nil {
402 elog.Fatalf("unable to open database: %s", err)
403 }
404 db, err := sql.Open("sqlite3", blobdbname)
405 if err != nil {
406 elog.Fatalf("unable to open database: %s", err)
407 }
408 return db
409}
410
411func getconfig(key string, value interface{}) error {
412 m, ok := value.(*map[string]bool)
413 if ok {
414 rows, err := stmtConfig.Query(key)
415 if err != nil {
416 return err
417 }
418 defer rows.Close()
419 for rows.Next() {
420 var s string
421 err = rows.Scan(&s)
422 if err != nil {
423 return err
424 }
425 (*m)[s] = true
426 }
427 return nil
428 }
429 row := stmtConfig.QueryRow(key)
430 err := row.Scan(value)
431 if err == sql.ErrNoRows {
432 err = nil
433 }
434 return err
435}
436
437func setconfig(key string, val interface{}) error {
438 db := opendatabase()
439 db.Exec("delete from config where key = ?", key)
440 _, err := db.Exec("insert into config (key, value) values (?, ?)", key, val)
441 return err
442}
443
444func openListener() (net.Listener, error) {
445 var listenAddr string
446 err := getconfig("listenaddr", &listenAddr)
447 if err != nil {
448 return nil, err
449 }
450 if listenAddr == "" {
451 return nil, fmt.Errorf("must have listenaddr")
452 }
453 proto := "tcp"
454 if listenAddr[0] == '/' {
455 proto = "unix"
456 err := os.Remove(listenAddr)
457 if err != nil && !os.IsNotExist(err) {
458 elog.Printf("unable to unlink socket: %s", err)
459 }
460 }
461 listener, err := net.Listen(proto, listenAddr)
462 if err != nil {
463 return nil, err
464 }
465 if proto == "unix" {
466 os.Chmod(listenAddr, 0777)
467 }
468 return listener, nil
469}