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 for _, line := range strings.Split(sqlSchema, ";") {
104 _, err = db.Exec(line)
105 if err != nil {
106 elog.Print(err)
107 return
108 }
109 }
110 r := bufio.NewReader(os.Stdin)
111
112 initblobdb()
113
114 prepareStatements(db)
115
116 err = createuser(db, r)
117 if err != nil {
118 elog.Print(err)
119 return
120 }
121 // must came later or user above will have negative id
122 err = createserveruser(db)
123 if err != nil {
124 elog.Print(err)
125 return
126 }
127
128 fmt.Printf("listen address: ")
129 addr, err := r.ReadString('\n')
130 if err != nil {
131 elog.Print(err)
132 return
133 }
134 addr = addr[:len(addr)-1]
135 if len(addr) < 1 {
136 elog.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 elog.Print(err)
144 return
145 }
146 addr = addr[:len(addr)-1]
147 if len(addr) < 1 {
148 elog.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 elog.Fatalf("%s already exists", blobdbname)
173 }
174 blobdb, err := sql.Open("sqlite3", blobdbname)
175 if err != nil {
176 elog.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 elog.Print(err)
182 return
183 }
184 _, err = blobdb.Exec("create index idx_filexid on filedata(xid)")
185 if err != nil {
186 elog.Print(err)
187 return
188 }
189 _, err = blobdb.Exec("create index idx_filehash on filedata(hash)")
190 if err != nil {
191 elog.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 elog.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 elog.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 elog.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 elog.Print(err)
277 return
278 }
279 err = login.SetPassword(user.ID, pass)
280 if err != nil {
281 elog.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 elog.Fatalf("unable to open database: %s", err)
379 }
380 db, err := sql.Open("sqlite3", dbname)
381 if err != nil {
382 elog.Fatalf("unable to open database: %s", err)
383 }
384 stmtConfig, err = db.Prepare("select value from config where key = ?")
385 if err != nil {
386 elog.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 elog.Fatalf("unable to open database: %s", err)
397 }
398 db, err := sql.Open("sqlite3", blobdbname)
399 if err != nil {
400 elog.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 elog.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}