Bind to localhost by default and stop echoing backend errors (which can embed credentials or low-level details) back over the API and log broadcast. Validate hotspot SSID/passphrase/channel before writing hostapd.conf and tighten its mode to 0600 since it stores the WPA PSK. Restrict WebSocket upgrades to same-origin so a LAN browser can't be turned into a proxy for the API. Guard shared state: status reads/writes go through StatusMutex (the periodic updater races with the toggle and status handlers otherwise), broadcastToWebSockets no longer mutates the client map under RLock, and station-event callbacks now run under SafeGo so a panic in app code can't take down the daemon. Stop channels in hostapd, dhcp, and iwd signal monitors are now closed under sync.Once to survive concurrent Stop calls. App.Shutdown is idempotent and waits for the periodic loops before closing backends, so signal-driven and deferred shutdowns no longer race. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
106 lines
2.7 KiB
Go
106 lines
2.7 KiB
Go
package config
|
|
|
|
import (
|
|
"flag"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
)
|
|
|
|
type Config struct {
|
|
Bind string
|
|
WifiInterface string
|
|
HotspotInterface string
|
|
WifiBackend string
|
|
StationBackend string // "arp", "dhcp", or "hostapd"
|
|
DHCPLeasesPath string
|
|
ARPTablePath string
|
|
SyslogEnabled bool
|
|
SyslogPath string
|
|
SyslogFilter []string
|
|
SyslogSource string
|
|
}
|
|
|
|
// ConsolidateConfig fills an Options struct by reading configuration from
|
|
// config files, environment, then command line.
|
|
//
|
|
// Should be called only one time.
|
|
func ConsolidateConfig() (opts *Config, err error) {
|
|
// Define defaults options
|
|
opts = &Config{
|
|
Bind: "127.0.0.1:8080",
|
|
WifiInterface: "wlan0",
|
|
HotspotInterface: "wlan1",
|
|
DHCPLeasesPath: "/var/lib/dhcp/dhcpd.leases",
|
|
ARPTablePath: "/proc/net/arp",
|
|
SyslogEnabled: false,
|
|
SyslogPath: "/var/log/messages",
|
|
SyslogFilter: []string{"daemon.info wpa_supplicant:", "daemon.info iwd:", "daemon.info hostapd:"},
|
|
SyslogSource: "iwd",
|
|
}
|
|
|
|
declareFlags(opts)
|
|
|
|
// Establish a list of possible configuration file locations
|
|
configLocations := []string{
|
|
"repeater.conf",
|
|
}
|
|
|
|
if home, err := os.UserConfigDir(); err == nil {
|
|
configLocations = append(configLocations, path.Join(home, "repeater", "repeater.conf"))
|
|
}
|
|
|
|
configLocations = append(configLocations, path.Join("etc", "repeater.conf"))
|
|
|
|
// If config file exists, read configuration from it
|
|
for _, filename := range configLocations {
|
|
if _, e := os.Stat(filename); !os.IsNotExist(e) {
|
|
log.Printf("Loading configuration from %s\n", filename)
|
|
err = parseFile(opts, filename)
|
|
if err != nil {
|
|
return
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// Then, overwrite that by what is present in the environment
|
|
err = parseEnvironmentVariables(opts)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Finaly, command line takes precedence
|
|
err = parseCLI(opts)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Validate configuration
|
|
if opts.WifiBackend != "iwd" && opts.WifiBackend != "wpasupplicant" {
|
|
log.Fatalf("wifi-backend must be set to 'iwd' or 'wpasupplicant' (got: '%s')", opts.WifiBackend)
|
|
}
|
|
|
|
if opts.StationBackend != "arp" && opts.StationBackend != "dhcp" && opts.StationBackend != "hostapd" {
|
|
log.Fatalf("station-backend must be set to 'arp', 'dhcp', or 'hostapd' (got: '%s')", opts.StationBackend)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// parseLine treats a config line and place the read value in the variable
|
|
// declared to the corresponding flag.
|
|
func parseLine(o *Config, line string) (err error) {
|
|
fields := strings.SplitN(line, "=", 2)
|
|
orig_key := strings.TrimSpace(fields[0])
|
|
value := strings.TrimSpace(fields[1])
|
|
|
|
key := strings.TrimPrefix(orig_key, "REPEATER_")
|
|
key = strings.Replace(key, "_", "-", -1)
|
|
key = strings.ToLower(key)
|
|
|
|
err = flag.Set(key, value)
|
|
|
|
return
|
|
}
|