module(..., package.seeall) -- Load libraries require("modelfunctions") require("validator") require("fs") require("format") -- Set variables local configfile = "/etc/ssh/sshd_config" local processname = "sshd" local packagename = "openssh-server" local header = "SSH" local path="PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin " -- ################################################################################ -- LOCAL FUNCTIONS -- return "Yes" or "No" on true/false or value as string local function config_value(value) if type(value) == "boolean" then if value then return "yes" else return "no" end end return tostring(value) end local function validate_config(config) local success = true if not validator.is_ipv4(config.value.ListenAddress.value) then config.value.ListenAddress.errtxt = "Invalid IP" success = false end if not validator.is_port(config.value.Port.value) then config.value.Port.value = "Invalid Port" success = false end return success, config end -- ################################################################################ -- PUBLIC FUNCTIONS function startstop_service(action) return modelfunctions.startstop_service(processname, action) end function getstatus() return modelfunctions.getstatus(processname, packagename, header .. " status") end function getconfigfile() return modelfunctions.getfiledetails(configfile) end function setconfigfile(filedetails) return modelfunctions.setfiledetails(filedetails, {configfile}) end function read_config() local output = {} output.Port = cfe({ value=22, label="Port" }) output.ListenAddress = cfe({ value="0.0.0.0", label="Listen address" }) output.PermitRootLogin = cfe({ type="boolean", value=true, label="Permit Root Login" }) output.PasswordAuthentication = cfe({ type="boolean", value=true, label="Password Authentication" }) output.UseDNS = cfe({ type="boolean", value=true, label="Use DNS" }) local config = format.parse_configfile(fs.read_file(configfile) or "") if config then output.Port.value = config.Port or output.Port.value output.ListenAddress.value = config.ListenAddress or output.ListenAddress.value output.PermitRootLogin.value = not (config.PermitRootLogin == "no") output.PasswordAuthentication.value = not (config.PasswordAuthentication == "no") output.UseDNS.value = not (config.UseDNS == "no") end return cfe({ type="group", value=output, label="OpenSSH Config" }) end function update_config(config) local success, config = validate_config(config) if success then for name,val in pairs(config.value) do val.line = name.." "..config_value(val.value) end local lines = {} for line in string.gmatch(fs.read_file(configfile) or "", "([^\n]*)\n?") do for name,val in pairs(config.value) do if val.line and string.find(line, "^%s*#?%s*"..name) then if string.find(line, "^%s*#") then lines[#lines+1] = val.line else line = val.line end val.line = nil end end lines[#lines+1] = line end for name,val in pairs(config.value) do if val.line then lines[#lines+1] = val.line val.line = nil end end fs.write_file(configfile, string.gsub(table.concat(lines, "\n"), "\n+$", "")) else config.errtxt = "Failed to save config" end return config end function list_conn_peers() local output = {} local netstat = {} local ps = {} local who = {} config = read_config() local f = io.popen( path .. 'netstat -lnaW' ) local flines = format.search_for_lines(f:read("*a") or "", "ESTABLISHED") f:close() local g = io.popen( path .. 'netstat -laW' ) local glines = format.search_for_lines(g:read("*a") or "", "ESTABLISHED") g:close() for i,line in ipairs(flines) do local loc, peer = string.match(line, "^%S+%s+%S+%s+%S+%s+(%S+)%s+(%S+)") if loc then peer = string.match(peer, "%d+%.%d+%.%d+%.%d+") if string.find(loc, ":"..config.value.Port.value.."$") and peer then if not netstat[peer] then local name = string.match(glines[i], "^%S+%s+%S+%s+%S+%s+%S+%s+(%S+)") name = string.gsub(name, ":.*", "") netstat[peer] = {cnt=0, name=name} end netstat[peer].cnt = netstat[peer].cnt + 1 end end end local f = io.popen( path .. 'ps' ) for i,line in ipairs(format.search_for_lines(f:read("*a") or "", "sshd:")) do table.insert(ps, string.match(line,"@(%S+)")) end f:close() local f = io.popen( path .. 'who' ) local who = f:read("*a") or "" f:close() for peer,v in pairs(netstat) do if not (netstat[peer].tty) then netstat[peer].tty = {} end for line in string.gmatch(who, "[^\n]+") do if string.find(line, peer) or (v.name ~= "" and string.find(line, v.name)) then for j,l in ipairs(ps) do if string.find(line, l) then local user,tty,idle,time = string.match(line, "^(%S*)%s*(%S*)%s*(%S*)%s*(%S*%s*%S*%s*%S*)") table.insert(netstat[peer].tty, { user=user, tty=tty, idle=idle, time=time, }) break end end end end table.insert(output, v) output[#output]['host'] = peer end return output end function list_users() local users = {"root"} for dir in fs.find(null, "/home/") do local user = basename(dir) if fs.is_dir(dir) and not string.find(user, "^%.") then users[#users + 1] = user end end return cfe({ type="list", value=users, label="User list" }) end function list_auths(user) user = user or "root" local cmdresult = cfe({ type="group", value={}, label="Authorized Key List" }) cmdresult.value.user = cfe({ value=user, label="User" }) cmdresult.value.auth = cfe({ type="structure", value={}, label="Authorized Keys" }) if not user == "root" and (string.find(user, "/") or not fs.is_dir("/home/"..user)) then cmdresult.value.user.errtxt = "Invalid user" else local file = "/"..user.."/.ssh/authorized_keys" if user ~= "root" then file = "/home"..file end local data = fs.read_file(file) or "" for line in string.gmatch(data, "([^\n]+)\n?") do local typ,key,id = string.match(line, "(%S+)%s(%S+)%s(%S+)") table.insert(cmdresult.value.auth.value, {key=key, id=id}) end end return cmdresult end function delete_auth(user, auth) user = user or "root" local cmdresult = cfe({ value="Failed to delete key", errtxt="User not found", label="Delete Authorized Key Result" }) if user == "root" or (not string.find(user, "/") and fs.is_dir("/home/"..user)) then cmdresult.errtxt = "Key not found" local file = "/"..user.."/.ssh/authorized_keys" if user ~= "root" then file = "/home"..file end local data = fs.read_file(file) if data then local newdata = {} for line in string.gmatch(data, "([^\n]+)\n?") do if string.match(line, "%s(%S+)$") == auth then cmdresult.value = "Deleted key" cmdresult.errtxt = nil else newdata[#newdata + 1] = line end end if not cmdresult.errtxt then fs.write_file(file, table.concat(newdata, "\n")) end end end return cmdresult end function get_auth(user) user = user or "root" local cmdresult = cfe({ type="group", value={}, label="Authorized Key List" }) cmdresult.value.user = cfe({ value=user, label="User" }) cmdresult.value.cert = cfe({ type="longtext", label="SSH Certificate Contents" }) return cmdresult end function create_auth(authstr) authstr.value.user.value = authstr.value.user.value or "root" local success = true if not authstr.value.user.value == "root" and (string.find(authstr.value.user.value, "/") or not fs.is_dir("/home/"..authstr.value.user.value)) then authstr.value.user.errtxt = "Invalid user" success = false end -- not sure how to validate the cert authstr.value.cert.value = string.match(authstr.value.cert.value, "^[%s\n]*(.*%S)[%s\n]*$") or "" if authstr.value.cert.value == "" then authstr.value.cert.errtxt = "Cert cannot be empty" success = false elseif not string.match(authstr.value.cert.value, "ssh%-%S+%s%S+%s%S+$") then authstr.value.cert.errtxt = "Invalid format" success = false end if success then local file = "/"..authstr.value.user.value.."/.ssh/authorized_keys" if authstr.value.user.value ~= "root" then file = "/home"..file end local data = fs.read_file(file) or "" if string.match(data, "^[%s\n]*$") then data = authstr.value.cert.value else data = string.match(data, "^[%s\n]*(.*%S)[%s\n]*$") for id in string.gmatch(data, "([^\n]+)\n?") do if string.match(id, "%S+$") == string.match(authstr.value.cert.value, "%S+$") then authstr.value.cert.errtxt = "This ID already exists" success = false break end end data = string.gsub(data, "\n*$", "\n"..authstr.value.cert.value) end if success then fs.write_file(file, data) end end if not success then authstr.errtxt = "Failed to add key" end return authstr end