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