executor.go 78 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510
  1. package cli
  2. import (
  3. "context"
  4. "fmt"
  5. "net"
  6. "os/exec"
  7. "sort"
  8. "strconv"
  9. "strings"
  10. "time"
  11. "proxmox-router/internal/auth"
  12. "proxmox-router/internal/bridge"
  13. "proxmox-router/internal/bwtest"
  14. "proxmox-router/internal/certs"
  15. "proxmox-router/internal/dhcp"
  16. "proxmox-router/internal/dns"
  17. "proxmox-router/internal/dynamicrouting"
  18. "proxmox-router/internal/firewall"
  19. "proxmox-router/internal/logging"
  20. "proxmox-router/internal/network"
  21. "proxmox-router/internal/routing"
  22. "proxmox-router/internal/services"
  23. "proxmox-router/internal/system"
  24. "proxmox-router/internal/vpn"
  25. "proxmox-router/internal/vrrp"
  26. )
  27. type Executor struct {
  28. authMgr *auth.Manager
  29. netMgr *network.Manager
  30. routingMgr *routing.RoutingManager
  31. dynRoutingMgr *dynamicrouting.Manager
  32. fwMgr *firewall.FirewallManager
  33. dhcpMgr *dhcp.Manager
  34. dhcpServMgr *dhcp.ServerManager
  35. dnsMgr *dns.Manager
  36. bridgeMgr *bridge.BridgeManager
  37. vpnMgr *vpn.Manager
  38. vrrpMgr *vrrp.Manager
  39. bwtestMgr *bwtest.Manager
  40. servicesMgr *services.Manager
  41. certMgr *certs.CertManager
  42. }
  43. func NewExecutor(
  44. authMgr *auth.Manager,
  45. netMgr *network.Manager,
  46. routingMgr *routing.RoutingManager,
  47. dynRoutingMgr *dynamicrouting.Manager,
  48. fwMgr *firewall.FirewallManager,
  49. dhcpMgr *dhcp.Manager,
  50. dhcpServMgr *dhcp.ServerManager,
  51. dnsMgr *dns.Manager,
  52. bridgeMgr *bridge.BridgeManager,
  53. vpnMgr *vpn.Manager,
  54. vrrpMgr *vrrp.Manager,
  55. bwtestMgr *bwtest.Manager,
  56. servicesMgr *services.Manager,
  57. certMgr *certs.CertManager,
  58. ) *Executor {
  59. return &Executor{
  60. authMgr: authMgr,
  61. netMgr: netMgr,
  62. routingMgr: routingMgr,
  63. dynRoutingMgr: dynRoutingMgr,
  64. fwMgr: fwMgr,
  65. dhcpMgr: dhcpMgr,
  66. dhcpServMgr: dhcpServMgr,
  67. dnsMgr: dnsMgr,
  68. bridgeMgr: bridgeMgr,
  69. vpnMgr: vpnMgr,
  70. vrrpMgr: vrrpMgr,
  71. bwtestMgr: bwtestMgr,
  72. servicesMgr: servicesMgr,
  73. certMgr: certMgr,
  74. }
  75. }
  76. func (e *Executor) Execute(rawCmd string, user *auth.PublicUser) (string, bool) {
  77. cmd := strings.TrimSpace(rawCmd)
  78. if cmd == "" {
  79. return "", false
  80. }
  81. cmd = strings.TrimPrefix(cmd, "/")
  82. parts := strings.Fields(cmd)
  83. if len(parts) == 0 {
  84. return "", false
  85. }
  86. mainCmd := strings.ToLower(parts[0])
  87. switch mainCmd {
  88. case "quit", "exit":
  89. return ColorYellow + "Connection closed. Goodbye!\n" + ColorReset, true
  90. case "help", "?":
  91. return e.showHelp(), false
  92. case "clear":
  93. return "\033[2J\033[H", false
  94. case "ping":
  95. return e.handlePing(parts[1:]), false
  96. case "traceroute":
  97. return e.handleTraceroute(parts[1:]), false
  98. case "log":
  99. return e.handleLog(parts[1:]), false
  100. case "system":
  101. return e.handleSystem(parts[1:], user), false
  102. case "interface":
  103. return e.handleInterface(parts[1:], user), false
  104. case "ip":
  105. return e.handleIP(parts[1:], user), false
  106. case "routing":
  107. return e.handleRouting(parts[1:], user), false
  108. case "tool":
  109. return e.handleTool(parts[1:]), false
  110. case "user":
  111. return e.handleUser(parts[1:], user), false
  112. case "certificate", "certificates":
  113. return e.handleCertificate(parts[1:], user), false
  114. default:
  115. return fmt.Sprintf(ColorRed+"Unknown command or syntax error: %q. Type 'help' or '?' for command list.\n"+ColorReset, cmd), false
  116. }
  117. }
  118. func parseArgs(parts []string) ([]string, map[string]string) {
  119. var pos []string
  120. params := make(map[string]string)
  121. for _, p := range parts {
  122. if idx := strings.Index(p, "="); idx != -1 {
  123. k := strings.ToLower(p[:idx])
  124. v := p[idx+1:]
  125. params[k] = v
  126. } else {
  127. pos = append(pos, p)
  128. }
  129. }
  130. return pos, params
  131. }
  132. // ==================== LOGGING ====================
  133. func (e *Executor) handleLog(parts []string) string {
  134. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" || parts[0] == "show" {
  135. limit := 50
  136. filter := ""
  137. _, params := parseArgs(parts[1:])
  138. if lStr, ok := params["limit"]; ok {
  139. if lVal, err := strconv.Atoi(lStr); err == nil && lVal > 0 {
  140. limit = lVal
  141. }
  142. }
  143. if fStr, ok := params["where"]; ok {
  144. filter = fStr
  145. } else if fStr, ok := params["filter"]; ok {
  146. filter = fStr
  147. }
  148. logger := logging.GetLogger()
  149. entries := logger.List(limit, filter)
  150. if len(entries) == 0 {
  151. return ColorYellow + "No log records found.\n" + ColorReset
  152. }
  153. tbl := NewTable("#", "TIME", "TOPICS", "MESSAGE")
  154. for _, logEntry := range entries {
  155. timeStr := logEntry.Time.Format("Jan/02 15:04:05")
  156. tbl.AddRow(
  157. fmt.Sprintf("%d", logEntry.ID),
  158. timeStr,
  159. logEntry.Topics,
  160. logEntry.Message,
  161. )
  162. }
  163. return tbl.String()
  164. }
  165. return ColorRed + "Usage: /log print [limit=N] [where=filter]\n" + ColorReset
  166. }
  167. // ==================== INTERFACES ====================
  168. func (e *Executor) handleInterface(parts []string, user *auth.PublicUser) string {
  169. if len(parts) == 0 {
  170. return e.printAllInterfaces()
  171. }
  172. sub := strings.ToLower(parts[0])
  173. switch sub {
  174. case "print", "pr", "show", "list":
  175. return e.printAllInterfaces()
  176. case "vlan":
  177. return e.handleInterfaceVLAN(parts[1:], user)
  178. case "bridge":
  179. return e.handleInterfaceBridge(parts[1:], user)
  180. case "wireguard":
  181. return e.handleInterfaceWireGuard(parts[1:], user)
  182. case "eoip":
  183. return e.handleInterfaceEoIP(parts[1:], user)
  184. case "vrrp":
  185. return e.handleInterfaceVRRP(parts[1:], user)
  186. case "pppoe-client":
  187. return e.handleInterfacePPPoE(parts[1:], user)
  188. case "enable":
  189. if len(parts) < 2 {
  190. return ColorRed + "Usage: /interface enable <name>\n" + ColorReset
  191. }
  192. if user.Role == auth.RoleRead {
  193. return ColorRed + "Permission denied: viewer role cannot modify interfaces\n" + ColorReset
  194. }
  195. name := parts[1]
  196. if err := e.netMgr.SetInterfaceState(name, true); err != nil {
  197. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  198. }
  199. return ColorGreen + fmt.Sprintf("Interface %s enabled successfully.\n", name) + ColorReset
  200. case "disable":
  201. if len(parts) < 2 {
  202. return ColorRed + "Usage: /interface disable <name>\n" + ColorReset
  203. }
  204. if user.Role == auth.RoleRead {
  205. return ColorRed + "Permission denied: viewer role cannot modify interfaces\n" + ColorReset
  206. }
  207. name := parts[1]
  208. if err := e.netMgr.SetInterfaceState(name, false); err != nil {
  209. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  210. }
  211. return ColorGreen + fmt.Sprintf("Interface %s disabled successfully.\n", name) + ColorReset
  212. case "set":
  213. if len(parts) < 2 {
  214. return ColorRed + "Usage: /interface set <name> mtu=<val>\n" + ColorReset
  215. }
  216. if user.Role == auth.RoleRead {
  217. return ColorRed + "Permission denied: viewer role cannot modify interfaces\n" + ColorReset
  218. }
  219. name := parts[1]
  220. _, params := parseArgs(parts[2:])
  221. if mtuStr, ok := params["mtu"]; ok {
  222. mtu, err := strconv.Atoi(mtuStr)
  223. if err != nil || mtu < 68 || mtu > 9000 {
  224. return ColorRed + "Invalid MTU value (68-9000)\n" + ColorReset
  225. }
  226. if err := e.netMgr.SetInterfaceMTU(name, mtu); err != nil {
  227. return ColorRed + "Error setting MTU: " + err.Error() + ColorReset + "\n"
  228. }
  229. return ColorGreen + fmt.Sprintf("Interface %s MTU updated to %d.\n", name, mtu) + ColorReset
  230. }
  231. return ColorRed + "Usage: /interface set <name> mtu=<val>\n" + ColorReset
  232. default:
  233. return ColorRed + "Unknown interface subcommand: " + sub + "\n" + ColorReset
  234. }
  235. }
  236. func (e *Executor) printAllInterfaces() string {
  237. ifaces, err := e.netMgr.ListInterfaces()
  238. if err != nil {
  239. return ColorRed + "Error listing interfaces: " + err.Error() + ColorReset + "\n"
  240. }
  241. tbl := NewTable("FLAGS", "NAME", "TYPE", "STATE", "MTU", "MAC ADDRESS", "IP ADDRESSES")
  242. for _, iface := range ifaces {
  243. isUp := strings.ToUpper(iface.State) == "UP"
  244. flags := ""
  245. if isUp {
  246. flags += "R"
  247. } else {
  248. flags += "X"
  249. }
  250. stateStr := ColorGreen + "UP" + ColorReset
  251. if !isUp {
  252. stateStr = ColorRed + "DOWN" + ColorReset
  253. }
  254. allIPs := append([]string{}, iface.IPv4...)
  255. allIPs = append(allIPs, iface.IPv6...)
  256. ips := strings.Join(allIPs, ", ")
  257. if ips == "" {
  258. ips = "-"
  259. }
  260. tbl.AddRow(flags, iface.Name, string(iface.Type), stateStr, fmt.Sprintf("%d", iface.MTU), iface.MAC, ips)
  261. }
  262. return tbl.String()
  263. }
  264. // --- VLAN ---
  265. func (e *Executor) handleInterfaceVLAN(parts []string, user *auth.PublicUser) string {
  266. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  267. vlans, err := e.netMgr.ListVLANs()
  268. if err != nil {
  269. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  270. }
  271. tbl := NewTable("#", "NAME", "VLAN-ID", "INTERFACE", "MTU", "STATE")
  272. for idx, v := range vlans {
  273. isUp := strings.ToUpper(v.State) == "UP"
  274. st := ColorGreen + "UP" + ColorReset
  275. if !isUp {
  276. st = ColorRed + "DOWN" + ColorReset
  277. }
  278. tbl.AddRow(fmt.Sprintf("%d", idx), v.Name, fmt.Sprintf("%d", v.VlanID), v.ParentName, fmt.Sprintf("%d", v.MTU), st)
  279. }
  280. return tbl.String()
  281. }
  282. sub := strings.ToLower(parts[0])
  283. if user.Role == auth.RoleRead {
  284. return ColorRed + "Permission denied: viewer role cannot modify VLANs\n" + ColorReset
  285. }
  286. switch sub {
  287. case "add":
  288. _, params := parseArgs(parts[1:])
  289. name := params["name"]
  290. parent := params["interface"]
  291. vlanIDStr := params["vlan-id"]
  292. if vlanIDStr == "" {
  293. vlanIDStr = params["vlan_id"]
  294. }
  295. vlanID, err := strconv.Atoi(vlanIDStr)
  296. if err != nil || vlanID < 1 || vlanID > 4094 {
  297. return ColorRed + "Invalid vlan-id (1-4094)\n" + ColorReset
  298. }
  299. if parent == "" {
  300. return ColorRed + "Missing parent interface (interface=...)\n" + ColorReset
  301. }
  302. if name == "" {
  303. name = fmt.Sprintf("%s.%d", parent, vlanID)
  304. }
  305. req := network.CreateVLANRequest{
  306. ParentName: parent,
  307. VlanID: vlanID,
  308. Name: name,
  309. AutoUp: true,
  310. }
  311. if _, err := e.netMgr.CreateVLAN(req); err != nil {
  312. return ColorRed + "Error creating VLAN: " + err.Error() + ColorReset + "\n"
  313. }
  314. return ColorGreen + fmt.Sprintf("VLAN interface %s created.\n", name) + ColorReset
  315. case "remove", "rem", "delete":
  316. pos, _ := parseArgs(parts[1:])
  317. if len(pos) == 0 {
  318. return ColorRed + "Usage: /interface vlan remove <name>\n" + ColorReset
  319. }
  320. name := pos[0]
  321. if err := e.netMgr.DeleteVLAN(name); err != nil {
  322. return ColorRed + "Error deleting VLAN: " + err.Error() + ColorReset + "\n"
  323. }
  324. return ColorGreen + fmt.Sprintf("VLAN %s removed.\n", name) + ColorReset
  325. default:
  326. return ColorRed + "Usage: /interface vlan [print|add|remove]\n" + ColorReset
  327. }
  328. }
  329. // --- Bridge ---
  330. func (e *Executor) handleInterfaceBridge(parts []string, user *auth.PublicUser) string {
  331. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  332. if e.bridgeMgr == nil {
  333. return ColorRed + "Bridge manager not available.\n" + ColorReset
  334. }
  335. bridges := e.bridgeMgr.ListBridges()
  336. tbl := NewTable("FLAGS", "NAME", "MTU", "MAC-ADDRESS", "RUNNING", "PORTS")
  337. for _, b := range bridges {
  338. flags := ""
  339. if b.Disabled {
  340. flags += "X"
  341. } else {
  342. flags += "R"
  343. }
  344. tbl.AddRow(flags, b.Name, fmt.Sprintf("%d", b.MTU), b.MACAddress, fmt.Sprintf("%t", b.Running), fmt.Sprintf("%d", b.PortsCount))
  345. }
  346. return tbl.String()
  347. }
  348. sub := strings.ToLower(parts[0])
  349. if sub == "port" {
  350. return e.handleBridgePort(parts[1:], user)
  351. }
  352. if user.Role == auth.RoleRead {
  353. return ColorRed + "Permission denied: viewer role cannot modify bridge\n" + ColorReset
  354. }
  355. switch sub {
  356. case "add":
  357. _, params := parseArgs(parts[1:])
  358. name := params["name"]
  359. if name == "" {
  360. return ColorRed + "Usage: /interface bridge add name=<name>\n" + ColorReset
  361. }
  362. b := bridge.BridgeInstance{
  363. Name: name,
  364. Disabled: false,
  365. }
  366. if err := e.bridgeMgr.SaveBridge(b); err != nil {
  367. return ColorRed + "Error adding bridge: " + err.Error() + ColorReset + "\n"
  368. }
  369. return ColorGreen + fmt.Sprintf("Bridge %s added.\n", name) + ColorReset
  370. case "remove", "rem", "delete":
  371. pos, _ := parseArgs(parts[1:])
  372. if len(pos) == 0 {
  373. return ColorRed + "Usage: /interface bridge remove <id|name>\n" + ColorReset
  374. }
  375. if err := e.bridgeMgr.DeleteBridge(pos[0]); err != nil {
  376. return ColorRed + "Error deleting bridge: " + err.Error() + ColorReset + "\n"
  377. }
  378. return ColorGreen + fmt.Sprintf("Bridge %s removed.\n", pos[0]) + ColorReset
  379. case "enable":
  380. pos, _ := parseArgs(parts[1:])
  381. if len(pos) == 0 {
  382. return ColorRed + "Usage: /interface bridge enable <id|name>\n" + ColorReset
  383. }
  384. if err := e.bridgeMgr.SetBridgeState(pos[0], false); err != nil {
  385. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  386. }
  387. return ColorGreen + fmt.Sprintf("Bridge %s enabled.\n", pos[0]) + ColorReset
  388. case "disable":
  389. pos, _ := parseArgs(parts[1:])
  390. if len(pos) == 0 {
  391. return ColorRed + "Usage: /interface bridge disable <id|name>\n" + ColorReset
  392. }
  393. if err := e.bridgeMgr.SetBridgeState(pos[0], true); err != nil {
  394. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  395. }
  396. return ColorGreen + fmt.Sprintf("Bridge %s disabled.\n", pos[0]) + ColorReset
  397. default:
  398. return ColorRed + "Usage: /interface bridge [print|add|remove|enable|disable|port]\n" + ColorReset
  399. }
  400. }
  401. func (e *Executor) handleBridgePort(parts []string, user *auth.PublicUser) string {
  402. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  403. ports := e.bridgeMgr.ListPorts()
  404. tbl := NewTable("FLAGS", "INTERFACE", "BRIDGE", "STATUS", "PVID")
  405. for _, p := range ports {
  406. flags := ""
  407. if p.Disabled {
  408. flags += "X"
  409. } else {
  410. flags += "H"
  411. }
  412. tbl.AddRow(flags, p.Interface, p.Bridge, p.Status, fmt.Sprintf("%d", p.PVID))
  413. }
  414. return tbl.String()
  415. }
  416. if user.Role == auth.RoleRead {
  417. return ColorRed + "Permission denied: viewer role cannot modify bridge ports\n" + ColorReset
  418. }
  419. sub := strings.ToLower(parts[0])
  420. switch sub {
  421. case "add":
  422. _, params := parseArgs(parts[1:])
  423. br := params["bridge"]
  424. iface := params["interface"]
  425. if br == "" || iface == "" {
  426. return ColorRed + "Usage: /interface bridge port add bridge=<bridge> interface=<iface>\n" + ColorReset
  427. }
  428. port := bridge.BridgePort{
  429. Bridge: br,
  430. Interface: iface,
  431. Disabled: false,
  432. }
  433. if err := e.bridgeMgr.SavePort(port); err != nil {
  434. return ColorRed + "Error adding bridge port: " + err.Error() + ColorReset + "\n"
  435. }
  436. return ColorGreen + fmt.Sprintf("Port %s attached to bridge %s.\n", iface, br) + ColorReset
  437. case "remove", "rem", "delete":
  438. pos, _ := parseArgs(parts[1:])
  439. if len(pos) == 0 {
  440. return ColorRed + "Usage: /interface bridge port remove <id>\n" + ColorReset
  441. }
  442. if err := e.bridgeMgr.DeletePort(pos[0]); err != nil {
  443. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  444. }
  445. return ColorGreen + fmt.Sprintf("Bridge port %s removed.\n", pos[0]) + ColorReset
  446. default:
  447. return ColorRed + "Usage: /interface bridge port [print|add|remove]\n" + ColorReset
  448. }
  449. }
  450. // --- WireGuard ---
  451. func (e *Executor) handleInterfaceWireGuard(parts []string, user *auth.PublicUser) string {
  452. if e.vpnMgr == nil || e.vpnMgr.WireGuard == nil {
  453. return ColorRed + "WireGuard subsystem not available.\n" + ColorReset
  454. }
  455. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  456. ifaces := e.vpnMgr.WireGuard.ListInterfaces()
  457. tbl := NewTable("FLAGS", "NAME", "LISTEN-PORT", "PUBLIC-KEY", "STATUS")
  458. for _, w := range ifaces {
  459. flags := ""
  460. if w.Disabled {
  461. flags += "X"
  462. } else {
  463. flags += "R"
  464. }
  465. st := "running"
  466. if !w.Running {
  467. st = "stopped"
  468. }
  469. tbl.AddRow(flags, w.Name, fmt.Sprintf("%d", w.ListenPort), w.PublicKey, st)
  470. }
  471. return tbl.String()
  472. }
  473. sub := strings.ToLower(parts[0])
  474. if sub == "peers" || sub == "peer" {
  475. return e.handleWireGuardPeers(parts[1:], user)
  476. }
  477. if user.Role == auth.RoleRead {
  478. return ColorRed + "Permission denied: viewer role cannot modify wireguard\n" + ColorReset
  479. }
  480. switch sub {
  481. case "add":
  482. _, params := parseArgs(parts[1:])
  483. name := params["name"]
  484. portStr := params["listen-port"]
  485. privKey := params["private-key"]
  486. port := 51820
  487. if portStr != "" {
  488. if pVal, err := strconv.Atoi(portStr); err == nil && pVal > 0 {
  489. port = pVal
  490. }
  491. }
  492. if name == "" {
  493. return ColorRed + "Usage: /interface wireguard add name=<name> [listen-port=<port>]\n" + ColorReset
  494. }
  495. req := vpn.SaveWireGuardInterfaceRequest{
  496. WireGuardInterface: vpn.WireGuardInterface{
  497. Name: name,
  498. ListenPort: port,
  499. PrivateKey: privKey,
  500. Disabled: false,
  501. },
  502. }
  503. if err := e.vpnMgr.WireGuard.SaveInterface(req); err != nil {
  504. return ColorRed + "Error adding WireGuard interface: " + err.Error() + ColorReset + "\n"
  505. }
  506. return ColorGreen + fmt.Sprintf("WireGuard interface %s added.\n", name) + ColorReset
  507. case "remove", "rem", "delete":
  508. pos, _ := parseArgs(parts[1:])
  509. if len(pos) == 0 {
  510. return ColorRed + "Usage: /interface wireguard remove <name>\n" + ColorReset
  511. }
  512. if err := e.vpnMgr.WireGuard.DeleteInterface(pos[0]); err != nil {
  513. return ColorRed + "Error deleting WireGuard interface: " + err.Error() + ColorReset + "\n"
  514. }
  515. return ColorGreen + fmt.Sprintf("WireGuard interface %s removed.\n", pos[0]) + ColorReset
  516. case "enable":
  517. pos, _ := parseArgs(parts[1:])
  518. if len(pos) == 0 {
  519. return ColorRed + "Usage: /interface wireguard enable <name>\n" + ColorReset
  520. }
  521. if err := e.vpnMgr.WireGuard.SetInterfaceEnabled(pos[0], true); err != nil {
  522. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  523. }
  524. return ColorGreen + fmt.Sprintf("WireGuard interface %s enabled.\n", pos[0]) + ColorReset
  525. case "disable":
  526. pos, _ := parseArgs(parts[1:])
  527. if len(pos) == 0 {
  528. return ColorRed + "Usage: /interface wireguard disable <name>\n" + ColorReset
  529. }
  530. if err := e.vpnMgr.WireGuard.SetInterfaceEnabled(pos[0], false); err != nil {
  531. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  532. }
  533. return ColorGreen + fmt.Sprintf("WireGuard interface %s disabled.\n", pos[0]) + ColorReset
  534. default:
  535. return ColorRed + "Usage: /interface wireguard [print|add|remove|enable|disable|peers]\n" + ColorReset
  536. }
  537. }
  538. func (e *Executor) handleWireGuardPeers(parts []string, user *auth.PublicUser) string {
  539. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  540. _, params := parseArgs(parts)
  541. peers := e.vpnMgr.WireGuard.ListPeers(params["interface"])
  542. tbl := NewTable("INTERFACE", "PUBLIC-KEY", "ENDPOINT", "ALLOWED-IPS", "LAST-HANDSHAKE")
  543. for _, p := range peers {
  544. hs := "-"
  545. if p.LatestHandshake != "" {
  546. hs = p.LatestHandshake
  547. }
  548. ep := p.EndpointAddress
  549. if p.EndpointPort > 0 {
  550. ep = fmt.Sprintf("%s:%d", p.EndpointAddress, p.EndpointPort)
  551. }
  552. tbl.AddRow(p.Interface, p.PublicKey, ep, strings.Join(p.AllowedIPs, ","), hs)
  553. }
  554. return tbl.String()
  555. }
  556. if user.Role == auth.RoleRead {
  557. return ColorRed + "Permission denied: viewer role cannot modify wireguard peers\n" + ColorReset
  558. }
  559. sub := strings.ToLower(parts[0])
  560. switch sub {
  561. case "add":
  562. _, params := parseArgs(parts[1:])
  563. iface := params["interface"]
  564. pubKey := params["public-key"]
  565. endpoint := params["endpoint"]
  566. allowed := params["allowed-address"]
  567. if allowed == "" {
  568. allowed = params["allowed-ips"]
  569. }
  570. if iface == "" || pubKey == "" {
  571. return ColorRed + "Usage: /interface wireguard peers add interface=<iface> public-key=<key> [endpoint=host:port] [allowed-address=cidr]\n" + ColorReset
  572. }
  573. var allowedList []string
  574. if allowed != "" {
  575. allowedList = strings.Split(allowed, ",")
  576. }
  577. epHost := endpoint
  578. epPort := 0
  579. if strings.Contains(endpoint, ":") {
  580. parts := strings.Split(endpoint, ":")
  581. epHost = parts[0]
  582. if p, err := strconv.Atoi(parts[1]); err == nil {
  583. epPort = p
  584. }
  585. }
  586. req := vpn.SaveWireGuardPeerRequest{
  587. WireGuardPeer: vpn.WireGuardPeer{
  588. Interface: iface,
  589. PublicKey: pubKey,
  590. EndpointAddress: epHost,
  591. EndpointPort: epPort,
  592. AllowedIPs: allowedList,
  593. Disabled: false,
  594. },
  595. }
  596. if err := e.vpnMgr.WireGuard.SavePeer(req); err != nil {
  597. return ColorRed + "Error saving WireGuard peer: " + err.Error() + ColorReset + "\n"
  598. }
  599. return ColorGreen + "WireGuard peer added successfully.\n" + ColorReset
  600. case "remove", "rem", "delete":
  601. _, params := parseArgs(parts[1:])
  602. iface := params["interface"]
  603. pubKey := params["public-key"]
  604. if iface == "" || pubKey == "" {
  605. return ColorRed + "Usage: /interface wireguard peers remove interface=<iface> public-key=<key>\n" + ColorReset
  606. }
  607. if err := e.vpnMgr.WireGuard.DeletePeer(iface, pubKey); err != nil {
  608. return ColorRed + "Error deleting WireGuard peer: " + err.Error() + ColorReset + "\n"
  609. }
  610. return ColorGreen + "WireGuard peer removed successfully.\n" + ColorReset
  611. default:
  612. return ColorRed + "Usage: /interface wireguard peers [print|add|remove]\n" + ColorReset
  613. }
  614. }
  615. // --- EoIP ---
  616. func (e *Executor) handleInterfaceEoIP(parts []string, user *auth.PublicUser) string {
  617. if e.vpnMgr == nil || e.vpnMgr.EoIP == nil {
  618. return ColorRed + "EoIP subsystem not available.\n" + ColorReset
  619. }
  620. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  621. tunnels := e.vpnMgr.EoIP.List()
  622. tbl := NewTable("FLAGS", "NAME", "REMOTE-ADDRESS", "TUNNEL-ID", "STATUS")
  623. for _, t := range tunnels {
  624. flags := ""
  625. if t.Disabled {
  626. flags += "X"
  627. } else {
  628. flags += "R"
  629. }
  630. st := "stopped"
  631. if t.Running {
  632. st = "running"
  633. }
  634. tbl.AddRow(flags, t.Name, t.RemoteAddress, fmt.Sprintf("%d", t.TunnelID), st)
  635. }
  636. return tbl.String()
  637. }
  638. if user.Role == auth.RoleRead {
  639. return ColorRed + "Permission denied: viewer role cannot modify EoIP\n" + ColorReset
  640. }
  641. sub := strings.ToLower(parts[0])
  642. switch sub {
  643. case "add":
  644. _, params := parseArgs(parts[1:])
  645. name := params["name"]
  646. remote := params["remote-address"]
  647. tidStr := params["tunnel-id"]
  648. tid, err := strconv.Atoi(tidStr)
  649. if err != nil || tid < 1 || tid > 65535 {
  650. return ColorRed + "Invalid tunnel-id (1-65535)\n" + ColorReset
  651. }
  652. if name == "" || remote == "" {
  653. return ColorRed + "Usage: /interface eoip add name=<name> remote-address=<ip> tunnel-id=<id>\n" + ColorReset
  654. }
  655. inst := &vpn.EoIPInstance{
  656. Name: name,
  657. RemoteAddress: remote,
  658. TunnelID: tid,
  659. Disabled: false,
  660. }
  661. if err := e.vpnMgr.EoIP.Save(inst); err != nil {
  662. return ColorRed + "Error adding EoIP tunnel: " + err.Error() + ColorReset + "\n"
  663. }
  664. return ColorGreen + fmt.Sprintf("EoIP tunnel %s added.\n", name) + ColorReset
  665. case "remove", "rem", "delete":
  666. pos, _ := parseArgs(parts[1:])
  667. if len(pos) == 0 {
  668. return ColorRed + "Usage: /interface eoip remove <id>\n" + ColorReset
  669. }
  670. if err := e.vpnMgr.EoIP.Delete(pos[0]); err != nil {
  671. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  672. }
  673. return ColorGreen + fmt.Sprintf("EoIP tunnel %s removed.\n", pos[0]) + ColorReset
  674. case "enable":
  675. pos, _ := parseArgs(parts[1:])
  676. if len(pos) == 0 {
  677. return ColorRed + "Usage: /interface eoip enable <id>\n" + ColorReset
  678. }
  679. if err := e.vpnMgr.EoIP.SetState(pos[0], false); err != nil {
  680. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  681. }
  682. return ColorGreen + fmt.Sprintf("EoIP tunnel %s enabled.\n", pos[0]) + ColorReset
  683. case "disable":
  684. pos, _ := parseArgs(parts[1:])
  685. if len(pos) == 0 {
  686. return ColorRed + "Usage: /interface eoip disable <id>\n" + ColorReset
  687. }
  688. if err := e.vpnMgr.EoIP.SetState(pos[0], true); err != nil {
  689. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  690. }
  691. return ColorGreen + fmt.Sprintf("EoIP tunnel %s disabled.\n", pos[0]) + ColorReset
  692. default:
  693. return ColorRed + "Usage: /interface eoip [print|add|remove|enable|disable]\n" + ColorReset
  694. }
  695. }
  696. // --- VRRP ---
  697. func (e *Executor) handleInterfaceVRRP(parts []string, user *auth.PublicUser) string {
  698. if e.vrrpMgr == nil {
  699. return ColorRed + "VRRP subsystem not available.\n" + ColorReset
  700. }
  701. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  702. instances := e.vrrpMgr.List()
  703. tbl := NewTable("FLAGS", "NAME", "INTERFACE", "VRID", "PRIORITY", "STATE", "VIP")
  704. for _, inst := range instances {
  705. flags := ""
  706. if inst.Disabled {
  707. flags += "X"
  708. } else {
  709. flags += "R"
  710. }
  711. tbl.AddRow(flags, inst.Name, inst.Interface, fmt.Sprintf("%d", inst.VRID), fmt.Sprintf("%d", inst.Priority), inst.State, strings.Join(inst.VirtualIPs, ","))
  712. }
  713. return tbl.String()
  714. }
  715. if user.Role == auth.RoleRead {
  716. return ColorRed + "Permission denied: viewer role cannot modify VRRP\n" + ColorReset
  717. }
  718. sub := strings.ToLower(parts[0])
  719. switch sub {
  720. case "add":
  721. _, params := parseArgs(parts[1:])
  722. name := params["name"]
  723. iface := params["interface"]
  724. vridStr := params["vrid"]
  725. prioStr := params["priority"]
  726. vip := params["vip"]
  727. if vip == "" {
  728. vip = params["virtual-ip"]
  729. }
  730. vrid, err := strconv.Atoi(vridStr)
  731. if err != nil || vrid < 1 || vrid > 255 {
  732. return ColorRed + "Invalid vrid (1-255)\n" + ColorReset
  733. }
  734. prio := 100
  735. if prioStr != "" {
  736. if pVal, err := strconv.Atoi(prioStr); err == nil && pVal >= 1 && pVal <= 254 {
  737. prio = pVal
  738. }
  739. }
  740. if name == "" || iface == "" || vip == "" {
  741. return ColorRed + "Usage: /interface vrrp add name=<name> interface=<iface> vrid=<id> vip=<ip/cidr> [priority=100]\n" + ColorReset
  742. }
  743. inst := vrrp.VRRPInstance{
  744. Name: name,
  745. Interface: iface,
  746. VRID: vrid,
  747. Priority: prio,
  748. VirtualIPs: []string{vip},
  749. Disabled: false,
  750. }
  751. if _, err := e.vrrpMgr.Save(inst); err != nil {
  752. return ColorRed + "Error adding VRRP: " + err.Error() + ColorReset + "\n"
  753. }
  754. return ColorGreen + fmt.Sprintf("VRRP instance %s added.\n", name) + ColorReset
  755. case "remove", "rem", "delete":
  756. pos, _ := parseArgs(parts[1:])
  757. if len(pos) == 0 {
  758. return ColorRed + "Usage: /interface vrrp remove <id>\n" + ColorReset
  759. }
  760. if err := e.vrrpMgr.Delete(pos[0]); err != nil {
  761. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  762. }
  763. return ColorGreen + fmt.Sprintf("VRRP instance %s removed.\n", pos[0]) + ColorReset
  764. case "enable":
  765. pos, _ := parseArgs(parts[1:])
  766. if len(pos) == 0 {
  767. return ColorRed + "Usage: /interface vrrp enable <id>\n" + ColorReset
  768. }
  769. if err := e.vrrpMgr.ToggleState(pos[0], false); err != nil {
  770. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  771. }
  772. return ColorGreen + fmt.Sprintf("VRRP instance %s enabled.\n", pos[0]) + ColorReset
  773. case "disable":
  774. pos, _ := parseArgs(parts[1:])
  775. if len(pos) == 0 {
  776. return ColorRed + "Usage: /interface vrrp disable <id>\n" + ColorReset
  777. }
  778. if err := e.vrrpMgr.ToggleState(pos[0], true); err != nil {
  779. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  780. }
  781. return ColorGreen + fmt.Sprintf("VRRP instance %s disabled.\n", pos[0]) + ColorReset
  782. default:
  783. return ColorRed + "Usage: /interface vrrp [print|add|remove|enable|disable]\n" + ColorReset
  784. }
  785. }
  786. // --- PPPoE Client ---
  787. func (e *Executor) handleInterfacePPPoE(parts []string, user *auth.PublicUser) string {
  788. if e.vpnMgr == nil || e.vpnMgr.PPPoE == nil {
  789. return ColorRed + "PPPoE subsystem not available.\n" + ColorReset
  790. }
  791. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  792. clients := e.vpnMgr.PPPoE.List()
  793. tbl := NewTable("FLAGS", "NAME", "INTERFACE", "USER", "STATUS", "IP")
  794. for _, c := range clients {
  795. flags := ""
  796. if c.Disabled {
  797. flags += "X"
  798. } else {
  799. flags += "R"
  800. }
  801. st := "stopped"
  802. if c.Running {
  803. st = "running"
  804. }
  805. tbl.AddRow(flags, c.Name, c.Interface, c.Username, st, c.AssignedIP)
  806. }
  807. return tbl.String()
  808. }
  809. if user.Role == auth.RoleRead {
  810. return ColorRed + "Permission denied: viewer role cannot modify PPPoE\n" + ColorReset
  811. }
  812. sub := strings.ToLower(parts[0])
  813. switch sub {
  814. case "add":
  815. _, params := parseArgs(parts[1:])
  816. name := params["name"]
  817. iface := params["interface"]
  818. usr := params["user"]
  819. pwd := params["password"]
  820. if name == "" || iface == "" || usr == "" {
  821. return ColorRed + "Usage: /interface pppoe-client add name=<name> interface=<iface> user=<usr> password=<pwd>\n" + ColorReset
  822. }
  823. inst := vpn.PPPoEInstance{
  824. Name: name,
  825. Interface: iface,
  826. Username: usr,
  827. Password: pwd,
  828. Disabled: false,
  829. }
  830. if err := e.vpnMgr.PPPoE.Save(inst); err != nil {
  831. return ColorRed + "Error adding PPPoE client: " + err.Error() + ColorReset + "\n"
  832. }
  833. return ColorGreen + fmt.Sprintf("PPPoE client %s added.\n", name) + ColorReset
  834. case "remove", "rem", "delete":
  835. pos, _ := parseArgs(parts[1:])
  836. if len(pos) == 0 {
  837. return ColorRed + "Usage: /interface pppoe-client remove <name>\n" + ColorReset
  838. }
  839. if err := e.vpnMgr.PPPoE.Delete(pos[0]); err != nil {
  840. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  841. }
  842. return ColorGreen + fmt.Sprintf("PPPoE client %s removed.\n", pos[0]) + ColorReset
  843. case "connect":
  844. pos, _ := parseArgs(parts[1:])
  845. if len(pos) == 0 {
  846. return ColorRed + "Usage: /interface pppoe-client connect <name>\n" + ColorReset
  847. }
  848. if err := e.vpnMgr.PPPoE.Connect(pos[0]); err != nil {
  849. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  850. }
  851. return ColorGreen + fmt.Sprintf("Connecting PPPoE client %s...\n", pos[0]) + ColorReset
  852. case "disconnect":
  853. pos, _ := parseArgs(parts[1:])
  854. if len(pos) == 0 {
  855. return ColorRed + "Usage: /interface pppoe-client disconnect <name>\n" + ColorReset
  856. }
  857. if err := e.vpnMgr.PPPoE.Disconnect(pos[0]); err != nil {
  858. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  859. }
  860. return ColorGreen + fmt.Sprintf("Disconnected PPPoE client %s.\n", pos[0]) + ColorReset
  861. default:
  862. return ColorRed + "Usage: /interface pppoe-client [print|add|remove|connect|disconnect]\n" + ColorReset
  863. }
  864. }
  865. // ==================== IP SUBSYSTEM ====================
  866. func (e *Executor) handleIP(parts []string, user *auth.PublicUser) string {
  867. if len(parts) == 0 {
  868. return ColorYellow + "IP subsystem. Available: address, route, dhcp-client, dhcp-server, dns, firewall, service\n" + ColorReset
  869. }
  870. sub := strings.ToLower(parts[0])
  871. switch sub {
  872. case "address", "addr":
  873. return e.handleIPAddress(parts[1:], user)
  874. case "route":
  875. return e.handleIPRoute(parts[1:], user)
  876. case "dhcp-client":
  877. return e.handleIPDHCPClient(parts[1:], user)
  878. case "dhcp-server":
  879. return e.handleIPDHCPServer(parts[1:], user)
  880. case "dns":
  881. return e.handleIPDNS(parts[1:], user)
  882. case "firewall":
  883. return e.handleIPFirewall(parts[1:], user)
  884. case "service", "services":
  885. return e.handleIPService(parts[1:], user)
  886. default:
  887. return ColorRed + "Unknown IP command: " + sub + "\n" + ColorReset
  888. }
  889. }
  890. // --- IP Address ---
  891. func (e *Executor) handleIPAddress(parts []string, user *auth.PublicUser) string {
  892. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  893. ifaces, err := e.netMgr.ListInterfaces()
  894. if err != nil {
  895. return ColorRed + "Error listing IP addresses: " + err.Error() + ColorReset + "\n"
  896. }
  897. tbl := NewTable("#", "ADDRESS", "NETWORK", "INTERFACE", "STATUS")
  898. idx := 0
  899. for _, iface := range ifaces {
  900. for _, cidr := range append(iface.IPv4, iface.IPv6...) {
  901. ip, ipNet, parseErr := net.ParseCIDR(cidr)
  902. netStr := "-"
  903. if parseErr == nil && ipNet != nil {
  904. netStr = ipNet.IP.String()
  905. } else if ip != nil {
  906. netStr = ip.String()
  907. }
  908. tbl.AddRow(fmt.Sprintf("%d", idx), cidr, netStr, iface.Name, "enabled")
  909. idx++
  910. }
  911. }
  912. return tbl.String()
  913. }
  914. if user.Role == auth.RoleRead {
  915. return ColorRed + "Permission denied: viewer role cannot modify IP addresses\n" + ColorReset
  916. }
  917. sub := strings.ToLower(parts[0])
  918. switch sub {
  919. case "add":
  920. _, params := parseArgs(parts[1:])
  921. addr := params["address"]
  922. iface := params["interface"]
  923. if addr == "" || iface == "" {
  924. return ColorRed + "Usage: /ip address add address=<ip/mask> interface=<iface>\n" + ColorReset
  925. }
  926. if err := e.netMgr.AddInterfaceIP(iface, addr); err != nil {
  927. return ColorRed + "Error adding IP address: " + err.Error() + ColorReset + "\n"
  928. }
  929. return ColorGreen + fmt.Sprintf("IP address %s added to interface %s.\n", addr, iface) + ColorReset
  930. case "remove", "rem", "delete":
  931. _, params := parseArgs(parts[1:])
  932. addr := params["address"]
  933. iface := params["interface"]
  934. if addr == "" || iface == "" {
  935. return ColorRed + "Usage: /ip address remove address=<ip/mask> interface=<iface>\n" + ColorReset
  936. }
  937. if err := e.netMgr.DeleteInterfaceIP(iface, addr); err != nil {
  938. return ColorRed + "Error removing IP address: " + err.Error() + ColorReset + "\n"
  939. }
  940. return ColorGreen + fmt.Sprintf("IP address %s removed from interface %s.\n", addr, iface) + ColorReset
  941. default:
  942. return ColorRed + "Usage: /ip address [print|add|remove]\n" + ColorReset
  943. }
  944. }
  945. // --- IP Route ---
  946. func (e *Executor) handleIPRoute(parts []string, user *auth.PublicUser) string {
  947. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  948. routes := e.routingMgr.ListRoutes()
  949. tbl := NewTable("FLAGS", "DST-ADDRESS", "GATEWAY", "DISTANCE", "TABLE", "STATUS")
  950. for _, r := range routes {
  951. flags := ""
  952. if r.Disabled {
  953. flags += "X"
  954. } else {
  955. flags += "A"
  956. }
  957. if r.Static {
  958. flags += "S"
  959. } else {
  960. flags += "D"
  961. }
  962. st := "active"
  963. if r.Disabled {
  964. st = "disabled"
  965. }
  966. tbl.AddRow(flags, r.DstAddress, r.Gateway, fmt.Sprintf("%d", r.Distance), r.RoutingTable, st)
  967. }
  968. return tbl.String()
  969. }
  970. sub := strings.ToLower(parts[0])
  971. switch sub {
  972. case "rule":
  973. rules := e.routingMgr.ListPolicyRules()
  974. tbl := NewTable("#", "SRC", "DST", "TABLE", "PRIORITY")
  975. for idx, r := range rules {
  976. tbl.AddRow(fmt.Sprintf("%d", idx), r.SrcAddress, r.DstAddress, r.Table, fmt.Sprintf("%d", r.Priority))
  977. }
  978. return tbl.String()
  979. case "table":
  980. tables := e.routingMgr.ListTables()
  981. tbl := NewTable("#", "NAME", "TABLE-ID", "COMMENT")
  982. for idx, t := range tables {
  983. tbl.AddRow(fmt.Sprintf("%d", idx), t.Name, fmt.Sprintf("%d", t.TableID), t.Comment)
  984. }
  985. return tbl.String()
  986. }
  987. if user.Role == auth.RoleRead {
  988. return ColorRed + "Permission denied: viewer role cannot modify routes\n" + ColorReset
  989. }
  990. switch sub {
  991. case "add":
  992. _, params := parseArgs(parts[1:])
  993. dst := params["dst-address"]
  994. if dst == "" {
  995. dst = params["dst"]
  996. }
  997. gw := params["gateway"]
  998. table := params["routing-table"]
  999. if table == "" {
  1000. table = "main"
  1001. }
  1002. dist := 1
  1003. if dStr, ok := params["distance"]; ok {
  1004. if dVal, err := strconv.Atoi(dStr); err == nil && dVal >= 1 {
  1005. dist = dVal
  1006. }
  1007. }
  1008. if dst == "" || gw == "" {
  1009. return ColorRed + "Usage: /ip route add dst-address=<cidr> gateway=<ip> [distance=1] [routing-table=main]\n" + ColorReset
  1010. }
  1011. r := routing.RouteItem{
  1012. DstAddress: dst,
  1013. Gateway: gw,
  1014. Distance: dist,
  1015. RoutingTable: table,
  1016. Static: true,
  1017. Disabled: false,
  1018. }
  1019. if err := e.routingMgr.SaveRoute(r); err != nil {
  1020. return ColorRed + "Error adding route: " + err.Error() + ColorReset + "\n"
  1021. }
  1022. return ColorGreen + fmt.Sprintf("Route to %s via %s added.\n", dst, gw) + ColorReset
  1023. case "remove", "rem", "delete":
  1024. pos, _ := parseArgs(parts[1:])
  1025. if len(pos) == 0 {
  1026. return ColorRed + "Usage: /ip route remove <id>\n" + ColorReset
  1027. }
  1028. if err := e.routingMgr.DeleteRoute(pos[0]); err != nil {
  1029. return ColorRed + "Error removing route: " + err.Error() + ColorReset + "\n"
  1030. }
  1031. return ColorGreen + fmt.Sprintf("Route %s removed.\n", pos[0]) + ColorReset
  1032. case "enable":
  1033. pos, _ := parseArgs(parts[1:])
  1034. if len(pos) == 0 {
  1035. return ColorRed + "Usage: /ip route enable <id>\n" + ColorReset
  1036. }
  1037. if err := e.routingMgr.SetRouteState(pos[0], false); err != nil {
  1038. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1039. }
  1040. return ColorGreen + fmt.Sprintf("Route %s enabled.\n", pos[0]) + ColorReset
  1041. case "disable":
  1042. pos, _ := parseArgs(parts[1:])
  1043. if len(pos) == 0 {
  1044. return ColorRed + "Usage: /ip route disable <id>\n" + ColorReset
  1045. }
  1046. if err := e.routingMgr.SetRouteState(pos[0], true); err != nil {
  1047. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1048. }
  1049. return ColorGreen + fmt.Sprintf("Route %s disabled.\n", pos[0]) + ColorReset
  1050. default:
  1051. return ColorRed + "Usage: /ip route [print|add|remove|enable|disable|rule|table]\n" + ColorReset
  1052. }
  1053. }
  1054. // --- IP DHCP Client ---
  1055. func (e *Executor) handleIPDHCPClient(parts []string, user *auth.PublicUser) string {
  1056. if e.dhcpMgr == nil {
  1057. return ColorRed + "DHCP client manager not available.\n" + ColorReset
  1058. }
  1059. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  1060. clients := e.dhcpMgr.ListClients()
  1061. tbl := NewTable("FLAGS", "INTERFACE", "USE-PEER-DNS", "STATUS", "ADDRESS", "GATEWAY")
  1062. for _, c := range clients {
  1063. flags := ""
  1064. if !c.Enabled {
  1065. flags += "X"
  1066. } else {
  1067. flags += "R"
  1068. }
  1069. addr := c.IPAddress
  1070. if addr == "" {
  1071. addr = "-"
  1072. }
  1073. gw := c.Gateway
  1074. if gw == "" {
  1075. gw = "-"
  1076. }
  1077. tbl.AddRow(flags, c.Interface, fmt.Sprintf("%t", c.UsePeerDNS), c.Status, addr, gw)
  1078. }
  1079. return tbl.String()
  1080. }
  1081. if user.Role == auth.RoleRead {
  1082. return ColorRed + "Permission denied: viewer role cannot modify DHCP clients\n" + ColorReset
  1083. }
  1084. sub := strings.ToLower(parts[0])
  1085. switch sub {
  1086. case "add":
  1087. _, params := parseArgs(parts[1:])
  1088. iface := params["interface"]
  1089. if iface == "" {
  1090. return ColorRed + "Usage: /ip dhcp-client add interface=<iface> [use-peer-dns=yes]\n" + ColorReset
  1091. }
  1092. peerDNS := true
  1093. if val, ok := params["use-peer-dns"]; ok {
  1094. peerDNS = (strings.ToLower(val) == "yes" || strings.ToLower(val) == "true")
  1095. }
  1096. req := dhcp.CreateDHCPClientRequest{
  1097. Interface: iface,
  1098. UsePeerDNS: peerDNS,
  1099. Enabled: true,
  1100. }
  1101. if _, err := e.dhcpMgr.AddClient(req); err != nil {
  1102. return ColorRed + "Error adding DHCP client: " + err.Error() + ColorReset + "\n"
  1103. }
  1104. return ColorGreen + fmt.Sprintf("DHCP client added on interface %s.\n", iface) + ColorReset
  1105. case "remove", "rem", "delete":
  1106. pos, _ := parseArgs(parts[1:])
  1107. if len(pos) == 0 {
  1108. return ColorRed + "Usage: /ip dhcp-client remove <id>\n" + ColorReset
  1109. }
  1110. if err := e.dhcpMgr.DeleteClient(pos[0]); err != nil {
  1111. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1112. }
  1113. return ColorGreen + fmt.Sprintf("DHCP client %s removed.\n", pos[0]) + ColorReset
  1114. case "renew":
  1115. pos, _ := parseArgs(parts[1:])
  1116. if len(pos) == 0 {
  1117. return ColorRed + "Usage: /ip dhcp-client renew <id>\n" + ColorReset
  1118. }
  1119. if err := e.dhcpMgr.RenewLease(pos[0]); err != nil {
  1120. return ColorRed + "Error renewing lease: " + err.Error() + ColorReset + "\n"
  1121. }
  1122. return ColorGreen + fmt.Sprintf("DHCP lease renewal requested for %s.\n", pos[0]) + ColorReset
  1123. case "release":
  1124. pos, _ := parseArgs(parts[1:])
  1125. if len(pos) == 0 {
  1126. return ColorRed + "Usage: /ip dhcp-client release <id>\n" + ColorReset
  1127. }
  1128. if err := e.dhcpMgr.ReleaseLease(pos[0]); err != nil {
  1129. return ColorRed + "Error releasing lease: " + err.Error() + ColorReset + "\n"
  1130. }
  1131. return ColorGreen + fmt.Sprintf("DHCP lease released for %s.\n", pos[0]) + ColorReset
  1132. default:
  1133. return ColorRed + "Usage: /ip dhcp-client [print|add|remove|renew|release]\n" + ColorReset
  1134. }
  1135. }
  1136. // --- IP DHCP Server ---
  1137. func (e *Executor) handleIPDHCPServer(parts []string, user *auth.PublicUser) string {
  1138. if e.dhcpServMgr == nil {
  1139. return ColorRed + "DHCP server manager not available.\n" + ColorReset
  1140. }
  1141. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  1142. servers := e.dhcpServMgr.ListServers()
  1143. tbl := NewTable("FLAGS", "NAME", "INTERFACE", "ADDRESS-POOL", "LEASE-TIME")
  1144. for _, s := range servers {
  1145. flags := ""
  1146. if s.Disabled {
  1147. flags += "X"
  1148. } else {
  1149. flags += "R"
  1150. }
  1151. tbl.AddRow(flags, s.Name, s.Interface, s.AddressPool, s.LeaseTime)
  1152. }
  1153. return tbl.String()
  1154. }
  1155. sub := strings.ToLower(parts[0])
  1156. switch sub {
  1157. case "lease":
  1158. leases := e.dhcpServMgr.ListLeases()
  1159. tbl := NewTable("IP-ADDRESS", "MAC-ADDRESS", "HOSTNAME", "EXPIRES-IN", "STATUS")
  1160. for _, l := range leases {
  1161. tbl.AddRow(l.Address, l.MACAddress, l.HostName, l.ExpiresIn, l.Status)
  1162. }
  1163. return tbl.String()
  1164. case "network":
  1165. networks := e.dhcpServMgr.ListNetworks()
  1166. tbl := NewTable("SUBNET", "GATEWAY", "DNS-SERVERS", "DOMAIN")
  1167. for _, n := range networks {
  1168. tbl.AddRow(n.Address, n.Gateway, strings.Join(n.DNSServers, ","), n.Domain)
  1169. }
  1170. return tbl.String()
  1171. }
  1172. if user.Role == auth.RoleRead {
  1173. return ColorRed + "Permission denied: viewer role cannot modify DHCP server\n" + ColorReset
  1174. }
  1175. switch sub {
  1176. case "add":
  1177. _, params := parseArgs(parts[1:])
  1178. name := params["name"]
  1179. iface := params["interface"]
  1180. pool := params["address-pool"]
  1181. if pool == "" {
  1182. start := params["range-start"]
  1183. end := params["range-end"]
  1184. if start != "" && end != "" {
  1185. pool = fmt.Sprintf("%s-%s", start, end)
  1186. }
  1187. }
  1188. leaseTime := params["lease-time"]
  1189. if leaseTime == "" {
  1190. leaseTime = "12h"
  1191. }
  1192. if name == "" || iface == "" || pool == "" {
  1193. return ColorRed + "Usage: /ip dhcp-server add name=<name> interface=<iface> address-pool=<start-end> [lease-time=12h]\n" + ColorReset
  1194. }
  1195. inst := dhcp.DHCPServerInstance{
  1196. Name: name,
  1197. Interface: iface,
  1198. AddressPool: pool,
  1199. LeaseTime: leaseTime,
  1200. Disabled: false,
  1201. }
  1202. if _, err := e.dhcpServMgr.SaveServer(inst); err != nil {
  1203. return ColorRed + "Error adding DHCP server: " + err.Error() + ColorReset + "\n"
  1204. }
  1205. return ColorGreen + fmt.Sprintf("DHCP server %s added on interface %s.\n", name, iface) + ColorReset
  1206. case "remove", "rem", "delete":
  1207. pos, _ := parseArgs(parts[1:])
  1208. if len(pos) == 0 {
  1209. return ColorRed + "Usage: /ip dhcp-server remove <id>\n" + ColorReset
  1210. }
  1211. if err := e.dhcpServMgr.DeleteServer(pos[0]); err != nil {
  1212. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1213. }
  1214. return ColorGreen + fmt.Sprintf("DHCP server %s removed.\n", pos[0]) + ColorReset
  1215. case "enable":
  1216. pos, _ := parseArgs(parts[1:])
  1217. if len(pos) == 0 {
  1218. return ColorRed + "Usage: /ip dhcp-server enable <id>\n" + ColorReset
  1219. }
  1220. if err := e.dhcpServMgr.ToggleServerState(pos[0], false); err != nil {
  1221. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1222. }
  1223. return ColorGreen + fmt.Sprintf("DHCP server %s enabled.\n", pos[0]) + ColorReset
  1224. case "disable":
  1225. pos, _ := parseArgs(parts[1:])
  1226. if len(pos) == 0 {
  1227. return ColorRed + "Usage: /ip dhcp-server disable <id>\n" + ColorReset
  1228. }
  1229. if err := e.dhcpServMgr.ToggleServerState(pos[0], true); err != nil {
  1230. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1231. }
  1232. return ColorGreen + fmt.Sprintf("DHCP server %s disabled.\n", pos[0]) + ColorReset
  1233. default:
  1234. return ColorRed + "Usage: /ip dhcp-server [print|add|remove|enable|disable|lease|network]\n" + ColorReset
  1235. }
  1236. }
  1237. // --- IP DNS ---
  1238. func (e *Executor) handleIPDNS(parts []string, user *auth.PublicUser) string {
  1239. if e.dnsMgr == nil {
  1240. return ColorRed + "DNS manager not available.\n" + ColorReset
  1241. }
  1242. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  1243. settings := e.dnsMgr.GetSettings()
  1244. out := " servers: " + strings.Join(settings.Servers, ", ") + "\n"
  1245. out += " dynamic-servers: " + strings.Join(settings.DynamicServers, ", ") + "\n"
  1246. out += fmt.Sprintf(" allow-remote-requests: %t\n", settings.AllowRemoteRequests)
  1247. out += fmt.Sprintf(" cache-size: %d entries\n", settings.CacheSize)
  1248. out += fmt.Sprintf(" domain: %s\n", settings.Domain)
  1249. return out
  1250. }
  1251. sub := strings.ToLower(parts[0])
  1252. switch sub {
  1253. case "cache":
  1254. if len(parts) > 1 && parts[1] == "flush" {
  1255. if user.Role == auth.RoleRead {
  1256. return ColorRed + "Permission denied\n" + ColorReset
  1257. }
  1258. if err := e.dnsMgr.FlushCache(); err != nil {
  1259. return ColorRed + "Error flushing DNS cache: " + err.Error() + ColorReset + "\n"
  1260. }
  1261. return ColorGreen + "DNS cache flushed.\n" + ColorReset
  1262. }
  1263. return ColorYellow + "Usage: /ip dns cache flush\n" + ColorReset
  1264. case "static":
  1265. records := e.dnsMgr.ListStaticRecords()
  1266. tbl := NewTable("#", "NAME", "ADDRESS", "TTL", "STATUS")
  1267. for idx, r := range records {
  1268. st := "enabled"
  1269. if r.Disabled {
  1270. st = "disabled"
  1271. }
  1272. tbl.AddRow(fmt.Sprintf("%d", idx), r.Name, r.Address, fmt.Sprintf("%d", r.TTL), st)
  1273. }
  1274. return tbl.String()
  1275. case "set":
  1276. if user.Role == auth.RoleRead {
  1277. return ColorRed + "Permission denied\n" + ColorReset
  1278. }
  1279. _, params := parseArgs(parts[1:])
  1280. if sStr, ok := params["servers"]; ok {
  1281. srvs := strings.Split(sStr, ",")
  1282. cfg := e.dnsMgr.GetSettings()
  1283. cfg.Servers = srvs
  1284. if err := e.dnsMgr.SaveSettings(cfg); err != nil {
  1285. return ColorRed + "Error saving DNS settings: " + err.Error() + ColorReset + "\n"
  1286. }
  1287. return ColorGreen + "DNS servers updated.\n" + ColorReset
  1288. }
  1289. return ColorRed + "Usage: /ip dns set servers=<ip1,ip2>\n" + ColorReset
  1290. default:
  1291. return ColorRed + "Usage: /ip dns [print|set|cache flush|static]\n" + ColorReset
  1292. }
  1293. }
  1294. // --- IP Firewall ---
  1295. func (e *Executor) handleIPFirewall(parts []string, user *auth.PublicUser) string {
  1296. if e.fwMgr == nil {
  1297. return ColorRed + "Firewall manager not available.\n" + ColorReset
  1298. }
  1299. if len(parts) == 0 {
  1300. return ColorYellow + "Firewall subsystem. Available: filter, nat, mangle, address-list\n" + ColorReset
  1301. }
  1302. tableType := strings.ToLower(parts[0])
  1303. switch tableType {
  1304. case "filter":
  1305. return e.handleFirewallFilter(parts[1:], user)
  1306. case "nat":
  1307. return e.handleFirewallNAT(parts[1:], user)
  1308. case "mangle":
  1309. return e.handleFirewallMangle(parts[1:], user)
  1310. case "address-list":
  1311. return e.handleFirewallAddressList(parts[1:], user)
  1312. default:
  1313. return ColorRed + "Unknown firewall table: " + tableType + "\n" + ColorReset
  1314. }
  1315. }
  1316. func (e *Executor) handleFirewallFilter(parts []string, user *auth.PublicUser) string {
  1317. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  1318. rules := e.fwMgr.ListFilterRules()
  1319. tbl := NewTable("FLAGS", "CHAIN", "ACTION", "PROTO", "SRC-ADDR", "DST-ADDR", "DST-PORT", "COMMENT")
  1320. for _, r := range rules {
  1321. flags := ""
  1322. if r.Disabled {
  1323. flags += "X"
  1324. } else {
  1325. flags += " "
  1326. }
  1327. tbl.AddRow(flags, r.Chain, r.Action, r.Protocol, r.SrcAddress, r.DstAddress, r.DstPort, r.Comment)
  1328. }
  1329. return tbl.String()
  1330. }
  1331. if user.Role == auth.RoleRead {
  1332. return ColorRed + "Permission denied: viewer role cannot modify firewall\n" + ColorReset
  1333. }
  1334. sub := strings.ToLower(parts[0])
  1335. switch sub {
  1336. case "add":
  1337. _, params := parseArgs(parts[1:])
  1338. chain := params["chain"]
  1339. action := params["action"]
  1340. if chain == "" || action == "" {
  1341. return ColorRed + "Usage: /ip firewall filter add chain=<input|forward|output> action=<accept|drop|reject> [protocol=...] [dst-port=...]\n" + ColorReset
  1342. }
  1343. r := firewall.FilterRule{
  1344. Chain: chain,
  1345. Action: action,
  1346. Protocol: params["protocol"],
  1347. SrcAddress: params["src-address"],
  1348. DstAddress: params["dst-address"],
  1349. DstPort: params["dst-port"],
  1350. Comment: params["comment"],
  1351. Disabled: false,
  1352. }
  1353. if err := e.fwMgr.SaveFilterRule(r); err != nil {
  1354. return ColorRed + "Error adding filter rule: " + err.Error() + ColorReset + "\n"
  1355. }
  1356. return ColorGreen + "Firewall filter rule added.\n" + ColorReset
  1357. case "remove", "rem", "delete":
  1358. pos, _ := parseArgs(parts[1:])
  1359. if len(pos) == 0 {
  1360. return ColorRed + "Usage: /ip firewall filter remove <id>\n" + ColorReset
  1361. }
  1362. if err := e.fwMgr.DeleteFilterRule(pos[0]); err != nil {
  1363. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1364. }
  1365. return ColorGreen + fmt.Sprintf("Filter rule %s removed.\n", pos[0]) + ColorReset
  1366. case "enable":
  1367. pos, _ := parseArgs(parts[1:])
  1368. if len(pos) == 0 {
  1369. return ColorRed + "Usage: /ip firewall filter enable <id>\n" + ColorReset
  1370. }
  1371. if err := e.fwMgr.SetFilterRuleState(pos[0], false); err != nil {
  1372. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1373. }
  1374. return ColorGreen + fmt.Sprintf("Filter rule %s enabled.\n", pos[0]) + ColorReset
  1375. case "disable":
  1376. pos, _ := parseArgs(parts[1:])
  1377. if len(pos) == 0 {
  1378. return ColorRed + "Usage: /ip firewall filter disable <id>\n" + ColorReset
  1379. }
  1380. if err := e.fwMgr.SetFilterRuleState(pos[0], true); err != nil {
  1381. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1382. }
  1383. return ColorGreen + fmt.Sprintf("Filter rule %s disabled.\n", pos[0]) + ColorReset
  1384. default:
  1385. return ColorRed + "Usage: /ip firewall filter [print|add|remove|enable|disable]\n" + ColorReset
  1386. }
  1387. }
  1388. func (e *Executor) handleFirewallNAT(parts []string, user *auth.PublicUser) string {
  1389. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  1390. rules := e.fwMgr.ListNATRules()
  1391. tbl := NewTable("FLAGS", "CHAIN", "ACTION", "OUT-IFACE", "TO-ADDRESSES", "TO-PORTS", "COMMENT")
  1392. for _, r := range rules {
  1393. flags := ""
  1394. if r.Disabled {
  1395. flags += "X"
  1396. } else {
  1397. flags += " "
  1398. }
  1399. tbl.AddRow(flags, r.Chain, r.Action, r.OutInterface, r.ToAddresses, r.ToPorts, r.Comment)
  1400. }
  1401. return tbl.String()
  1402. }
  1403. if user.Role == auth.RoleRead {
  1404. return ColorRed + "Permission denied: viewer role cannot modify firewall\n" + ColorReset
  1405. }
  1406. sub := strings.ToLower(parts[0])
  1407. switch sub {
  1408. case "add":
  1409. _, params := parseArgs(parts[1:])
  1410. chain := params["chain"]
  1411. action := params["action"]
  1412. if chain == "" || action == "" {
  1413. return ColorRed + "Usage: /ip firewall nat add chain=<srcnat|dstnat> action=<masquerade|dst-nat|src-nat> [out-interface=...] [to-addresses=...]\n" + ColorReset
  1414. }
  1415. r := firewall.NATRule{
  1416. Chain: chain,
  1417. Action: action,
  1418. OutInterface: params["out-interface"],
  1419. ToAddresses: params["to-addresses"],
  1420. ToPorts: params["to-ports"],
  1421. Comment: params["comment"],
  1422. Disabled: false,
  1423. }
  1424. if err := e.fwMgr.SaveNATRule(r); err != nil {
  1425. return ColorRed + "Error adding NAT rule: " + err.Error() + ColorReset + "\n"
  1426. }
  1427. return ColorGreen + "Firewall NAT rule added.\n" + ColorReset
  1428. case "remove", "rem", "delete":
  1429. pos, _ := parseArgs(parts[1:])
  1430. if len(pos) == 0 {
  1431. return ColorRed + "Usage: /ip firewall nat remove <id>\n" + ColorReset
  1432. }
  1433. if err := e.fwMgr.DeleteNATRule(pos[0]); err != nil {
  1434. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1435. }
  1436. return ColorGreen + fmt.Sprintf("NAT rule %s removed.\n", pos[0]) + ColorReset
  1437. case "enable":
  1438. pos, _ := parseArgs(parts[1:])
  1439. if len(pos) == 0 {
  1440. return ColorRed + "Usage: /ip firewall nat enable <id>\n" + ColorReset
  1441. }
  1442. if err := e.fwMgr.SetNATRuleState(pos[0], false); err != nil {
  1443. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1444. }
  1445. return ColorGreen + fmt.Sprintf("NAT rule %s enabled.\n", pos[0]) + ColorReset
  1446. case "disable":
  1447. pos, _ := parseArgs(parts[1:])
  1448. if len(pos) == 0 {
  1449. return ColorRed + "Usage: /ip firewall nat disable <id>\n" + ColorReset
  1450. }
  1451. if err := e.fwMgr.SetNATRuleState(pos[0], true); err != nil {
  1452. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1453. }
  1454. return ColorGreen + fmt.Sprintf("NAT rule %s disabled.\n", pos[0]) + ColorReset
  1455. default:
  1456. return ColorRed + "Usage: /ip firewall nat [print|add|remove|enable|disable]\n" + ColorReset
  1457. }
  1458. }
  1459. func (e *Executor) handleFirewallMangle(parts []string, user *auth.PublicUser) string {
  1460. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  1461. rules := e.fwMgr.ListMangleRules()
  1462. tbl := NewTable("FLAGS", "CHAIN", "ACTION", "PASSTHROUGH", "COMMENT")
  1463. for _, r := range rules {
  1464. flags := ""
  1465. if r.Disabled {
  1466. flags += "X"
  1467. } else {
  1468. flags += " "
  1469. }
  1470. tbl.AddRow(flags, r.Chain, r.Action, fmt.Sprintf("%t", r.Passthrough), r.Comment)
  1471. }
  1472. return tbl.String()
  1473. }
  1474. if user.Role == auth.RoleRead {
  1475. return ColorRed + "Permission denied\n" + ColorReset
  1476. }
  1477. sub := strings.ToLower(parts[0])
  1478. switch sub {
  1479. case "add":
  1480. _, params := parseArgs(parts[1:])
  1481. chain := params["chain"]
  1482. action := params["action"]
  1483. if chain == "" || action == "" {
  1484. return ColorRed + "Usage: /ip firewall mangle add chain=... action=...\n" + ColorReset
  1485. }
  1486. r := firewall.MangleRule{
  1487. Chain: chain,
  1488. Action: action,
  1489. Passthrough: true,
  1490. Comment: params["comment"],
  1491. Disabled: false,
  1492. }
  1493. if err := e.fwMgr.SaveMangleRule(r); err != nil {
  1494. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1495. }
  1496. return ColorGreen + "Mangle rule added.\n" + ColorReset
  1497. case "remove", "rem", "delete":
  1498. pos, _ := parseArgs(parts[1:])
  1499. if len(pos) == 0 {
  1500. return ColorRed + "Usage: /ip firewall mangle remove <id>\n" + ColorReset
  1501. }
  1502. if err := e.fwMgr.DeleteMangleRule(pos[0]); err != nil {
  1503. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1504. }
  1505. return ColorGreen + fmt.Sprintf("Mangle rule %s removed.\n", pos[0]) + ColorReset
  1506. default:
  1507. return ColorRed + "Usage: /ip firewall mangle [print|add|remove]\n" + ColorReset
  1508. }
  1509. }
  1510. func (e *Executor) handleFirewallAddressList(parts []string, user *auth.PublicUser) string {
  1511. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  1512. entries := e.fwMgr.ListAddressLists()
  1513. tbl := NewTable("FLAGS", "LIST", "ADDRESS", "COMMENT")
  1514. for _, entry := range entries {
  1515. flags := ""
  1516. if entry.Disabled {
  1517. flags += "X"
  1518. } else {
  1519. flags += " "
  1520. }
  1521. tbl.AddRow(flags, entry.Name, entry.Address, entry.Comment)
  1522. }
  1523. return tbl.String()
  1524. }
  1525. if user.Role == auth.RoleRead {
  1526. return ColorRed + "Permission denied\n" + ColorReset
  1527. }
  1528. sub := strings.ToLower(parts[0])
  1529. switch sub {
  1530. case "add":
  1531. _, params := parseArgs(parts[1:])
  1532. list := params["list"]
  1533. addr := params["address"]
  1534. if list == "" || addr == "" {
  1535. return ColorRed + "Usage: /ip firewall address-list add list=<name> address=<cidr> [comment=...]\n" + ColorReset
  1536. }
  1537. entry := firewall.AddressListEntry{
  1538. Name: list,
  1539. Address: addr,
  1540. Comment: params["comment"],
  1541. Disabled: false,
  1542. }
  1543. if err := e.fwMgr.SaveAddressListEntry(entry); err != nil {
  1544. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1545. }
  1546. return ColorGreen + fmt.Sprintf("Added %s to address list %s.\n", addr, list) + ColorReset
  1547. case "remove", "rem", "delete":
  1548. pos, _ := parseArgs(parts[1:])
  1549. if len(pos) == 0 {
  1550. return ColorRed + "Usage: /ip firewall address-list remove <id>\n" + ColorReset
  1551. }
  1552. if err := e.fwMgr.DeleteAddressListEntry(pos[0]); err != nil {
  1553. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1554. }
  1555. return ColorGreen + fmt.Sprintf("Address list entry %s removed.\n", pos[0]) + ColorReset
  1556. default:
  1557. return ColorRed + "Usage: /ip firewall address-list [print|add|remove]\n" + ColorReset
  1558. }
  1559. }
  1560. // --- IP Service ---
  1561. func (e *Executor) handleIPService(parts []string, user *auth.PublicUser) string {
  1562. if e.servicesMgr == nil {
  1563. return ColorRed + "Services manager not available.\n" + ColorReset
  1564. }
  1565. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  1566. svcs := e.servicesMgr.ListServices()
  1567. tbl := NewTable("NAME", "PORT", "DISABLED", "CERTIFICATE", "COMMENT")
  1568. for _, s := range svcs {
  1569. disStr := ColorGreen + "no" + ColorReset
  1570. if s.Disabled {
  1571. disStr = ColorRed + "yes" + ColorReset
  1572. }
  1573. certStr := "-"
  1574. if s.Certificate != "" {
  1575. certStr = ColorCyan + s.Certificate + ColorReset
  1576. }
  1577. tbl.AddRow(s.Name, fmt.Sprintf("%d", s.Port), disStr, certStr, s.Comment)
  1578. }
  1579. return tbl.String()
  1580. }
  1581. if user.Role != auth.RoleFull {
  1582. return ColorRed + "Permission denied: admin role required to manage services\n" + ColorReset
  1583. }
  1584. sub := strings.ToLower(parts[0])
  1585. switch sub {
  1586. case "enable":
  1587. pos, _ := parseArgs(parts[1:])
  1588. if len(pos) == 0 {
  1589. return ColorRed + "Usage: /ip service enable <name>\n" + ColorReset
  1590. }
  1591. name := pos[0]
  1592. if err := e.servicesMgr.SetServiceState(name, false); err != nil {
  1593. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1594. }
  1595. return ColorGreen + fmt.Sprintf("Service %s enabled.\n", name) + ColorReset
  1596. case "disable":
  1597. pos, _ := parseArgs(parts[1:])
  1598. if len(pos) == 0 {
  1599. return ColorRed + "Usage: /ip service disable <name>\n" + ColorReset
  1600. }
  1601. name := pos[0]
  1602. if err := e.servicesMgr.SetServiceState(name, true); err != nil {
  1603. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1604. }
  1605. return ColorGreen + fmt.Sprintf("Service %s disabled.\n", name) + ColorReset
  1606. case "set":
  1607. pos, params := parseArgs(parts[1:])
  1608. if len(pos) == 0 {
  1609. return ColorRed + "Usage: /ip service set <name> [port=<port>] [disabled=yes|no] [certificate=<cert_name>]\n" + ColorReset
  1610. }
  1611. name := pos[0]
  1612. svc, ok := e.servicesMgr.GetService(name)
  1613. if !ok {
  1614. return ColorRed + fmt.Sprintf("Service %q not found\n", name) + ColorReset
  1615. }
  1616. port := svc.Port
  1617. disabled := svc.Disabled
  1618. certName := svc.Certificate
  1619. if pStr, ok := params["port"]; ok {
  1620. if pVal, err := strconv.Atoi(pStr); err == nil && pVal > 0 && pVal < 65536 {
  1621. port = pVal
  1622. }
  1623. }
  1624. if dStr, ok := params["disabled"]; ok {
  1625. disabled = (strings.ToLower(dStr) == "yes" || strings.ToLower(dStr) == "true")
  1626. }
  1627. if cStr, ok := params["certificate"]; ok {
  1628. certName = cStr
  1629. }
  1630. if err := e.servicesMgr.SetService(name, port, disabled, certName); err != nil {
  1631. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  1632. }
  1633. return ColorGreen + fmt.Sprintf("Service %s updated (port=%d, disabled=%t, certificate=%q).\n", name, port, disabled, certName) + ColorReset
  1634. default:
  1635. return ColorRed + "Usage: /ip service [print|enable|disable|set]\n" + ColorReset
  1636. }
  1637. }
  1638. // ==================== DYNAMIC ROUTING ====================
  1639. func (e *Executor) handleRouting(parts []string, user *auth.PublicUser) string {
  1640. if e.dynRoutingMgr == nil {
  1641. return ColorRed + "Dynamic routing subsystem not available.\n" + ColorReset
  1642. }
  1643. if len(parts) == 0 {
  1644. return ColorYellow + "Routing subsystem. Available: bgp, ospf, filter\n" + ColorReset
  1645. }
  1646. sub := strings.ToLower(parts[0])
  1647. switch sub {
  1648. case "bgp":
  1649. return e.handleRoutingBGP(parts[1:], user)
  1650. case "ospf":
  1651. return e.handleRoutingOSPF(parts[1:], user)
  1652. case "filter":
  1653. return e.handleRoutingFilter(parts[1:], user)
  1654. default:
  1655. return ColorRed + "Unknown routing protocol: " + sub + "\n" + ColorReset
  1656. }
  1657. }
  1658. func (e *Executor) handleRoutingBGP(parts []string, user *auth.PublicUser) string {
  1659. if len(parts) == 0 || parts[0] == "instance" {
  1660. inst := e.dynRoutingMgr.GetBGPInstance()
  1661. out := fmt.Sprintf(" enabled: %t\n", inst.Enabled)
  1662. out += fmt.Sprintf(" as: %d\n", inst.AS)
  1663. out += fmt.Sprintf(" router-id: %s\n", inst.RouterID)
  1664. return out
  1665. }
  1666. sub := strings.ToLower(parts[0])
  1667. switch sub {
  1668. case "peer", "connection", "peers":
  1669. peers := e.dynRoutingMgr.ListBGPPeers()
  1670. tbl := NewTable("FLAGS", "NAME", "REMOTE-AS", "REMOTE-ADDRESS", "STATUS", "UPTIME")
  1671. for _, p := range peers {
  1672. flags := ""
  1673. if p.Disabled {
  1674. flags += "X"
  1675. } else {
  1676. flags += "E"
  1677. }
  1678. st := p.State
  1679. if st == "" {
  1680. st = "Active"
  1681. }
  1682. tbl.AddRow(flags, p.Name, fmt.Sprintf("%d", p.RemoteAS), p.RemoteAddress, st, p.Uptime)
  1683. }
  1684. return tbl.String()
  1685. case "network", "networks":
  1686. nets := e.dynRoutingMgr.ListBGPNetworks()
  1687. tbl := NewTable("NETWORK", "COMMENT")
  1688. for _, n := range nets {
  1689. tbl.AddRow(n.Network, n.Comment)
  1690. }
  1691. return tbl.String()
  1692. case "routes":
  1693. routes := e.dynRoutingMgr.GetBGPRoutes()
  1694. tbl := NewTable("PREFIX", "NEXT-HOP", "PATH", "METRIC", "WEIGHT")
  1695. for _, r := range routes {
  1696. tbl.AddRow(r.Prefix, r.NextHop, r.Path, fmt.Sprintf("%d", r.Metric), fmt.Sprintf("%d", r.Weight))
  1697. }
  1698. return tbl.String()
  1699. default:
  1700. return ColorRed + "Usage: /routing bgp [instance|peer|network|routes]\n" + ColorReset
  1701. }
  1702. }
  1703. func (e *Executor) handleRoutingOSPF(parts []string, user *auth.PublicUser) string {
  1704. if len(parts) == 0 || parts[0] == "instance" {
  1705. instances := e.dynRoutingMgr.ListOSPFInstances()
  1706. tbl := NewTable("NAME", "ROUTER-ID", "VERSION", "STATUS")
  1707. for _, inst := range instances {
  1708. st := "enabled"
  1709. if inst.Disabled {
  1710. st = "disabled"
  1711. }
  1712. tbl.AddRow(inst.Name, inst.RouterID, fmt.Sprintf("%d", inst.Version), st)
  1713. }
  1714. return tbl.String()
  1715. }
  1716. sub := strings.ToLower(parts[0])
  1717. switch sub {
  1718. case "area", "areas":
  1719. areas := e.dynRoutingMgr.ListOSPFAreas()
  1720. tbl := NewTable("NAME", "AREA-ID", "INSTANCE", "TYPE")
  1721. for _, a := range areas {
  1722. tbl.AddRow(a.Name, a.AreaID, a.Instance, a.AreaType)
  1723. }
  1724. return tbl.String()
  1725. case "interface", "interfaces":
  1726. ifaces := e.dynRoutingMgr.ListOSPFInterfaces()
  1727. tbl := NewTable("INTERFACE", "AREA", "COST", "PRIORITY", "NETWORK-TYPE")
  1728. for _, i := range ifaces {
  1729. tbl.AddRow(i.Interface, i.Area, fmt.Sprintf("%d", i.Cost), fmt.Sprintf("%d", i.Priority), i.NetworkType)
  1730. }
  1731. return tbl.String()
  1732. case "neighbor", "neighbors":
  1733. neighbors := e.dynRoutingMgr.GetOSPFNeighbors()
  1734. tbl := NewTable("ROUTER-ID", "ADDRESS", "INTERFACE", "STATE", "PRIORITY")
  1735. for _, n := range neighbors {
  1736. tbl.AddRow(n.NeighborID, n.Address, n.Interface, n.State, fmt.Sprintf("%d", n.Priority))
  1737. }
  1738. return tbl.String()
  1739. default:
  1740. return ColorRed + "Usage: /routing ospf [instance|area|interface|neighbor]\n" + ColorReset
  1741. }
  1742. }
  1743. func (e *Executor) handleRoutingFilter(parts []string, user *auth.PublicUser) string {
  1744. rules := e.dynRoutingMgr.ListRoutingFilters()
  1745. tbl := NewTable("CHAIN", "RULE", "ACTION", "STATUS")
  1746. for _, r := range rules {
  1747. st := "enabled"
  1748. if r.Disabled {
  1749. st = "disabled"
  1750. }
  1751. tbl.AddRow(r.Chain, r.Rule, r.Action, st)
  1752. }
  1753. return tbl.String()
  1754. }
  1755. // ==================== TOOLS ====================
  1756. func (e *Executor) handleTool(parts []string) string {
  1757. if len(parts) == 0 {
  1758. return ColorYellow + "Tools available: ping, traceroute, bandwidth-test\n" + ColorReset
  1759. }
  1760. sub := strings.ToLower(parts[0])
  1761. switch sub {
  1762. case "ping":
  1763. return e.handlePing(parts[1:])
  1764. case "traceroute":
  1765. return e.handleTraceroute(parts[1:])
  1766. case "bandwidth-test", "btest":
  1767. return e.handleBandwidthTest(parts[1:])
  1768. default:
  1769. return ColorRed + "Unknown tool: " + sub + "\n" + ColorReset
  1770. }
  1771. }
  1772. func (e *Executor) handlePing(args []string) string {
  1773. if len(args) == 0 {
  1774. return ColorRed + "Usage: ping <host> [count=4]\n" + ColorReset
  1775. }
  1776. pos, params := parseArgs(args)
  1777. if len(pos) == 0 {
  1778. return ColorRed + "Missing host target for ping\n" + ColorReset
  1779. }
  1780. target := pos[0]
  1781. count := "4"
  1782. if c, ok := params["count"]; ok {
  1783. count = c
  1784. }
  1785. cmd := exec.Command("ping", "-c", count, target)
  1786. out, err := cmd.CombinedOutput()
  1787. if err != nil && len(out) == 0 {
  1788. return ColorRed + "Ping execution failed: " + err.Error() + ColorReset + "\n"
  1789. }
  1790. return string(out)
  1791. }
  1792. func (e *Executor) handleTraceroute(args []string) string {
  1793. if len(args) == 0 {
  1794. return ColorRed + "Usage: traceroute <host>\n" + ColorReset
  1795. }
  1796. pos, _ := parseArgs(args)
  1797. if len(pos) == 0 {
  1798. return ColorRed + "Missing host target for traceroute\n" + ColorReset
  1799. }
  1800. target := pos[0]
  1801. cmd := exec.Command("traceroute", "-n", "-m", "15", target)
  1802. out, err := cmd.CombinedOutput()
  1803. if err != nil {
  1804. cmdFallback := exec.Command("tracepath", "-n", target)
  1805. fbOut, fbErr := cmdFallback.CombinedOutput()
  1806. if fbErr == nil {
  1807. return string(fbOut)
  1808. }
  1809. return ColorRed + "Traceroute failed: " + err.Error() + "\n" + string(out) + ColorReset + "\n"
  1810. }
  1811. return string(out)
  1812. }
  1813. func (e *Executor) handleBandwidthTest(args []string) string {
  1814. if e.bwtestMgr == nil {
  1815. return ColorRed + "Bandwidth test manager not available.\n" + ColorReset
  1816. }
  1817. pos, params := parseArgs(args)
  1818. if len(pos) == 0 {
  1819. return ColorRed + "Usage: /tool bandwidth-test <target-ip> [direction=both|transmit|receive] [protocol=udp|tcp] [duration=5s]\n" + ColorReset
  1820. }
  1821. target := pos[0]
  1822. dir := "both"
  1823. if d, ok := params["direction"]; ok {
  1824. dir = d
  1825. }
  1826. proto := "udp"
  1827. if p, ok := params["protocol"]; ok {
  1828. proto = p
  1829. }
  1830. dur := 5 * time.Second
  1831. if dStr, ok := params["duration"]; ok {
  1832. if dVal, err := time.ParseDuration(dStr); err == nil && dVal >= time.Second {
  1833. dur = dVal
  1834. }
  1835. }
  1836. req := bwtest.SessionRequest{
  1837. Target: target,
  1838. Direction: dir,
  1839. Protocol: proto,
  1840. Duration: int(dur.Seconds()),
  1841. }
  1842. ctx, cancel := context.WithTimeout(context.Background(), dur+3*time.Second)
  1843. defer cancel()
  1844. if err := e.bwtestMgr.StartTest(ctx, req); err != nil {
  1845. return ColorRed + "Failed to initiate bandwidth test: " + err.Error() + ColorReset + "\n"
  1846. }
  1847. time.Sleep(dur + 500*time.Millisecond)
  1848. st := e.bwtestMgr.GetTestStatus()
  1849. out := fmt.Sprintf("Bandwidth test to %s completed:\n", target)
  1850. if st.Summary != nil {
  1851. out += fmt.Sprintf(" Tx Avg : %.2f Mbps\n", st.Summary.AvgTxMbps)
  1852. out += fmt.Sprintf(" Rx Avg : %.2f Mbps\n", st.Summary.AvgRxMbps)
  1853. out += fmt.Sprintf(" Loss : %.1f%%\n", st.Summary.AvgLossPct)
  1854. out += fmt.Sprintf(" Jitter : %.2f ms\n", st.Summary.AvgJitterMs)
  1855. } else if st.CurrentSample != nil {
  1856. out += fmt.Sprintf(" Tx Rate : %.2f Mbps\n", st.CurrentSample.TxMbps)
  1857. out += fmt.Sprintf(" Rx Rate : %.2f Mbps\n", st.CurrentSample.RxMbps)
  1858. } else if st.Error != "" {
  1859. out += fmt.Sprintf(" Error : %s\n", st.Error)
  1860. } else {
  1861. out += " Test finished.\n"
  1862. }
  1863. return out
  1864. }
  1865. // ==================== USER MANAGEMENT ====================
  1866. func (e *Executor) handleUser(parts []string, actor *auth.PublicUser) string {
  1867. if e.authMgr == nil {
  1868. return ColorRed + "Auth manager not available.\n" + ColorReset
  1869. }
  1870. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  1871. users := e.authMgr.ListUsers()
  1872. tbl := NewTable("FLAGS", "NAME", "ROLE", "LAST-LOGIN", "COMMENT")
  1873. for _, u := range users {
  1874. flags := ""
  1875. if u.Disabled {
  1876. flags += "X"
  1877. } else {
  1878. flags += " "
  1879. }
  1880. lastLogin := "never"
  1881. if !u.LastLoginAt.IsZero() {
  1882. lastLogin = u.LastLoginAt.Format("Jan/02 15:04:05")
  1883. }
  1884. tbl.AddRow(flags, u.Username, string(u.Role), lastLogin, u.Comment)
  1885. }
  1886. return tbl.String()
  1887. }
  1888. if actor.Role != auth.RoleFull {
  1889. return ColorRed + "Permission denied: admin role required to manage users\n" + ColorReset
  1890. }
  1891. sub := strings.ToLower(parts[0])
  1892. switch sub {
  1893. case "add":
  1894. _, params := parseArgs(parts[1:])
  1895. name := params["name"]
  1896. pwd := params["password"]
  1897. roleStr := params["role"]
  1898. if roleStr == "" {
  1899. roleStr = "admin"
  1900. }
  1901. comment := params["comment"]
  1902. if name == "" || pwd == "" {
  1903. return ColorRed + "Usage: /user add name=<username> password=<pwd> [role=admin|operator|viewer] [comment=...]\n" + ColorReset
  1904. }
  1905. req := auth.CreateUserRequest{
  1906. Username: name,
  1907. Password: pwd,
  1908. Role: auth.Role(roleStr),
  1909. Comment: comment,
  1910. }
  1911. if err := e.authMgr.CreateUser(req); err != nil {
  1912. return ColorRed + "Error creating user: " + err.Error() + ColorReset + "\n"
  1913. }
  1914. return ColorGreen + fmt.Sprintf("User %s created.\n", name) + ColorReset
  1915. case "remove", "rem", "delete":
  1916. pos, _ := parseArgs(parts[1:])
  1917. if len(pos) == 0 {
  1918. return ColorRed + "Usage: /user remove <username>\n" + ColorReset
  1919. }
  1920. name := pos[0]
  1921. if err := e.authMgr.DeleteUser(name, actor.Username); err != nil {
  1922. return ColorRed + "Error removing user: " + err.Error() + ColorReset + "\n"
  1923. }
  1924. return ColorGreen + fmt.Sprintf("User %s removed.\n", name) + ColorReset
  1925. case "set":
  1926. pos, params := parseArgs(parts[1:])
  1927. if len(pos) == 0 {
  1928. return ColorRed + "Usage: /user set <username> [password=...] [role=...] [disabled=yes|no]\n" + ColorReset
  1929. }
  1930. name := pos[0]
  1931. pub, err := e.authMgr.GetPublicUser(name)
  1932. if err != nil || pub == nil {
  1933. return ColorRed + "User not found\n" + ColorReset
  1934. }
  1935. role := pub.Role
  1936. if rStr, ok := params["role"]; ok {
  1937. role = auth.Role(rStr)
  1938. }
  1939. dis := pub.Disabled
  1940. if dStr, ok := params["disabled"]; ok {
  1941. dis = (strings.ToLower(dStr) == "yes" || strings.ToLower(dStr) == "true")
  1942. }
  1943. cmt := pub.Comment
  1944. if c, ok := params["comment"]; ok {
  1945. cmt = c
  1946. }
  1947. req := auth.UpdateUserRequest{
  1948. Role: role,
  1949. Disabled: dis,
  1950. Comment: cmt,
  1951. }
  1952. if err := e.authMgr.UpdateUser(name, req); err != nil {
  1953. return ColorRed + "Error updating user: " + err.Error() + ColorReset + "\n"
  1954. }
  1955. if pwd, ok := params["password"]; ok && pwd != "" {
  1956. if err := e.authMgr.ChangePassword(name, "", pwd, true); err != nil {
  1957. return ColorRed + "Error resetting password: " + err.Error() + ColorReset + "\n"
  1958. }
  1959. }
  1960. return ColorGreen + fmt.Sprintf("User %s updated.\n", name) + ColorReset
  1961. default:
  1962. return ColorRed + "Usage: /user [print|add|remove|set]\n" + ColorReset
  1963. }
  1964. }
  1965. // ==================== SYSTEM ====================
  1966. func (e *Executor) handleSystem(parts []string, user *auth.PublicUser) string {
  1967. if len(parts) == 0 {
  1968. return ColorYellow + "System commands: resource print, reboot, identity print, identity set name=...\n" + ColorReset
  1969. }
  1970. sub := strings.ToLower(parts[0])
  1971. switch sub {
  1972. case "resource":
  1973. st, err := system.GetStatus()
  1974. if err != nil {
  1975. return ColorRed + "Error retrieving system resources: " + err.Error() + ColorReset + "\n"
  1976. }
  1977. freeMemMB := uint64(0)
  1978. if st.MemTotalMB > st.MemUsedMB {
  1979. freeMemMB = st.MemTotalMB - st.MemUsedMB
  1980. }
  1981. out := " uptime: " + formatUptime(int64(st.UptimeSeconds)) + "\n"
  1982. out += fmt.Sprintf(" version: DebTik RouterOS 1.0 (Kernel %s)\n", st.Kernel)
  1983. out += fmt.Sprintf(" free-memory: %d MiB\n", freeMemMB)
  1984. out += fmt.Sprintf(" total-memory: %d MiB\n", st.MemTotalMB)
  1985. out += fmt.Sprintf(" cpu-count: %d\n", st.CPUCores)
  1986. out += fmt.Sprintf(" cpu-load: %.1f%%\n", st.CPUPct)
  1987. out += fmt.Sprintf(" os: %s\n", st.OS)
  1988. return out
  1989. case "identity":
  1990. if len(parts) > 1 && parts[1] == "set" {
  1991. if user.Role != auth.RoleFull {
  1992. return ColorRed + "Permission denied: admin role required\n" + ColorReset
  1993. }
  1994. _, params := parseArgs(parts[2:])
  1995. name := params["name"]
  1996. if name == "" {
  1997. return ColorRed + "Usage: /system identity set name=<new-name>\n" + ColorReset
  1998. }
  1999. if err := exec.Command("hostnamectl", "set-hostname", name).Run(); err != nil {
  2000. return ColorRed + "Error setting identity: " + err.Error() + ColorReset + "\n"
  2001. }
  2002. return ColorGreen + fmt.Sprintf("System identity set to %q.\n", name) + ColorReset
  2003. }
  2004. st, _ := system.GetStatus()
  2005. h := "DebTik"
  2006. if st != nil && st.Hostname != "" {
  2007. h = st.Hostname
  2008. }
  2009. return fmt.Sprintf(" name: %s\n", h)
  2010. case "reboot":
  2011. if user.Role != auth.RoleFull {
  2012. return ColorRed + "Permission denied: admin role required to reboot\n" + ColorReset
  2013. }
  2014. go func() {
  2015. time.Sleep(1 * time.Second)
  2016. _ = exec.Command("systemctl", "reboot").Run()
  2017. }()
  2018. return ColorYellow + "System reboot initiated! Connection will terminate.\n" + ColorReset
  2019. default:
  2020. return ColorRed + "Unknown system subcommand: " + sub + "\n" + ColorReset
  2021. }
  2022. }
  2023. func (e *Executor) showHelp() string {
  2024. b := strings.Builder{}
  2025. b.WriteString(ColorCyan + ColorBold + "DebTik RouterOS CLI Command Directory:\n" + ColorReset)
  2026. b.WriteString(" /interface - Network interface manager (print, enable, disable, set)\n")
  2027. b.WriteString(" /interface vlan - VLAN virtual interfaces (print, add, remove)\n")
  2028. b.WriteString(" /interface bridge - L2 Bridge and ports (print, add, remove, port)\n")
  2029. b.WriteString(" /interface wireguard - WireGuard tunnels & peers (print, add, peers)\n")
  2030. b.WriteString(" /interface eoip - Ethernet over IP GRE tunnels (print, add, remove)\n")
  2031. b.WriteString(" /interface vrrp - Virtual Router Redundancy Protocol (print, add, remove)\n")
  2032. b.WriteString(" /interface pppoe-client - PPPoE WAN Client sessions (print, add, connect)\n")
  2033. b.WriteString(" /ip\n")
  2034. b.WriteString(" /ip address - IP address assignments (print, add, remove)\n")
  2035. b.WriteString(" /ip route - Static routes & tables (print, add, remove, rule)\n")
  2036. b.WriteString(" /ip dhcp-client - DHCP client on WAN links (print, add, renew, release)\n")
  2037. b.WriteString(" /ip dhcp-server - Local DHCP server & leases (print, add, lease, network)\n")
  2038. b.WriteString(" /ip dns - Router DNS resolver (print, set, cache flush)\n")
  2039. b.WriteString(" /ip firewall - Netfilter / iptables firewall\n")
  2040. b.WriteString(" /ip firewall filter - Traffic filtering rules (print, add, remove)\n")
  2041. b.WriteString(" /ip firewall nat - Source & destination NAT rules (print, add, remove)\n")
  2042. b.WriteString(" /ip firewall mangle - Packet marking and QoS (print, add, remove)\n")
  2043. b.WriteString(" /ip firewall address-list - Dynamic & static address groups (print, add, remove)\n")
  2044. b.WriteString(" /ip service - Management services: Telnet, SSH, Web (print, enable, disable, set)\n")
  2045. b.WriteString(" /routing - Dynamic routing engine\n")
  2046. b.WriteString(" /routing bgp - BGP instance, peers, routes (instance, peer, routes)\n")
  2047. b.WriteString(" /routing ospf - OSPF areas, interfaces, neighbors (instance, area, neighbor)\n")
  2048. b.WriteString(" /routing filter - Route filtering policies (print)\n")
  2049. b.WriteString(" /tool - Diagnostics & performance testing\n")
  2050. b.WriteString(" /tool ping <host> - ICMP ping test\n")
  2051. b.WriteString(" /tool traceroute <host> - Route trace analysis\n")
  2052. b.WriteString(" /tool bandwidth-test <host> - High performance throughput & jitter test\n")
  2053. b.WriteString(" /log - System activity logs (print [limit=N] [where=filter])\n")
  2054. b.WriteString(" /user - Local user accounts (print, add, remove, set)\n")
  2055. b.WriteString(" /system - Router hardware and core controls\n")
  2056. b.WriteString(" /system resource print - CPU, RAM, disk, uptime metrics\n")
  2057. b.WriteString(" /system identity - Hostname identity (print, set name=...)\n")
  2058. b.WriteString(" /system reboot - Safe system restart\n")
  2059. b.WriteString(" quit / exit - Disconnect session\n")
  2060. return b.String()
  2061. }
  2062. // ==================== CERTIFICATES (/certificate) ====================
  2063. func (e *Executor) handleCertificate(parts []string, user *auth.PublicUser) string {
  2064. if e.certMgr == nil {
  2065. return ColorRed + "Certificates manager not available.\n" + ColorReset
  2066. }
  2067. if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
  2068. list := e.certMgr.ListCertificates()
  2069. tbl := NewTable("#", "NAME", "COMMON-NAME", "TYPE", "ISSUER", "EXPIRES", "SANS")
  2070. for idx, c := range list {
  2071. tStr := ColorGreen + c.Type + ColorReset
  2072. if c.IsCA {
  2073. tStr = ColorCyan + "ca" + ColorReset
  2074. }
  2075. expStr := c.ValidTo.Format("Jan/02/2006")
  2076. if time.Now().After(c.ValidTo) {
  2077. expStr = ColorRed + expStr + " (EXPIRED)" + ColorReset
  2078. }
  2079. sansStr := strings.Join(c.SANs, ",")
  2080. if sansStr == "" {
  2081. sansStr = "-"
  2082. }
  2083. tbl.AddRow(fmt.Sprintf("%d", idx), c.Name, c.CommonName, tStr, c.Issuer, expStr, sansStr)
  2084. }
  2085. return tbl.String()
  2086. }
  2087. if user.Role != auth.RoleFull {
  2088. return ColorRed + "Permission denied: admin role required to manage certificates\n" + ColorReset
  2089. }
  2090. sub := strings.ToLower(parts[0])
  2091. switch sub {
  2092. case "add-ca":
  2093. _, params := parseArgs(parts[1:])
  2094. name := params["name"]
  2095. cn := params["common-name"]
  2096. if name == "" || cn == "" {
  2097. return ColorRed + "Usage: /certificate add-ca name=<name> common-name=<cn> [days=<days>] [key-type=RSA-2048|RSA-4096|ECDSA-P256]\n" + ColorReset
  2098. }
  2099. days := 3650
  2100. if dStr, ok := params["days"]; ok {
  2101. if dVal, err := strconv.Atoi(dStr); err == nil && dVal > 0 {
  2102. days = dVal
  2103. }
  2104. }
  2105. keyType := params["key-type"]
  2106. if keyType == "" {
  2107. keyType = "RSA-2048"
  2108. }
  2109. info, err := e.certMgr.CreateCA(certs.CreateCARequest{
  2110. Name: name,
  2111. CommonName: cn,
  2112. Days: days,
  2113. KeyType: keyType,
  2114. Comment: params["comment"],
  2115. })
  2116. if err != nil {
  2117. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  2118. }
  2119. return ColorGreen + fmt.Sprintf("Certificate Authority %q created successfully.\n", info.Name) + ColorReset
  2120. case "add":
  2121. _, params := parseArgs(parts[1:])
  2122. name := params["name"]
  2123. cn := params["common-name"]
  2124. if name == "" || cn == "" {
  2125. return ColorRed + "Usage: /certificate add name=<name> common-name=<cn> [ca=<ca_name>] [sans=<san1,san2>] [days=<days>] [key-type=...]\n" + ColorReset
  2126. }
  2127. days := 365
  2128. if dStr, ok := params["days"]; ok {
  2129. if dVal, err := strconv.Atoi(dStr); err == nil && dVal > 0 {
  2130. days = dVal
  2131. }
  2132. }
  2133. var sans []string
  2134. if sStr, ok := params["sans"]; ok {
  2135. for _, s := range strings.Split(sStr, ",") {
  2136. if s = strings.TrimSpace(s); s != "" {
  2137. sans = append(sans, s)
  2138. }
  2139. }
  2140. }
  2141. keyType := params["key-type"]
  2142. if keyType == "" {
  2143. keyType = "RSA-2048"
  2144. }
  2145. info, err := e.certMgr.CreateCertificate(certs.CreateCertRequest{
  2146. Name: name,
  2147. CommonName: cn,
  2148. CAName: params["ca"],
  2149. SANs: sans,
  2150. Days: days,
  2151. KeyType: keyType,
  2152. IsServer: true,
  2153. Comment: params["comment"],
  2154. })
  2155. if err != nil {
  2156. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  2157. }
  2158. return ColorGreen + fmt.Sprintf("Certificate %q created successfully (Issuer: %s).\n", info.Name, info.Issuer) + ColorReset
  2159. case "issue-letsencrypt":
  2160. _, params := parseArgs(parts[1:])
  2161. domain := params["domain"]
  2162. email := params["email"]
  2163. if domain == "" || email == "" {
  2164. return ColorRed + "Usage: /certificate issue-letsencrypt domain=<domain> email=<email> [name=<name>] [staging=yes|no]\n" + ColorReset
  2165. }
  2166. name := params["name"]
  2167. if name == "" {
  2168. name = domain
  2169. }
  2170. staging := strings.ToLower(params["staging"]) == "yes" || strings.ToLower(params["staging"]) == "true"
  2171. ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
  2172. defer cancel()
  2173. info, err := e.certMgr.IssueLetsEncrypt(ctx, certs.LetsEncryptRequest{
  2174. Name: name,
  2175. Domain: domain,
  2176. Email: email,
  2177. Staging: staging,
  2178. Comment: params["comment"],
  2179. })
  2180. if err != nil {
  2181. return ColorRed + "Let's Encrypt Error: " + err.Error() + ColorReset + "\n"
  2182. }
  2183. return ColorGreen + fmt.Sprintf("Let's Encrypt certificate %q issued successfully!\n", info.Name) + ColorReset
  2184. case "export":
  2185. pos, _ := parseArgs(parts[1:])
  2186. if len(pos) == 0 {
  2187. return ColorRed + "Usage: /certificate export <name>\n" + ColorReset
  2188. }
  2189. certPEM, keyPEM, err := e.certMgr.ExportPEM(pos[0])
  2190. if err != nil {
  2191. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  2192. }
  2193. return fmt.Sprintf("----- CERTIFICATE -----\n%s\n----- PRIVATE KEY -----\n%s\n", certPEM, keyPEM)
  2194. case "remove", "rem", "delete":
  2195. pos, _ := parseArgs(parts[1:])
  2196. if len(pos) == 0 {
  2197. return ColorRed + "Usage: /certificate remove <name>\n" + ColorReset
  2198. }
  2199. name := pos[0]
  2200. if e.servicesMgr != nil {
  2201. if sslSvc, ok := e.servicesMgr.GetService("www-ssl"); ok {
  2202. if strings.EqualFold(sslSvc.Certificate, name) && !sslSvc.Disabled {
  2203. return ColorRed + "Cannot remove certificate: actively used by enabled www-ssl service\n" + ColorReset
  2204. }
  2205. }
  2206. }
  2207. if err := e.certMgr.DeleteCertificate(name); err != nil {
  2208. return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
  2209. }
  2210. return ColorGreen + fmt.Sprintf("Certificate %s removed.\n", name) + ColorReset
  2211. default:
  2212. return ColorRed + "Usage: /certificate [print|add-ca|add|issue-letsencrypt|export|remove]\n" + ColorReset
  2213. }
  2214. }
  2215. func formatUptime(seconds int64) string {
  2216. d := seconds / 86400
  2217. h := (seconds % 86400) / 3600
  2218. m := (seconds % 3600) / 60
  2219. s := seconds % 60
  2220. if d > 0 {
  2221. return fmt.Sprintf("%dd %02dh %02dm %02ds", d, h, m, s)
  2222. }
  2223. return fmt.Sprintf("%02dh %02dm %02ds", h, m, s)
  2224. }
  2225. func formatBytes(bytes int64) string {
  2226. const unit = 1024
  2227. if bytes < unit {
  2228. return fmt.Sprintf("%d B", bytes)
  2229. }
  2230. div, exp := int64(unit), 0
  2231. for n := bytes / unit; n >= unit; n /= unit {
  2232. div *= unit
  2233. exp++
  2234. }
  2235. return fmt.Sprintf("%.1f %ciB", float64(bytes)/float64(div), "KMGTPE"[exp])
  2236. }
  2237. // Helpers for sorting/filtering
  2238. func sortStrings(s []string) {
  2239. sort.Strings(s)
  2240. }