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 defer db.Close()
109 r := bufio.NewReader(os.Stdin)
110
111 err = createuser(db, r)
112 if err != nil {
113 log.Print(err)
114 return
115 }
116 err = createserveruser(db)
117 if err != nil {
118 log.Print(err)
119 return
120 }
121
122 fmt.Printf("listen address: ")
123 addr, err := r.ReadString('\n')
124 if err != nil {
125 log.Print(err)
126 return
127 }
128 addr = addr[:len(addr)-1]
129 if len(addr) < 1 {
130 log.Print("that's way too short")
131 return
132 }
133 setconfig("listenaddr", addr)
134 fmt.Printf("server name: ")
135 addr, err = r.ReadString('\n')
136 if err != nil {
137 log.Print(err)
138 return
139 }
140 addr = addr[:len(addr)-1]
141 if len(addr) < 1 {
142 log.Print("that's way too short")
143 return
144 }
145 setconfig("servername", addr)
146 var randbytes [16]byte
147 rand.Read(randbytes[:])
148 key := fmt.Sprintf("%x", randbytes)
149 setconfig("csrfkey", key)
150 setconfig("dbversion", myVersion)
151
152 setconfig("servermsg", "<h2>Things happen.</h2>")
153 setconfig("aboutmsg", "<h3>What is honk?</h3>\n<p>Honk is amazing!")
154 setconfig("loginmsg", "<h2>login</h2>")
155 setconfig("debug", 0)
156
157 initblobdb()
158
159 prepareStatements(db)
160 db.Close()
161 fmt.Printf("done.\n")
162 os.Exit(0)
163}
164
165func initblobdb() {
166 blobdbname := dataDir + "/blob.db"
167 _, err := os.Stat(blobdbname)
168 if err == nil {
169 log.Fatalf("%s already exists", blobdbname)
170 }
171 blobdb, err := sql.Open("sqlite3", blobdbname)
172 if err != nil {
173 log.Print(err)
174 return
175 }
176 _, err = blobdb.Exec("create table filedata (xid text, media text, content blob)")
177 if err != nil {
178 log.Print(err)
179 return
180 }
181 _, err = blobdb.Exec("create index idx_filexid on filedata(xid)")
182 if err != nil {
183 log.Print(err)
184 return
185 }
186 blobdb.Close()
187}
188
189func adduser() {
190 db := opendatabase()
191 defer func() {
192 os.Exit(1)
193 }()
194 c := make(chan os.Signal)
195 signal.Notify(c, os.Interrupt)
196 go func() {
197 <-c
198 C.termecho(1)
199 fmt.Printf("\n")
200 os.Exit(1)
201 }()
202
203 r := bufio.NewReader(os.Stdin)
204
205 err := createuser(db, r)
206 if err != nil {
207 log.Print(err)
208 return
209 }
210
211 db.Close()
212 os.Exit(0)
213}
214
215func chpass() {
216 if len(os.Args) < 3 {
217 fmt.Printf("need a username\n")
218 os.Exit(1)
219 }
220 user, err := butwhatabout(os.Args[2])
221 if err != nil {
222 log.Fatal(err)
223 }
224 defer func() {
225 os.Exit(1)
226 }()
227 c := make(chan os.Signal)
228 signal.Notify(c, os.Interrupt)
229 go func() {
230 <-c
231 C.termecho(1)
232 fmt.Printf("\n")
233 os.Exit(1)
234 }()
235
236 db := opendatabase()
237 login.Init(db)
238
239 r := bufio.NewReader(os.Stdin)
240
241 pass, err := askpassword(r)
242 if err != nil {
243 log.Print(err)
244 return
245 }
246 err = login.SetPassword(user.ID, pass)
247 if err != nil {
248 log.Print(err)
249 return
250 }
251 fmt.Printf("done\n")
252 os.Exit(0)
253}
254
255func askpassword(r *bufio.Reader) (string, error) {
256 C.termecho(0)
257 fmt.Printf("password: ")
258 pass, err := r.ReadString('\n')
259 C.termecho(1)
260 fmt.Printf("\n")
261 if err != nil {
262 return "", err
263 }
264 pass = pass[:len(pass)-1]
265 if len(pass) < 6 {
266 return "", fmt.Errorf("that's way too short")
267 }
268 return pass, nil
269}
270
271func createuser(db *sql.DB, r *bufio.Reader) error {
272 fmt.Printf("username: ")
273 name, err := r.ReadString('\n')
274 if err != nil {
275 return err
276 }
277 name = name[:len(name)-1]
278 if len(name) < 1 {
279 return fmt.Errorf("that's way too short")
280 }
281 pass, err := askpassword(r)
282 if err != nil {
283 return err
284 }
285 hash, err := bcrypt.GenerateFromPassword([]byte(pass), 12)
286 if err != nil {
287 return err
288 }
289 k, err := rsa.GenerateKey(rand.Reader, 2048)
290 if err != nil {
291 return err
292 }
293 pubkey, err := httpsig.EncodeKey(&k.PublicKey)
294 if err != nil {
295 return err
296 }
297 seckey, err := httpsig.EncodeKey(k)
298 if err != nil {
299 return err
300 }
301 about := "what about me?"
302 _, err = db.Exec("insert into users (username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?)", name, name, about, hash, pubkey, seckey, "{}")
303 if err != nil {
304 return err
305 }
306 return nil
307}
308
309func createserveruser(db *sql.DB) error {
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 name := "server"
323 about := "server"
324 hash := "*"
325 _, err = db.Exec("insert into users (userid, username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?, ?)", serverUID, name, name, about, hash, pubkey, seckey, "")
326 if err != nil {
327 return err
328 }
329 return nil
330}
331
332func opendatabase() *sql.DB {
333 if alreadyopendb != nil {
334 return alreadyopendb
335 }
336 dbname := dataDir + "/honk.db"
337 _, err := os.Stat(dbname)
338 if err != nil {
339 log.Fatalf("unable to open database: %s", err)
340 }
341 db, err := sql.Open("sqlite3", dbname)
342 if err != nil {
343 log.Fatalf("unable to open database: %s", err)
344 }
345 stmtConfig, err = db.Prepare("select value from config where key = ?")
346 if err != nil {
347 log.Fatal(err)
348 }
349 alreadyopendb = db
350 return db
351}
352
353func openblobdb() *sql.DB {
354 blobdbname := dataDir + "/blob.db"
355 _, err := os.Stat(blobdbname)
356 if err != nil {
357 log.Fatalf("unable to open database: %s", err)
358 }
359 db, err := sql.Open("sqlite3", blobdbname)
360 if err != nil {
361 log.Fatalf("unable to open database: %s", err)
362 }
363 return db
364}
365
366func getconfig(key string, value interface{}) error {
367 m, ok := value.(*map[string]bool)
368 if ok {
369 rows, err := stmtConfig.Query(key)
370 if err != nil {
371 return err
372 }
373 defer rows.Close()
374 for rows.Next() {
375 var s string
376 err = rows.Scan(&s)
377 if err != nil {
378 return err
379 }
380 (*m)[s] = true
381 }
382 return nil
383 }
384 row := stmtConfig.QueryRow(key)
385 err := row.Scan(value)
386 if err == sql.ErrNoRows {
387 err = nil
388 }
389 return err
390}
391
392func setconfig(key string, val interface{}) error {
393 db := opendatabase()
394 _, err := db.Exec("insert into config (key, value) values (?, ?)", key, val)
395 return err
396}
397
398func updateconfig(key string, val interface{}) error {
399 db := opendatabase()
400 _, err := db.Exec("update config set value = ? where key = ?", val, key)
401 return err
402}
403
404func openListener() (net.Listener, error) {
405 var listenAddr string
406 err := getconfig("listenaddr", &listenAddr)
407 if err != nil {
408 return nil, err
409 }
410 if listenAddr == "" {
411 return nil, fmt.Errorf("must have listenaddr")
412 }
413 proto := "tcp"
414 if listenAddr[0] == '/' {
415 proto = "unix"
416 err := os.Remove(listenAddr)
417 if err != nil && !os.IsNotExist(err) {
418 log.Printf("unable to unlink socket: %s", err)
419 }
420 }
421 listener, err := net.Listen(proto, listenAddr)
422 if err != nil {
423 return nil, err
424 }
425 if proto == "unix" {
426 os.Chmod(listenAddr, 0777)
427 }
428 return listener, nil
429}