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/sha512"
39 "database/sql"
40 "fmt"
41 "io/ioutil"
42 "log"
43 "net"
44 "os"
45 "os/signal"
46 "strings"
47
48 "golang.org/x/crypto/bcrypt"
49 _ "humungus.tedunangst.com/r/go-sqlite3"
50)
51
52var savedstyleparams = make(map[string]string)
53
54func getstyleparam(file string) string {
55 if p, ok := savedstyleparams[file]; ok {
56 return p
57 }
58 data, err := ioutil.ReadFile(file)
59 if err != nil {
60 return ""
61 }
62 hasher := sha512.New()
63 hasher.Write(data)
64
65 return fmt.Sprintf("?v=%.8x", hasher.Sum(nil))
66}
67
68var dbtimeformat = "2006-01-02 15:04:05"
69
70var alreadyopendb *sql.DB
71var dbname = "honk.db"
72var stmtConfig *sql.Stmt
73var myVersion = 1
74
75func initdb() {
76 schema, err := ioutil.ReadFile("schema.sql")
77 if err != nil {
78 log.Fatal(err)
79 }
80 _, err = os.Stat(dbname)
81 if err == nil {
82 log.Fatalf("%s already exists", dbname)
83 }
84 db, err := sql.Open("sqlite3", dbname)
85 if err != nil {
86 log.Fatal(err)
87 }
88 defer func() {
89 os.Remove(dbname)
90 os.Exit(1)
91 }()
92 c := make(chan os.Signal)
93 signal.Notify(c, os.Interrupt)
94 go func() {
95 <-c
96 C.termecho(1)
97 fmt.Printf("\n")
98 os.Remove(dbname)
99 os.Exit(1)
100 }()
101
102 for _, line := range strings.Split(string(schema), ";") {
103 _, err = db.Exec(line)
104 if err != nil {
105 log.Print(err)
106 return
107 }
108 }
109 defer db.Close()
110 r := bufio.NewReader(os.Stdin)
111 fmt.Printf("username: ")
112 name, err := r.ReadString('\n')
113 if err != nil {
114 log.Print(err)
115 return
116 }
117 name = name[:len(name)-1]
118 if len(name) < 1 {
119 log.Print("that's way too short")
120 return
121 }
122 C.termecho(0)
123 fmt.Printf("password: ")
124 pass, err := r.ReadString('\n')
125 C.termecho(1)
126 fmt.Printf("\n")
127 if err != nil {
128 log.Print(err)
129 return
130 }
131 pass = pass[:len(pass)-1]
132 if len(pass) < 6 {
133 log.Print("that's way too short")
134 return
135 }
136 hash, err := bcrypt.GenerateFromPassword([]byte(pass), 12)
137 if err != nil {
138 log.Print(err)
139 return
140 }
141 _, err = db.Exec("insert into users (username, hash) values (?, ?)", name, hash)
142 if err != nil {
143 log.Print(err)
144 return
145 }
146 fmt.Printf("listen address: ")
147 addr, err := r.ReadString('\n')
148 if err != nil {
149 log.Print(err)
150 return
151 }
152 addr = addr[:len(addr)-1]
153 if len(addr) < 1 {
154 log.Print("that's way too short")
155 return
156 }
157 _, err = db.Exec("insert into config (key, value) values (?, ?)", "listenaddr", addr)
158 if err != nil {
159 log.Print(err)
160 return
161 }
162 fmt.Printf("server name: ")
163 addr, err = r.ReadString('\n')
164 if err != nil {
165 log.Print(err)
166 return
167 }
168 addr = addr[:len(addr)-1]
169 if len(addr) < 1 {
170 log.Print("that's way too short")
171 return
172 }
173 _, err = db.Exec("insert into config (key, value) values (?, ?)", "servername", addr)
174 if err != nil {
175 log.Print(err)
176 return
177 }
178 var randbytes [16]byte
179 rand.Read(randbytes[:])
180 key := fmt.Sprintf("%x", randbytes)
181 _, err = db.Exec("insert into config (key, value) values (?, ?)", "csrfkey", key)
182 if err != nil {
183 log.Print(err)
184 return
185 }
186 _, err = db.Exec("insert into config (key, value) values (?, ?)", "dbversion", myVersion)
187 if err != nil {
188 log.Print(err)
189 return
190 }
191 err = finishusersetup()
192 if err != nil {
193 log.Print(err)
194 return
195 }
196 prepareStatements(db)
197 db.Close()
198 fmt.Printf("done.\n")
199 os.Exit(0)
200}
201
202func opendatabase() *sql.DB {
203 if alreadyopendb != nil {
204 return alreadyopendb
205 }
206 var err error
207 _, err = os.Stat(dbname)
208 if err != nil {
209 log.Fatalf("unable to open database: %s", err)
210 }
211 db, err := sql.Open("sqlite3", dbname)
212 if err != nil {
213 log.Fatalf("unable to open database: %s", err)
214 }
215 stmtConfig, err = db.Prepare("select value from config where key = ?")
216 if err != nil {
217 log.Fatal(err)
218 }
219 alreadyopendb = db
220 return db
221}
222
223func getconfig(key string, value interface{}) error {
224 row := stmtConfig.QueryRow(key)
225 err := row.Scan(value)
226 if err == sql.ErrNoRows {
227 err = nil
228 }
229 return err
230}
231
232func openListener() (net.Listener, error) {
233 var listenAddr string
234 err := getconfig("listenaddr", &listenAddr)
235 if err != nil {
236 return nil, err
237 }
238 if listenAddr == "" {
239 return nil, fmt.Errorf("must have listenaddr")
240 }
241 proto := "tcp"
242 if listenAddr[0] == '/' {
243 proto = "unix"
244 err := os.Remove(listenAddr)
245 if err != nil && !os.IsNotExist(err) {
246 log.Printf("unable to unlink socket: %s", err)
247 }
248 }
249 listener, err := net.Listen(proto, listenAddr)
250 if err != nil {
251 return nil, err
252 }
253 if proto == "unix" {
254 os.Chmod(listenAddr, 0777)
255 }
256 return listener, nil
257}