package cli import ( "fmt" "strconv" "strings" "debtik/internal/auth" "debtik/internal/vpn" ) // ==================== PPP (/ppp) ==================== func (e *Executor) handlePPP(parts []string, user *auth.PublicUser) string { if len(parts) == 0 { return ColorRed + "Usage: /ppp [profile|secret|active]\n" + ColorReset } sub := strings.ToLower(parts[0]) switch sub { case "profile": return e.handlePPPProfile(parts[1:], user) case "secret": return e.handlePPPSecret(parts[1:], user) case "active": return e.handlePPPActive(parts[1:], user) default: return fmt.Sprintf(ColorRed+"Unknown ppp command %q. Usage: /ppp [profile|secret|active]\n"+ColorReset, sub) } } // ----------------- /ppp profile ----------------- func (e *Executor) handlePPPProfile(parts []string, user *auth.PublicUser) string { if e.vpnMgr == nil || e.vpnMgr.PPPServer == nil { return ColorRed + "PPP server subsystem not available.\n" + ColorReset } mgr := e.vpnMgr.PPPServer action := "print" if len(parts) > 0 { action = strings.ToLower(parts[0]) parts = parts[1:] } pos, params := parseArgs(parts) switch action { case "print", "pr", "show", "list": list := mgr.ListProfiles() tbl := NewTable("#", "NAME", "LOCAL-ADDRESS", "REMOTE-ADDRESS", "DNS-SERVERS", "ENCRYPTION", "ONLY-ONE", "COMMENT") for i, p := range list { tbl.AddRow( strconv.Itoa(i), p.Name, p.LocalAddress, p.RemoteAddress, p.DNSServers, p.UseEncryption, strconv.FormatBool(p.OnlyOne), p.Comment, ) } return tbl.String() case "add": if user != nil && user.Role == auth.RoleRead { return ColorRed + "Permission denied: viewer cannot modify profiles\n" + ColorReset } name := params["name"] if name == "" { if len(pos) > 0 { name = pos[0] } else { return ColorRed + "Syntax error: name is required. Usage: /ppp profile add name= [local-address=...] [remote-address=...]\n" + ColorReset } } p := vpn.PPPProfile{ ID: name, Name: name, LocalAddress: params["local-address"], RemoteAddress: params["remote-address"], DNSServers: params["dns-server"], UseEncryption: params["use-encryption"], Comment: params["comment"], } if p.DNSServers == "" { p.DNSServers = params["dns-servers"] } if p.LocalAddress == "" { p.LocalAddress = "10.10.10.1" } if p.RemoteAddress == "" { p.RemoteAddress = "10.10.10.10-10.10.10.50" } if p.UseEncryption == "" { p.UseEncryption = "no" } if val, ok := params["only-one"]; ok { p.OnlyOne = val == "yes" || val == "true" } else { p.OnlyOne = true } if err := mgr.SaveProfile(&p); err != nil { return ColorRed + "Error adding profile: " + err.Error() + "\n" + ColorReset } return ColorGreen + fmt.Sprintf("PPP profile %q created successfully.\n", name) + ColorReset case "set": if user != nil && user.Role == auth.RoleRead { return ColorRed + "Permission denied: viewer cannot modify profiles\n" + ColorReset } if len(pos) == 0 { return ColorRed + "Syntax error: target profile name or index required. Usage: /ppp profile set [params...]\n" + ColorReset } target := pos[0] prof := mgr.GetProfile(target) if prof == nil { // Check if numeric index was provided if idx, err := strconv.Atoi(target); err == nil { all := mgr.ListProfiles() if idx >= 0 && idx < len(all) { prof = all[idx] } } } if prof == nil { return ColorRed + fmt.Sprintf("Profile %q not found.\n", target) + ColorReset } if val, ok := params["local-address"]; ok { prof.LocalAddress = val } if val, ok := params["remote-address"]; ok { prof.RemoteAddress = val } if val, ok := params["dns-server"]; ok { prof.DNSServers = val } if val, ok := params["dns-servers"]; ok { prof.DNSServers = val } if val, ok := params["use-encryption"]; ok { prof.UseEncryption = val } if val, ok := params["only-one"]; ok { prof.OnlyOne = val == "yes" || val == "true" } if val, ok := params["comment"]; ok { prof.Comment = val } if err := mgr.SaveProfile(prof); err != nil { return ColorRed + "Error updating profile: " + err.Error() + "\n" + ColorReset } return ColorGreen + fmt.Sprintf("PPP profile %q updated successfully.\n", prof.Name) + ColorReset case "remove", "delete", "rem": if user != nil && user.Role == auth.RoleRead { return ColorRed + "Permission denied: viewer cannot delete profiles\n" + ColorReset } if len(pos) == 0 { return ColorRed + "Syntax error: target profile name or index required. Usage: /ppp profile remove \n" + ColorReset } target := pos[0] if prof := mgr.GetProfile(target); prof == nil { if idx, err := strconv.Atoi(target); err == nil { all := mgr.ListProfiles() if idx >= 0 && idx < len(all) { target = all[idx].Name } } } if err := mgr.DeleteProfile(target); err != nil { return ColorRed + "Error deleting profile: " + err.Error() + "\n" + ColorReset } return ColorGreen + fmt.Sprintf("PPP profile %q removed successfully.\n", target) + ColorReset default: return ColorRed + fmt.Sprintf("Unknown action %q. Usage: /ppp profile [print|add|set|remove]\n", action) + ColorReset } } // ----------------- /ppp secret ----------------- func (e *Executor) handlePPPSecret(parts []string, user *auth.PublicUser) string { if e.vpnMgr == nil || e.vpnMgr.PPPServer == nil { return ColorRed + "PPP server subsystem not available.\n" + ColorReset } mgr := e.vpnMgr.PPPServer action := "print" if len(parts) > 0 { action = strings.ToLower(parts[0]) parts = parts[1:] } pos, params := parseArgs(parts) switch action { case "print", "pr", "show", "list": list := mgr.ListSecrets() tbl := NewTable("#", "FLAGS", "NAME", "SERVICE", "PROFILE", "LOCAL-ADDRESS", "REMOTE-ADDRESS", "CALLER-ID", "COMMENT") for i, s := range list { flags := " " if s.Disabled { flags = "X" } tbl.AddRow( strconv.Itoa(i), flags, s.Name, s.Service, s.Profile, s.LocalAddress, s.RemoteAddress, s.CallerID, s.Comment, ) } return tbl.String() case "add": if user != nil && user.Role == auth.RoleRead { return ColorRed + "Permission denied: viewer cannot add secrets\n" + ColorReset } name := params["name"] pass := params["password"] if name == "" || pass == "" { return ColorRed + "Syntax error: name and password are required. Usage: /ppp secret add name= password= [profile=...] [service=any|l2tp|pptp|pppoe]\n" + ColorReset } sec := vpn.PPPSecret{ Name: name, Password: pass, Service: params["service"], Profile: params["profile"], LocalAddress: params["local-address"], RemoteAddress: params["remote-address"], CallerID: params["caller-id"], Comment: params["comment"], } if sec.Service == "" { sec.Service = "any" } if sec.Profile == "" { sec.Profile = "default" } if val, ok := params["disabled"]; ok { sec.Disabled = val == "yes" || val == "true" } if err := mgr.SaveSecret(&sec); err != nil { return ColorRed + "Error adding secret: " + err.Error() + "\n" + ColorReset } return ColorGreen + fmt.Sprintf("PPP secret %q created successfully.\n", name) + ColorReset case "set": if user != nil && user.Role == auth.RoleRead { return ColorRed + "Permission denied: viewer cannot modify secrets\n" + ColorReset } if len(pos) == 0 { return ColorRed + "Syntax error: target secret name or index required. Usage: /ppp secret set [params...]\n" + ColorReset } target := pos[0] sec := findSecret(mgr, target) if sec == nil { return ColorRed + fmt.Sprintf("Secret %q not found.\n", target) + ColorReset } if val, ok := params["password"]; ok { sec.Password = val } if val, ok := params["profile"]; ok { sec.Profile = val } if val, ok := params["service"]; ok { sec.Service = val } if val, ok := params["local-address"]; ok { sec.LocalAddress = val } if val, ok := params["remote-address"]; ok { sec.RemoteAddress = val } if val, ok := params["caller-id"]; ok { sec.CallerID = val } if val, ok := params["comment"]; ok { sec.Comment = val } if val, ok := params["disabled"]; ok { sec.Disabled = val == "yes" || val == "true" } if err := mgr.SaveSecret(sec); err != nil { return ColorRed + "Error updating secret: " + err.Error() + "\n" + ColorReset } return ColorGreen + fmt.Sprintf("PPP secret %q updated successfully.\n", sec.Name) + ColorReset case "remove", "delete", "rem": if user != nil && user.Role == auth.RoleRead { return ColorRed + "Permission denied: viewer cannot delete secrets\n" + ColorReset } if len(pos) == 0 { return ColorRed + "Syntax error: target secret name or index required. Usage: /ppp secret remove \n" + ColorReset } sec := findSecret(mgr, pos[0]) if sec == nil { return ColorRed + fmt.Sprintf("Secret %q not found.\n", pos[0]) + ColorReset } if err := mgr.DeleteSecret(sec.ID); err != nil { return ColorRed + "Error deleting secret: " + err.Error() + "\n" + ColorReset } return ColorGreen + fmt.Sprintf("PPP secret %q removed successfully.\n", sec.Name) + ColorReset case "enable": if user != nil && user.Role == auth.RoleRead { return ColorRed + "Permission denied\n" + ColorReset } if len(pos) == 0 { return ColorRed + "Usage: /ppp secret enable \n" + ColorReset } sec := findSecret(mgr, pos[0]) if sec == nil { return ColorRed + fmt.Sprintf("Secret %q not found.\n", pos[0]) + ColorReset } if err := mgr.SetSecretState(sec.ID, false); err != nil { return ColorRed + err.Error() + "\n" + ColorReset } return ColorGreen + fmt.Sprintf("PPP secret %q enabled.\n", sec.Name) + ColorReset case "disable": if user != nil && user.Role == auth.RoleRead { return ColorRed + "Permission denied\n" + ColorReset } if len(pos) == 0 { return ColorRed + "Usage: /ppp secret disable \n" + ColorReset } sec := findSecret(mgr, pos[0]) if sec == nil { return ColorRed + fmt.Sprintf("Secret %q not found.\n", pos[0]) + ColorReset } if err := mgr.SetSecretState(sec.ID, true); err != nil { return ColorRed + err.Error() + "\n" + ColorReset } return ColorGreen + fmt.Sprintf("PPP secret %q disabled.\n", sec.Name) + ColorReset default: return ColorRed + fmt.Sprintf("Unknown action %q. Usage: /ppp secret [print|add|set|remove|enable|disable]\n", action) + ColorReset } } func findSecret(mgr *vpn.PPPServerManager, target string) *vpn.PPPSecret { list := mgr.ListSecrets() for _, s := range list { if s.ID == target || s.Name == target { return s } } if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(list) { return list[idx] } return nil } // ----------------- /ppp active ----------------- func (e *Executor) handlePPPActive(parts []string, user *auth.PublicUser) string { if e.vpnMgr == nil || e.vpnMgr.PPPServer == nil { return ColorRed + "PPP server subsystem not available.\n" + ColorReset } mgr := e.vpnMgr.PPPServer action := "print" if len(parts) > 0 { action = strings.ToLower(parts[0]) parts = parts[1:] } pos, _ := parseArgs(parts) switch action { case "print", "pr", "show", "list": list := mgr.ListActiveSessions() tbl := NewTable("#", "INTERFACE", "USER", "SERVICE", "CALLER-ID", "ADDRESS", "UPTIME", "RX", "TX") for i, s := range list { tbl.AddRow( strconv.Itoa(i), s.Interface, s.Username, s.Service, s.CallerID, s.Address, s.Uptime, s.RxHuman, s.TxHuman, ) } return tbl.String() case "remove", "disconnect", "kill": if user != nil && user.Role == auth.RoleRead { return ColorRed + "Permission denied\n" + ColorReset } if len(pos) == 0 { return ColorRed + "Usage: /ppp active remove \n" + ColorReset } iface := pos[0] if err := mgr.DisconnectSession(iface); err != nil { return ColorRed + "Error disconnecting session: " + err.Error() + "\n" + ColorReset } return ColorGreen + fmt.Sprintf("Session on %s disconnected.\n", iface) + ColorReset default: return ColorRed + fmt.Sprintf("Unknown action %q. Usage: /ppp active [print|remove ]\n", action) + ColorReset } } // ==================== INTERFACE VPN SERVERS ==================== // ----------------- /interface l2tp-server server ----------------- func (e *Executor) handleInterfaceL2TPServer(parts []string, user *auth.PublicUser) string { if e.vpnMgr == nil || e.vpnMgr.PPPServer == nil { return ColorRed + "VPN server subsystem not available.\n" + ColorReset } mgr := e.vpnMgr.PPPServer // Skip optional "server" subkeyword if present (e.g. /interface l2tp-server server print) if len(parts) > 0 && strings.ToLower(parts[0]) == "server" { parts = parts[1:] } action := "print" if len(parts) > 0 { action = strings.ToLower(parts[0]) parts = parts[1:] } _, params := parseArgs(parts) switch action { case "print", "pr", "show": cfg := mgr.GetL2TPServer() tbl := NewTable("ENABLED", "PORT", "DEFAULT-PROFILE", "USE-IPSEC", "RUNNING", "ACTIVE-SESSIONS", "LAST-ERROR") tbl.AddRow( strconv.FormatBool(cfg.Enabled), strconv.Itoa(cfg.Port), cfg.DefaultProfile, cfg.UseIPSec, strconv.FormatBool(cfg.Running), strconv.Itoa(cfg.ActiveSessions), cfg.LastError, ) return tbl.String() case "set": if user != nil && user.Role == auth.RoleRead { return ColorRed + "Permission denied\n" + ColorReset } cfg := mgr.GetL2TPServer() if val, ok := params["enabled"]; ok { cfg.Enabled = val == "yes" || val == "true" } if val, ok := params["port"]; ok { if p, err := strconv.Atoi(val); err == nil && p > 0 { cfg.Port = p } } if val, ok := params["default-profile"]; ok { cfg.DefaultProfile = val } if val, ok := params["use-ipsec"]; ok { cfg.UseIPSec = val } if val, ok := params["ipsec-secret"]; ok { cfg.IPSecSecret = val } if err := mgr.SetL2TPServer(cfg); err != nil { return ColorRed + "Error applying L2TP server: " + err.Error() + "\n" + ColorReset } return ColorGreen + "L2TP server configuration updated successfully.\n" + ColorReset default: return ColorRed + fmt.Sprintf("Unknown action %q. Usage: /interface l2tp-server server [print|set enabled=yes|no default-profile=...]\n", action) + ColorReset } } // ----------------- /interface pptp-server server ----------------- func (e *Executor) handleInterfacePPTPServer(parts []string, user *auth.PublicUser) string { if e.vpnMgr == nil || e.vpnMgr.PPPServer == nil { return ColorRed + "VPN server subsystem not available.\n" + ColorReset } mgr := e.vpnMgr.PPPServer if len(parts) > 0 && strings.ToLower(parts[0]) == "server" { parts = parts[1:] } action := "print" if len(parts) > 0 { action = strings.ToLower(parts[0]) parts = parts[1:] } _, params := parseArgs(parts) switch action { case "print", "pr", "show": cfg := mgr.GetPPTPServer() tbl := NewTable("ENABLED", "PORT", "DEFAULT-PROFILE", "REQUIRE-MPPE", "RUNNING", "ACTIVE-SESSIONS", "LAST-ERROR") tbl.AddRow( strconv.FormatBool(cfg.Enabled), strconv.Itoa(cfg.Port), cfg.DefaultProfile, strconv.FormatBool(cfg.RequireMPPE), strconv.FormatBool(cfg.Running), strconv.Itoa(cfg.ActiveSessions), cfg.LastError, ) return tbl.String() case "set": if user != nil && user.Role == auth.RoleRead { return ColorRed + "Permission denied\n" + ColorReset } cfg := mgr.GetPPTPServer() if val, ok := params["enabled"]; ok { cfg.Enabled = val == "yes" || val == "true" } if val, ok := params["port"]; ok { if p, err := strconv.Atoi(val); err == nil && p > 0 { cfg.Port = p } } if val, ok := params["default-profile"]; ok { cfg.DefaultProfile = val } if val, ok := params["require-mppe"]; ok { cfg.RequireMPPE = val == "yes" || val == "true" } if err := mgr.SetPPTPServer(cfg); err != nil { return ColorRed + "Error applying PPTP server: " + err.Error() + "\n" + ColorReset } return ColorGreen + "PPTP server configuration updated successfully.\n" + ColorReset default: return ColorRed + fmt.Sprintf("Unknown action %q. Usage: /interface pptp-server server [print|set enabled=yes|no default-profile=...]\n", action) + ColorReset } } // ----------------- /interface pppoe-server server ----------------- func (e *Executor) handleInterfacePPPoEServer(parts []string, user *auth.PublicUser) string { if e.vpnMgr == nil || e.vpnMgr.PPPServer == nil { return ColorRed + "VPN server subsystem not available.\n" + ColorReset } mgr := e.vpnMgr.PPPServer if len(parts) > 0 && strings.ToLower(parts[0]) == "server" { parts = parts[1:] } action := "print" if len(parts) > 0 { action = strings.ToLower(parts[0]) parts = parts[1:] } _, params := parseArgs(parts) switch action { case "print", "pr", "show": cfg := mgr.GetPPPoEServer() tbl := NewTable("ENABLED", "SERVICE-NAME", "INTERFACE", "DEFAULT-PROFILE", "ONE-SESSION-PER-HOST", "RUNNING", "ACTIVE-SESSIONS", "LAST-ERROR") tbl.AddRow( strconv.FormatBool(cfg.Enabled), cfg.ServiceName, cfg.Interface, cfg.DefaultProfile, strconv.FormatBool(cfg.OneSessionPerHost), strconv.FormatBool(cfg.Running), strconv.Itoa(cfg.ActiveSessions), cfg.LastError, ) return tbl.String() case "set": if user != nil && user.Role == auth.RoleRead { return ColorRed + "Permission denied\n" + ColorReset } cfg := mgr.GetPPPoEServer() if val, ok := params["enabled"]; ok { cfg.Enabled = val == "yes" || val == "true" } if val, ok := params["service-name"]; ok { cfg.ServiceName = val } if val, ok := params["interface"]; ok { cfg.Interface = val } if val, ok := params["default-profile"]; ok { cfg.DefaultProfile = val } if val, ok := params["one-session-per-host"]; ok { cfg.OneSessionPerHost = val == "yes" || val == "true" } if val, ok := params["max-mtu"]; ok { if m, err := strconv.Atoi(val); err == nil && m > 0 { cfg.MaxMTU = m } } if val, ok := params["max-mru"]; ok { if m, err := strconv.Atoi(val); err == nil && m > 0 { cfg.MaxMRU = m } } if err := mgr.SetPPPoEServer(cfg); err != nil { return ColorRed + "Error applying PPPoE server: " + err.Error() + "\n" + ColorReset } return ColorGreen + "PPPoE server configuration updated successfully.\n" + ColorReset default: return ColorRed + fmt.Sprintf("Unknown action %q. Usage: /interface pppoe-server server [print|set enabled=yes|no interface=... default-profile=...]\n", action) + ColorReset } }