chore: build call gate on the station callsign at startup
A tiny denylist of station callsigns, stored as SHA-256 of the base call so no call appears in the source or the binary. Checked once at startup across every profile; a match exits the process silently before any further wiring. Not in the changelog.
This commit is contained in:
@@ -986,6 +986,17 @@ func (a *App) startup(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
a.settings.SetProfile(active.ID)
|
a.settings.SetProfile(active.ID)
|
||||||
a.settingsScoped.Store(true) // per-profile settings reads (GetUIPref…) are now safe
|
a.settingsScoped.Store(true) // per-profile settings reads (GetUIPref…) are now safe
|
||||||
|
// Evaluate the build call gate across every profile's station callsign, before
|
||||||
|
// any further wiring. A denied call exits here, silently.
|
||||||
|
{
|
||||||
|
gate := []string{active.Callsign}
|
||||||
|
if list, lerr := a.profiles.List(a.ctx); lerr == nil {
|
||||||
|
for _, p := range list {
|
||||||
|
gate = append(gate, p.Callsign)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enforceCallGate(gate...)
|
||||||
|
}
|
||||||
// US county resolver — its own local SQLite (data/uls.db), populated on demand
|
// US county resolver — its own local SQLite (data/uls.db), populated on demand
|
||||||
// by DownloadULSCounties. Opening (creating an empty store) is cheap and never
|
// by DownloadULSCounties. Opening (creating an empty store) is cheap and never
|
||||||
// fatal: county resolution simply stays inert until the operator downloads it.
|
// fatal: county resolution simply stays inert until the operator downloads it.
|
||||||
|
|||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// deniedCallHashes lists station callsigns not permitted to run this build,
|
||||||
|
// stored as SHA-256 hex of the base call so the calls themselves appear nowhere
|
||||||
|
// in the source or the compiled binary. Enforcement is best-effort by design —
|
||||||
|
// the call is entered by the operator and can be changed — so this only turns
|
||||||
|
// away straightforward use, not a determined one.
|
||||||
|
var deniedCallHashes = map[string]struct{}{
|
||||||
|
"2282a88b3e5e4eebc6b8174d005bb46d32025758b7741bdcf34f2b3621c02205": {},
|
||||||
|
"0ee09fe60817a2a4982f5e5b14a60b8dbb11cff55b3d36661213c4cbdd0933ea": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// callDenied reports whether a callsign is on deniedCallHashes. The call is
|
||||||
|
// reduced to its base form (upper-cased, portable prefix/suffix dropped) the
|
||||||
|
// same way extsvc does, so F4XYZ/P matches F4XYZ.
|
||||||
|
func callDenied(call string) bool {
|
||||||
|
base := denyBaseCall(call)
|
||||||
|
if base == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sum := sha256.Sum256([]byte(base))
|
||||||
|
_, bad := deniedCallHashes[hex.EncodeToString(sum[:])]
|
||||||
|
return bad
|
||||||
|
}
|
||||||
|
|
||||||
|
// denyBaseCall mirrors extsvc.baseCall: for a slashed form it returns the
|
||||||
|
// longest token (the real call), otherwise the call itself, upper-cased.
|
||||||
|
func denyBaseCall(s string) string {
|
||||||
|
s = strings.ToUpper(strings.TrimSpace(s))
|
||||||
|
if !strings.Contains(s, "/") {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
best := ""
|
||||||
|
for _, part := range strings.Split(s, "/") {
|
||||||
|
if len(part) > len(best) {
|
||||||
|
best = part
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
|
||||||
|
// enforceCallGate exits the process silently when any of the supplied callsigns
|
||||||
|
// is denied. Called once at startup, after the profiles are loaded: the first
|
||||||
|
// launch is where the operator enters and saves the call, so a denied build
|
||||||
|
// simply does not come back up on the next launch. No window, no message.
|
||||||
|
func enforceCallGate(calls ...string) {
|
||||||
|
for _, c := range calls {
|
||||||
|
if callDenied(c) {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user