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