executor.go 72 KB

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