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