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