mirror of
https://github.com/renorris/openfsd
synced 2026-03-22 23:05:36 +08:00
212 lines
4.7 KiB
Go
212 lines
4.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"database/sql"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"github.com/go-playground/validator/v10"
|
|
_ "github.com/mattn/go-sqlite3"
|
|
"github.com/renorris/openfsd/protocol"
|
|
"github.com/sethvargo/go-envconfig"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
)
|
|
|
|
type ServerConfig struct {
|
|
FsdListenAddr string `env:"FSD_ADDR, default=0.0.0.0:6809"`
|
|
HttpListenAddr string `env:"HTTP_ADDR, default=0.0.0.0:9086"`
|
|
HttpsEnabled bool `env:"HTTPS_ENABLED, default=false"`
|
|
TLSCertFile string `env:"TLS_CERT_FILE"`
|
|
TLSKeyFile string `env:"TLS_KEY_FILE"`
|
|
DatabaseFile string `env:"DATABASE_FILE, default=./fsd.db"`
|
|
MOTD string `env:"MOTD, default=openfsd"`
|
|
}
|
|
|
|
var SC *ServerConfig
|
|
var DB *sql.DB
|
|
var PO *PostOffice
|
|
var JWTKey []byte
|
|
|
|
const TableCreateStatement = `
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
cid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
password CHAR(60) NOT NULL,
|
|
rating TINYINT NOT NULL,
|
|
real_name VARCHAR(64) NOT NULL,
|
|
creation_time DATETIME NOT NULL
|
|
);
|
|
`
|
|
|
|
func StartFSDServer(fsdCtx context.Context) {
|
|
// Attempt to resolve the previously-configured listen address
|
|
addr, err := net.ResolveTCPAddr("tcp4", SC.FsdListenAddr)
|
|
if err != nil {
|
|
log.Fatal("Error resolving address: " + err.Error())
|
|
}
|
|
|
|
// Attempt to open a listener on that address
|
|
listener, err := net.ListenTCP("tcp4", addr)
|
|
if err != nil {
|
|
log.Fatal("Error listening: " + err.Error())
|
|
}
|
|
|
|
// Defer listener close
|
|
// This will force the listener goroutine to encounter an error and promptly close itself
|
|
defer func() {
|
|
closeErr := listener.Close()
|
|
if closeErr != nil {
|
|
log.Println("Error closing listener: " + closeErr.Error())
|
|
}
|
|
}()
|
|
|
|
// Listen on a separate goroutine
|
|
// connChan shall be closed when the listener closes (due to an error or surrounding context closed)
|
|
connChan := make(chan *net.TCPConn)
|
|
go func(connChan chan<- *net.TCPConn) {
|
|
// Guarantee that connChan will be closed when we return
|
|
defer close(connChan)
|
|
|
|
for {
|
|
// Wait for a connection on the listener
|
|
conn, listenerErr := listener.AcceptTCP()
|
|
if listenerErr != nil {
|
|
return
|
|
}
|
|
|
|
// Wait for a consumer on connChan, OR return if the context cancels.
|
|
select {
|
|
case connChan <- conn:
|
|
case <-fsdCtx.Done():
|
|
return
|
|
}
|
|
}
|
|
}(connChan)
|
|
|
|
log.Println("FSD listening")
|
|
|
|
// Poll from connChan, check if the channel is healthy
|
|
// Also poll from our context, check if we need to return
|
|
for {
|
|
select {
|
|
case conn, ok := <-connChan:
|
|
if !ok {
|
|
return
|
|
}
|
|
go HandleConnection(conn)
|
|
case <-fsdCtx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func configureProtocolValidator() {
|
|
protocol.V = validator.New(validator.WithRequiredStructEnabled())
|
|
}
|
|
|
|
func configurePostOffice() {
|
|
PO = &PostOffice{
|
|
clientRegistry: make(map[string]*FSDClient),
|
|
supervisorRegistry: make(map[string]*FSDClient),
|
|
geohashRegistry: make(map[string][]*FSDClient),
|
|
}
|
|
}
|
|
|
|
func configureJwt() {
|
|
idBytes := make([]byte, 64)
|
|
_, err := rand.Read(idBytes)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
JWTKey = idBytes
|
|
}
|
|
|
|
func configureDatabase() {
|
|
db, err := sql.Open("sqlite3", SC.DatabaseFile)
|
|
if err != nil {
|
|
log.Panic(err)
|
|
}
|
|
|
|
_, err = db.Exec(TableCreateStatement)
|
|
if err != nil {
|
|
log.Panic(err)
|
|
}
|
|
|
|
// Check if the users table is empty
|
|
// (is this the first time we've started up using this database?)
|
|
usersEmpty, err := IsUsersTableEmpty(db)
|
|
if err != nil {
|
|
log.Panic(err)
|
|
}
|
|
|
|
// If it's empty, add a default admin user
|
|
if usersEmpty {
|
|
buf := make([]byte, 8) // since each byte is 2 hex characters
|
|
if _, err = io.ReadFull(rand.Reader, buf); err != nil {
|
|
log.Panic(err)
|
|
}
|
|
|
|
pwd := hex.EncodeToString(buf)
|
|
|
|
cid := 100000
|
|
pwdHash, err := bcrypt.GenerateFromPassword([]byte(pwd), 10)
|
|
if err != nil {
|
|
log.Panic(err)
|
|
}
|
|
rating := protocol.NetworkRatingADM
|
|
realName := "Default Administrator"
|
|
|
|
record, err := AddUserRecord(db, cid, string(pwdHash), rating, realName)
|
|
if err != nil {
|
|
log.Panic(err)
|
|
}
|
|
|
|
fmt.Printf("Added user: %s CID: %d Password: %s Rating: Administrator\n", record.RealName, record.CID, pwd)
|
|
}
|
|
|
|
// Set global DB variable
|
|
DB = db
|
|
}
|
|
|
|
func setupServerConfig() {
|
|
ctx := context.Background()
|
|
|
|
var c ServerConfig
|
|
if err := envconfig.Process(ctx, &c); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
SC = &c
|
|
}
|
|
|
|
func main() {
|
|
setupServerConfig()
|
|
configureProtocolValidator()
|
|
|
|
configureDatabase()
|
|
defer DB.Close()
|
|
|
|
configureJwt()
|
|
configurePostOffice()
|
|
|
|
httpCtx, cancelHttp := context.WithCancel(context.Background())
|
|
go StartHttpServer(httpCtx)
|
|
defer cancelHttp()
|
|
|
|
fsdCtx, cancelFsd := context.WithCancel(context.Background())
|
|
go StartFSDServer(fsdCtx)
|
|
defer cancelFsd()
|
|
|
|
done := make(chan os.Signal, 1)
|
|
signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
// Wait for OS done signal
|
|
<-done
|
|
}
|