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