module (..., package.seeall) require ("modelfunctions") require ("posix") require ("format") require ("fs") require ("processinfo") require ("validator") require ("date") local processname = "openvpn" local packagename = "openvpn" local baseurl = "/etc/openvpn/" -- ################################################################################ -- LOCAL FUNCTIONS local function config_content( f ) local config = {} local lines = format.parse_linesandwords(fs.read_file(f) or "", "[#;]") -- there can be multiple entries for i,linetable in ipairs(lines) do if config[linetable[1]] then config[linetable[1]] = config[linetable[1]] .. "\n" .. (table.concat(linetable, " ", 2) or "") else config[linetable[1]] = table.concat(linetable, " ", 2) or "" end end config.name = f if config.remote then config.remoteport = string.match ( config.remote, "^%S+%s+(%S*)" ) end if not ( config.log ) then config.log = config["log-append"] end if not ( config["max-clients"] ) then config["max-clients"] = "Unlimited" end if not ( config["local"] ) then config["local"] = "0.0.0.0" end return config end local is_running = function( process, parameters ) local retval = "Stopped" for i,v in ipairs(processinfo.pidof(process) or {}) do local path = string.gsub("/proc/".. v .. "/cmdline", "%s", "") local f = io.open(path,r) local file_resultx = f:read("*a") local file_result = string.match(file_resultx, parameters) f:close() if ( file_result ) then retval = "Running" end end return retval end local function check_valid_config (config) config.errtxt = nil if not (config.client) or not (config.ca) or not (config.cert) or not (config.key) or not (config.dev) or not (config.proto) or not (config.remote) then config.errtxt = "" if not (config.ca) then config.errtxt = config.errtxt .. "Check CA; " end if not (config.cert) then config.errtxt = config.errtxt .. "Check CERT; " end if not (config.key) then config.errtxt = config.errtxt .. "Check KEY; " end if not (config.dev) then config.errtxt = config.errtxt .. "Check DEV; " end if not (config.proto) then config.errtxt = config.errtxt .. "Check PROTO; " end if (config.client) or not (config.ca) or not (config.cert) or not (config.key) or not (config.dev) or not (config.proto) or not (config.port) then config.type = nil else config.type = "server" config.errtxt = nil end else config.type = "client" config.errtxt = nil end if not (config.type) then config.type = "unknown" end return config.type, config.errtxt end local function list_conffiles() local configfiles = {} for file in fs.find(".*conf", baseurl) do if fs.is_file(file) then configfiles[#configfiles+1] = file end end return configfiles end local function clientlist( statusfile ) local clientlist = {} local routinglist = {} local datechange = {} local list = {} if (statusfile) then local f = fs.read_file_as_array( statusfile ) local clientlst = false local routinglst = false if ( f ) then for k,v in ipairs(f) do local col = format.string_to_table(v, ",") if ( col[1] == "ROUTING TABLE" ) or ( col[1] == "GLOBAL STATS" ) then clientlst = false routinglst = false end if ( clientlst ) then table.insert(clientlist, { CN=col[1], REALADDR=col[2], BYTESRCV=col[3], BYTESSND=col[4], CONN=col[5] } ) end if ( routinglst ) then table.insert(routinglist, { VIRTADDR=col[1], CN=col[2], REALADDR=col[3], LAST=col[4] } ) if (col[4]) then local month,day,hour,min,sec,year = string.match(col[4],"^%S+%s+(%S+)%s+(%S+)%s+(%d%d):(%d%d):(%d%d)%s+(%S+)") table.insert(datechange, { year=year, month=date.abr_month_num(month), day=day, hour=hour, min=min, sec=sec } ) end end if ( col[1] == "Virtual Address" ) then routinglst = true end if ( col[1] == "Common Name" ) then clientlst = true end end end end -- JOIN 'CLIENT_LIST' and 'ROUTING_LIST' TABLES INTO ONE TABLE AND LATER ON PRESENT THIS TABLE for k,v in ipairs(clientlist) do for kk,vv in ipairs(routinglist) do if ( v.CN == vv.CN ) then table.insert(list, { CN=v.CN, REALADDR=v.REALADDR, BYTESRCV=v.BYTESRCV, BYTESSND=v.BYTESSND, VIRTADDR=vv.VIRTADDR, CONN=v.CONN, LAST = LAST } ) end end end local lastdatechangetxt, lastdatechangediff if ( #clientlist > 0 ) then local lastdatechange = date.date_to_seconds(datechange) lastdatechangetxt = os.date("%c", lastdatechange[#lastdatechange]) lastdatechangediff = os.time() - os.date(lastdatechange[table.maxn(lastdatechange)]) if (lastdatechangediff > 60) then lastdatechangediff = math.modf(lastdatechangediff / 60) .. " min" else lastdatechangediff = lastdatechangediff .. " sec" end end return list, #clientlist, lastdatechangetxt, lastdatechangediff end -- ################################################################################ -- PUBLIC FUNCTIONS function getstatus() local status = modelfunctions.getstatus(processname, packagename, "OpenVPN Status") status.value.autostart = nil status.value.status = nil return status end function getclientinfo(f) local config = config_content(f) return cfe({ type="structure", value=clientlist(config.status), label="Client info" }) end function get_config(f) local config = config_content(f) check_valid_config(config) if config.type == "server" then local clientlist, client_count, client_lastupdate, client_lastdatechangediff = clientlist(config.status) config["client_lastupdate"] = client_lastupdate or "?" config["client_lastdatechangediff"] = client_lastdatechangediff or "? min" config["client_count"] = client_count or 0 end config["status_isrunning"] = is_running ("openvpn", basename(f)) return cfe({ type="structure", value=config, label="OpenVPN Config" }) end function get_logfile(f) local config = config_content(f) return cfe({ value=config.log or "", label="Config file" }) end function get_conflist () local configlist = {} for i,file in ipairs(list_conffiles()) do config = config_content ( file ) local conf_type, errtxt = check_valid_config(config) local isrunning = is_running ("openvpn", basename(file)) local clientlist, connclients = clientlist (config.status) table.insert ( configlist, { name = file, type = conf_type, errtxt = errtxt, status = isrunning, clients = connclients } ) end return cfe({ type="structure", value=configlist, label="Configuration List" }) end function get_filecontent(f) --FIXME validate return modelfunctions.getfiledetails(f, list_conffiles()) end function update_filecontent(filedetails) --FIXME validate return modelfunctions.setfiledetails(filedetails, list_conffiles()) end function create_new_config() config = { name = cfe({ label="File Name", descr="File name should end with \".conf\"" }), } return cfe({ type="group", value=config, label="Config" }) end function create_config(config) local success = true local path = config.value.name.value if not string.find(path, "/") then path = baseurl .. path end if not validator.is_valid_filename(path, baseurl) then success = false config.value.name.errtxt = "Invalid path" elseif posix.stat(path) then success = false config.value.name.errtxt = "File already exists" elseif not string.match(path,".*%.conf") then success = false config.value.name.errtxt = "Filename must end with '.conf'" end if success then fs.create_file(path) else config.errtxt = "Failed to create config" end return config end function delete_config(name) local cmdresult = cfe({ label="Delete config result", errtxt="Failed to delete config - not found" }) if validator.is_valid_filename(name, baseurl) and fs.is_file(name) then os.remove(name) cmdresult.value = "Config Deleted" cmdresult.errtxt = nil end return cmdresult end function startstop_service(action,service) if (service ~= "openvpn") then if ( fs.is_file(baseurl .. service .. ".conf") ) then service = "openvpn." .. tostring(service) if not ( fs.is_file("/etc/init.d/" .. service ) ) then -- Create missing symlinks posix.link("/etc/init.d/openvpn","/etc/init.d/" .. service, true) end else return nil end end return modelfunctions.startstop_service(service, action) end