honk/util.go

464 lines
9.8 KiB
Go
Raw Permalink Normal View History

2019-04-09 13:59:33 +02:00
//
// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package main
/*
#include <termios.h>
void
termecho(int on)
{
struct termios t;
tcgetattr(1, &t);
if (on)
t.c_lflag |= ECHO;
else
t.c_lflag &= ~ECHO;
tcsetattr(1, TCSADRAIN, &t);
}
*/
import "C"
2019-04-09 13:59:33 +02:00
import (
"bufio"
"crypto/rand"
2019-05-22 21:11:39 +02:00
"crypto/rsa"
2019-04-09 13:59:33 +02:00
"crypto/sha512"
"database/sql"
"fmt"
"io/ioutil"
"net"
"os"
"os/signal"
"regexp"
2019-04-09 13:59:33 +02:00
"strings"
"golang.org/x/crypto/bcrypt"
_ "humungus.tedunangst.com/r/go-sqlite3"
"humungus.tedunangst.com/r/webs/httpsig"
2019-10-13 01:21:29 +02:00
"humungus.tedunangst.com/r/webs/login"
2019-04-09 13:59:33 +02:00
)
2019-09-19 04:17:50 +02:00
var savedassetparams = make(map[string]string)
2019-04-09 13:59:33 +02:00
2021-04-03 01:59:01 +02:00
var re_plainname = regexp.MustCompile("^[[:alnum:]_-]+$")
2020-11-26 03:55:25 +01:00
2019-09-19 04:17:50 +02:00
func getassetparam(file string) string {
if p, ok := savedassetparams[file]; ok {
return p
}
data, err := ioutil.ReadFile(file)
if err != nil {
return ""
2019-04-09 13:59:33 +02:00
}
hasher := sha512.New()
hasher.Write(data)
2019-04-09 13:59:33 +02:00
return fmt.Sprintf("?v=%.8x", hasher.Sum(nil))
}
var dbtimeformat = "2006-01-02 15:04:05"
var alreadyopendb *sql.DB
var stmtConfig *sql.Stmt
func initdb() {
dbname := dataDir + "/honk.db"
_, err := os.Stat(dbname)
2019-04-09 13:59:33 +02:00
if err == nil {
2022-02-06 06:42:13 +01:00
elog.Fatalf("%s already exists", dbname)
2019-04-09 13:59:33 +02:00
}
db, err := sql.Open("sqlite3", dbname)
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Fatal(err)
2019-04-09 13:59:33 +02:00
}
alreadyopendb = db
2019-04-09 13:59:33 +02:00
defer func() {
os.Remove(dbname)
os.Exit(1)
}()
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt)
go func() {
<-c
C.termecho(1)
2019-04-09 13:59:33 +02:00
fmt.Printf("\n")
os.Remove(dbname)
os.Exit(1)
}()
for _, line := range strings.Split(sqlSchema, ";") {
2019-04-09 13:59:33 +02:00
_, err = db.Exec(line)
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Print(err)
2019-04-09 13:59:33 +02:00
return
}
}
r := bufio.NewReader(os.Stdin)
2019-05-22 21:11:39 +02:00
initblobdb()
prepareStatements(db)
2020-12-10 07:16:46 +01:00
err = createuser(db, r)
2019-04-09 13:59:33 +02:00
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Print(err)
2019-04-09 13:59:33 +02:00
return
}
2020-12-10 07:16:46 +01:00
// must came later or user above will have negative id
err = createserveruser(db)
2019-10-25 23:31:48 +02:00
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Print(err)
2019-10-25 23:31:48 +02:00
return
}
2019-05-22 21:11:39 +02:00
2019-04-09 13:59:33 +02:00
fmt.Printf("listen address: ")
addr, err := r.ReadString('\n')
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Print(err)
2019-04-09 13:59:33 +02:00
return
}
addr = addr[:len(addr)-1]
if len(addr) < 1 {
2022-02-06 06:42:13 +01:00
elog.Print("that's way too short")
2019-04-09 13:59:33 +02:00
return
}
setconfig("listenaddr", addr)
2019-04-09 13:59:33 +02:00
fmt.Printf("server name: ")
addr, err = r.ReadString('\n')
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Print(err)
2019-04-09 13:59:33 +02:00
return
}
addr = addr[:len(addr)-1]
if len(addr) < 1 {
2022-02-06 06:42:13 +01:00
elog.Print("that's way too short")
2019-04-09 13:59:33 +02:00
return
}
setconfig("servername", addr)
2019-04-09 13:59:33 +02:00
var randbytes [16]byte
rand.Read(randbytes[:])
key := fmt.Sprintf("%x", randbytes)
setconfig("csrfkey", key)
setconfig("dbversion", myVersion)
setconfig("servermsg", "<h2>Things happen.</h2>")
setconfig("aboutmsg", "<h3>What is honk?</h3><p>Honk is amazing!")
setconfig("loginmsg", "<h2>login</h2>")
setconfig("debug", 0)
2019-05-22 21:11:39 +02:00
db.Close()
fmt.Printf("done.\n")
os.Exit(0)
}
func initblobdb() {
blobdbname := dataDir + "/blob.db"
_, err := os.Stat(blobdbname)
if err == nil {
2022-02-06 06:42:13 +01:00
elog.Fatalf("%s already exists", blobdbname)
}
blobdb, err := sql.Open("sqlite3", blobdbname)
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Print(err)
return
}
2020-09-30 21:20:40 +02:00
_, err = blobdb.Exec("create table filedata (xid text, media text, hash text, content blob)")
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Print(err)
return
}
_, err = blobdb.Exec("create index idx_filexid on filedata(xid)")
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Print(err)
return
}
2020-09-30 21:20:40 +02:00
_, err = blobdb.Exec("create index idx_filehash on filedata(hash)")
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Print(err)
2020-09-30 21:20:40 +02:00
return
}
blobdb.Close()
}
2019-05-22 21:11:39 +02:00
func adduser() {
db := opendatabase()
defer func() {
os.Exit(1)
}()
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt)
go func() {
<-c
C.termecho(1)
fmt.Printf("\n")
os.Exit(1)
}()
r := bufio.NewReader(os.Stdin)
err := createuser(db, r)
2019-04-09 13:59:33 +02:00
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Print(err)
2019-04-09 13:59:33 +02:00
return
}
2019-05-22 21:11:39 +02:00
2019-04-09 13:59:33 +02:00
os.Exit(0)
}
2019-12-10 20:03:44 +01:00
func deluser(username string) {
user, _ := butwhatabout(username)
if user == nil {
2022-02-06 06:42:13 +01:00
elog.Printf("no userfound")
2019-12-10 20:03:44 +01:00
return
}
userid := user.ID
db := opendatabase()
where := " where honkid in (select honkid from honks where userid = ?)"
doordie(db, "delete from donks"+where, userid)
doordie(db, "delete from onts"+where, userid)
doordie(db, "delete from honkmeta"+where, userid)
2020-05-26 06:32:08 +02:00
where = " where chonkid in (select chonkid from chonks where userid = ?)"
doordie(db, "delete from donks"+where, userid)
2019-12-10 20:03:44 +01:00
doordie(db, "delete from honks where userid = ?", userid)
2020-05-26 06:32:08 +02:00
doordie(db, "delete from chonks where userid = ?", userid)
2019-12-10 20:03:44 +01:00
doordie(db, "delete from honkers where userid = ?", userid)
doordie(db, "delete from zonkers where userid = ?", userid)
doordie(db, "delete from doovers where userid = ?", userid)
doordie(db, "delete from hfcs where userid = ?", userid)
doordie(db, "delete from auth where userid = ?", userid)
doordie(db, "delete from users where userid = ?", userid)
}
2019-10-13 01:21:29 +02:00
func chpass() {
if len(os.Args) < 3 {
fmt.Printf("need a username\n")
os.Exit(1)
}
user, err := butwhatabout(os.Args[2])
2019-05-22 21:11:39 +02:00
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Fatal(err)
2019-05-22 21:11:39 +02:00
}
2019-10-13 01:21:29 +02:00
defer func() {
os.Exit(1)
}()
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt)
go func() {
<-c
C.termecho(1)
fmt.Printf("\n")
os.Exit(1)
}()
db := opendatabase()
2022-02-09 22:04:06 +01:00
login.Init(login.InitArgs{Db: db, Logger: ilog})
2019-10-13 01:21:29 +02:00
r := bufio.NewReader(os.Stdin)
pass, err := askpassword(r)
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Print(err)
2019-10-13 01:21:29 +02:00
return
}
err = login.SetPassword(user.ID, pass)
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Print(err)
2019-10-13 01:21:29 +02:00
return
2019-05-22 21:11:39 +02:00
}
2019-10-13 01:21:29 +02:00
fmt.Printf("done\n")
os.Exit(0)
}
func askpassword(r *bufio.Reader) (string, error) {
2019-05-22 21:11:39 +02:00
C.termecho(0)
fmt.Printf("password: ")
pass, err := r.ReadString('\n')
C.termecho(1)
fmt.Printf("\n")
if err != nil {
2019-10-13 01:21:29 +02:00
return "", err
2019-05-22 21:11:39 +02:00
}
pass = pass[:len(pass)-1]
if len(pass) < 6 {
2019-10-13 01:21:29 +02:00
return "", fmt.Errorf("that's way too short")
}
return pass, nil
}
func createuser(db *sql.DB, r *bufio.Reader) error {
fmt.Printf("username: ")
name, err := r.ReadString('\n')
if err != nil {
return err
}
name = name[:len(name)-1]
if len(name) < 1 {
2019-05-22 21:11:39 +02:00
return fmt.Errorf("that's way too short")
}
2020-11-26 03:55:25 +01:00
if !re_plainname.MatchString(name) {
return fmt.Errorf("alphanumeric only please")
}
if _, err := butwhatabout(name); err == nil {
return fmt.Errorf("user already exists")
}
2019-10-13 01:21:29 +02:00
pass, err := askpassword(r)
if err != nil {
return err
}
2019-05-22 21:11:39 +02:00
hash, err := bcrypt.GenerateFromPassword([]byte(pass), 12)
if err != nil {
return err
}
k, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
pubkey, err := httpsig.EncodeKey(&k.PublicKey)
2019-05-22 21:11:39 +02:00
if err != nil {
return err
}
seckey, err := httpsig.EncodeKey(k)
2019-05-22 21:11:39 +02:00
if err != nil {
return err
}
2019-10-25 23:31:48 +02:00
about := "what about me?"
_, err = db.Exec("insert into users (username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?)", name, name, about, hash, pubkey, seckey, "{}")
2019-10-25 23:31:48 +02:00
if err != nil {
return err
}
return nil
}
func createserveruser(db *sql.DB) error {
k, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
pubkey, err := httpsig.EncodeKey(&k.PublicKey)
if err != nil {
return err
}
seckey, err := httpsig.EncodeKey(k)
if err != nil {
return err
}
name := "server"
about := "server"
hash := "*"
_, err = db.Exec("insert into users (userid, username, displayname, about, hash, pubkey, seckey, options) values (?, ?, ?, ?, ?, ?, ?, ?)", serverUID, name, name, about, hash, pubkey, seckey, "")
2019-05-22 21:11:39 +02:00
if err != nil {
return err
}
return nil
}
2019-04-09 13:59:33 +02:00
func opendatabase() *sql.DB {
if alreadyopendb != nil {
return alreadyopendb
}
dbname := dataDir + "/honk.db"
_, err := os.Stat(dbname)
2019-04-09 13:59:33 +02:00
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Fatalf("unable to open database: %s", err)
2019-04-09 13:59:33 +02:00
}
db, err := sql.Open("sqlite3", dbname)
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Fatalf("unable to open database: %s", err)
2019-04-09 13:59:33 +02:00
}
stmtConfig, err = db.Prepare("select value from config where key = ?")
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Fatal(err)
2019-04-09 13:59:33 +02:00
}
alreadyopendb = db
return db
}
2019-09-30 22:43:51 +02:00
func openblobdb() *sql.DB {
blobdbname := dataDir + "/blob.db"
_, err := os.Stat(blobdbname)
2019-09-30 22:43:51 +02:00
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Fatalf("unable to open database: %s", err)
2019-09-30 22:43:51 +02:00
}
db, err := sql.Open("sqlite3", blobdbname)
if err != nil {
2022-02-06 06:42:13 +01:00
elog.Fatalf("unable to open database: %s", err)
2019-09-30 22:43:51 +02:00
}
return db
}
2019-04-09 13:59:33 +02:00
func getconfig(key string, value interface{}) error {
m, ok := value.(*map[string]bool)
if ok {
rows, err := stmtConfig.Query(key)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var s string
err = rows.Scan(&s)
if err != nil {
return err
}
(*m)[s] = true
}
return nil
}
2019-04-09 13:59:33 +02:00
row := stmtConfig.QueryRow(key)
err := row.Scan(value)
if err == sql.ErrNoRows {
err = nil
}
return err
}
func setconfig(key string, val interface{}) error {
2019-08-15 15:25:36 +02:00
db := opendatabase()
2019-12-10 03:20:35 +01:00
db.Exec("delete from config where key = ?", key)
_, err := db.Exec("insert into config (key, value) values (?, ?)", key, val)
return err
}
2019-04-09 13:59:33 +02:00
func openListener() (net.Listener, error) {
var listenAddr string
err := getconfig("listenaddr", &listenAddr)
if err != nil {
return nil, err
}
if listenAddr == "" {
return nil, fmt.Errorf("must have listenaddr")
}
proto := "tcp"
if listenAddr[0] == '/' {
proto = "unix"
err := os.Remove(listenAddr)
if err != nil && !os.IsNotExist(err) {
2022-02-06 06:42:13 +01:00
elog.Printf("unable to unlink socket: %s", err)
2019-04-09 13:59:33 +02:00
}
}
listener, err := net.Listen(proto, listenAddr)
if err != nil {
return nil, err
}
if proto == "unix" {
os.Chmod(listenAddr, 0777)
}
return listener, nil
}