Files
gotermix/internals/server.go
T

162 lines
5.0 KiB
Go
Raw Normal View History

package internals
import (
"crypto/tls"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"path/filepath"
"time"
)
// Run is the application entry point, called from main().
func Run() {
addr := flag.String("addr", "127.0.0.1:5000", "listen address")
nopw := flag.Bool("nopw", false, "disable password authentication")
setlogin := flag.String("setlogin", "", "set login username (next arg is password)")
certFlag := flag.String("cert", "", "set custom TLS certificate PEM file (stored encrypted)")
cetkeyFlag := flag.String("certkey", "", "set custom TLS private key PEM file")
certreset := flag.Bool("certreset", false, "remove stored custom certificate, revert to self-signed")
flag.Parse()
initialCwd, _ = os.Getwd()
// Credentials file lives next to the executable.
exe, err := os.Executable()
if err != nil {
exe = "."
}
credsPath = filepath.Join(filepath.Dir(exe), credsFilename)
// ── -certreset: remove stored cert paths ─────────────────────
if *certreset {
c := loadCreds()
c.CertFile = ""
c.KeyFile = ""
if err := saveCreds(c); err != nil {
fmt.Fprintf(os.Stderr, "error saving config: %v\n", err)
os.Exit(1)
}
fmt.Println("custom certificate removed — will use self-signed on next start")
os.Exit(0)
}
// ── -cert / -certkey: store custom cert paths (encrypted) ─────
if *certFlag != "" {
keyPath := *cetkeyFlag
if keyPath == "" {
keyPath = *certFlag // allow combined cert+key PEM
}
// Validate the files are readable and form a valid keypair before storing.
if _, err := tls.LoadX509KeyPair(*certFlag, keyPath); err != nil {
fmt.Fprintf(os.Stderr, "certificate error: %v\n", err)
os.Exit(1)
}
c := loadCreds()
c.CertFile = *certFlag
c.KeyFile = keyPath
if err := saveCreds(c); err != nil {
fmt.Fprintf(os.Stderr, "error saving config: %v\n", err)
os.Exit(1)
}
fmt.Println("custom certificate stored")
os.Exit(0)
}
// ── -setlogin username password ────────────────────────────────
if *setlogin != "" {
args := flag.Args()
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "usage: -setlogin <username> <password>")
os.Exit(1)
}
existing := loadCreds() // preserve cert paths across credential changes
salt := newSalt()
c := storedCreds{
Username: *setlogin,
Salt: salt,
Hash: hashPassword(args[0], salt),
CertFile: existing.CertFile,
KeyFile: existing.KeyFile,
}
if err := saveCreds(c); err != nil {
fmt.Fprintf(os.Stderr, "error saving credentials: %v\n", err)
os.Exit(1)
}
fmt.Printf("credentials saved user=%q file=%s\n", *setlogin, credsPath)
os.Exit(0)
}
nopwMode = *nopw
appCreds = loadCreds()
initAuthSecret()
if nopwMode {
fmt.Println("auth: DISABLED (-nopw)")
} else {
fmt.Printf("auth: enabled user=%q creds=%s\n", appCreds.Username, credsPath)
}
// Reap idle sessions.
go func() {
t := time.NewTicker(10 * time.Minute)
defer t.Stop()
for range t.C {
sessionsMu.Lock()
for id, s := range sessions {
s.mu.Lock()
idle := time.Since(s.lastSeen)
s.mu.Unlock()
if idle > sessionTTL {
s.ptty.Close()
delete(sessions, id)
}
}
sessionsMu.Unlock()
}
}()
// Load TLS certificate — custom (stored encrypted) or auto-generated.
// The cert path is never printed to avoid leaking it in logs or terminal history.
var tlsCert tls.Certificate
if appCreds.CertFile != "" && appCreds.KeyFile != "" {
if cert, err := tls.LoadX509KeyPair(appCreds.CertFile, appCreds.KeyFile); err == nil {
tlsCert = cert
fmt.Println("TLS: custom certificate")
} else {
fmt.Fprintln(os.Stderr, "TLS: custom certificate failed to load, falling back to self-signed")
tlsCert, _ = generateSelfSignedCert()
}
} else {
tlsCert, _ = generateSelfSignedCert()
fmt.Println("TLS: self-signed (auto-generated)")
}
mux := http.NewServeMux()
mux.HandleFunc("/", handleIndex)
mux.HandleFunc("/s/", handleShell)
mux.HandleFunc("/ws/", handleWS)
mux.HandleFunc("/auth", handleAuth)
mux.HandleFunc("/upload", handleUpload)
mux.HandleFunc("/download", handleDownload)
mux.HandleFunc("/favicon.svg", handleFavicon)
mux.HandleFunc("/static/app.css", handleStaticCSS)
mux.HandleFunc("/static/app.js", handleStaticJS)
ln, _ := net.Listen("tcp", *addr)
fmt.Printf("Go Web Shell https://%s\n", *addr)
fmt.Println(" / new session (URL stays clean)")
fmt.Println(" /s/<id> reconnect to a specific session")
// Suppress the noisy "TLS handshake error" lines that appear whenever a
// browser rejects the self-signed certificate during the initial TLS dance.
srv := &http.Server{
Handler: mux,
ErrorLog: log.New(tlsHandshakeFilter{log.Writer()}, "", log.LstdFlags),
}
srv.Serve(tls.NewListener(ln, &tls.Config{Certificates: []tls.Certificate{tlsCert}})) //nolint:errcheck
}