package server import ( "crypto/rand" "encoding/hex" "net/url" "strconv" lpra "github.com/x-grk/longpolling-remote-access" ) // Config keys stored in the config table. const ( cfgHostAddress = "host_address" cfgEnrollSecret = "enroll_secret" cfgSetupDone = "setup_done" cfgInstallCode = "install_code" cfgInstallLen = "install_code_len" cfgUfwEnabled = "ufw_enabled" cfgTunnelHost = "tunnel_host" cfgTunnelPort = "tunnel_port" cfgDirectAddr = "direct_address" ) // DefaultHostAddress is the public URL agents dial by default. It is baked into // the rendered install.sh and can be changed from the settings page. const DefaultHostAddress = "https://REDACTED:8445" // Version is the server build version (see VERSION.txt). It is the newest // version agents update to. var Version = lpra.Version // ensureDefaults seeds the host address and a random enroll secret on first boot. func (db *DB) ensureDefaults() error { host, err := db.GetConfig(cfgHostAddress) if err != nil { return err } if host == "" { if err := db.SetConfig(cfgHostAddress, DefaultHostAddress); err != nil { return err } } secret, err := db.GetConfig(cfgEnrollSecret) if err != nil { return err } if secret == "" { if err := db.SetConfig(cfgEnrollSecret, randToken()); err != nil { return err } } code, err := db.GetConfig(cfgInstallCode) if err != nil { return err } if code == "" { if err := db.SetConfig(cfgInstallCode, shortCode(db.InstallCodeLen())); err != nil { return err } } return nil } func (db *DB) HostAddress() string { v, _ := db.GetConfig(cfgHostAddress) if v == "" { return DefaultHostAddress } return v } // DirectAddress is the server's direct URL, one that is not behind a proxy // like Cloudflare: the agents' fallback, and a way to install when the main // address is out of reach. Empty when not set. func (db *DB) DirectAddress() string { v, _ := db.GetConfig(cfgDirectAddr) return v } // directHost is the direct address's host name, without scheme and port. func directHost(addr string) string { u, err := url.Parse(addr) if err != nil { return "" } return u.Hostname() } func (db *DB) EnrollSecret() string { v, _ := db.GetConfig(cfgEnrollSecret) return v } // InstallCode returns the short code used for the /i/ install one-liner. func (db *DB) InstallCode() string { v, _ := db.GetConfig(cfgInstallCode) return v } // UfwEnabled reports whether panel-driven ufw integration is on (default yes). func (db *DB) UfwEnabled() bool { v, _ := db.GetConfig(cfgUfwEnabled) return v != "0" } func (db *DB) SetUfwEnabled(on bool) error { if on { return db.SetConfig(cfgUfwEnabled, "1") } return db.SetConfig(cfgUfwEnabled, "0") } // TunnelHost is the host agents dial for the direct tunnel. Empty means the // host of the URL they already reach the panel on. func (db *DB) TunnelHost() string { v, _ := db.GetConfig(cfgTunnelHost) return v } // TunnelPort is the direct-tunnel port set in Settings, or 0 when it is left // at the port of the server's -tunnel address. func (db *DB) TunnelPort() int { v, _ := db.GetConfig(cfgTunnelPort) n, _ := strconv.Atoi(v) if n < 1 || n > 65535 { return 0 } return n } func (db *DB) SetupDone() bool { v, _ := db.GetConfig(cfgSetupDone) return v == "1" } func (db *DB) MarkSetupDone() error { return db.SetConfig(cfgSetupDone, "1") } // randToken returns 32 bytes of cryptographic randomness as hex (64 chars). func randToken() string { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { panic("crypto/rand failed: " + err.Error()) } return hex.EncodeToString(b) } // Install code length: 3 by default, up to 24. const ( installCodeMin = 3 installCodeMax = 24 installCodeDef = 3 ) // InstallCodeLen is how many characters a new install code has. func (db *DB) InstallCodeLen() int { v, _ := db.GetConfig(cfgInstallLen) if n, err := strconv.Atoi(v); err == nil && n >= installCodeMin && n <= installCodeMax { return n } return installCodeDef } // shortCode returns a random code of n English letters and digits for the // /i/ install link. Every character is equally likely. func shortCode(n int) string { const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" out := make([]byte, 0, n) b := make([]byte, 1) for len(out) < n { if _, err := rand.Read(b); err != nil { panic("crypto/rand failed: " + err.Error()) } // 248 = 4 * 62: drop the rest, so no character comes up more often. if b[0] < 248 { out = append(out, alphabet[int(b[0])%len(alphabet)]) } } return string(out) }