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