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