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