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