local mymodule = {} modelfunctions = require("modelfunctions") posix = require("posix") format = require("acf.format") fs = require("acf.fs") processinfo = require("acf.processinfo") validator = require("acf.validator") date = require("acf.date") local processname = "slapd" local packagename = "openldap" local configfile = "/etc/openldap/slapd.conf" local baseurl = "/etc/openldap/" function mymodule.set_processname(p) processname = p configfile = "/etc/openldap/"..processname..".conf" end -- ################################################################################ -- 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 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 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) and (#datechange > 0)) then local lastdatechange = date.date_to_seconds(datechange) lastdatechangetxt = os.date("%c", lastdatechange[#lastdatechange]) lastdatechangediff = os.time() - os.date(lastdatechange[#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 mymodule.getstatus() return modelfunctions.getstatus(processname, packagename, "OpenLDAP Status") end function mymodule.get_startstop(self, clientdata) return modelfunctions.get_startstop(processname) end function mymodule.startstop_service(self, startstop, action) return modelfunctions.startstop_service(startstop, action) end function mymodule.getclientinfo() local config = config_content(configfile) return cfe({ type="structure", value=clientlist(config.status), label="Client info" }) end function mymodule.get_config() local config = config_content(configfile) 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 return cfe({ type="structure", value=config, label="OpenVPN Config" }) end function mymodule.get_logfile(f) local config = config_content(configfile) return cfe({ value=config.log or "", label="Log file" }) end function mymodule.get_filecontent() --FIXME validate return modelfunctions.getfiledetails(configfile) end function mymodule.update_filecontent(self, filedetails) --FIXME validate return modelfunctions.setfiledetails(self, filedetails, {configfile}) end function mymodule.list_certs() local list = {} for file in fs.find(".*%.pem", certurl) do list[#list+1] = file end return cfe({ type="list", value=list, label="OpenVPN Certificates" }) end function mymodule.get_delete_cert(self, clientdata) local retval = {} retval.cert = cfe({ value=clientdata.cert or "", label="Certificate Local Name" }) return cfe({ type="group", value=retval, label="Delete Certificate" }) end function mymodule.delete_cert(self, delcert) local list = mymodule.list_certs() delcert.value.cert.errtxt = "Invalid cert name" delcert.errtxt = "Failed to delete certificate" for i,cert in ipairs(list.value) do if cert == delcert.value.cert.value then os.remove(cert) delcert.value.cert.errtxt = nil delcert.errtxt = nil break end end return delcert end function mymodule.new_upload_cert() local value = {} value.cert = cfe({ type="raw", value=0, label="Certificate", descr='File must be a password protected ".pfx" file' }) value.password = cfe({ label="Certificate Password" }) value.name = cfe({ label="Certificate Local Name" }) return cfe({ type="group", value=value }) end function mymodule.upload_cert(self, newcert) local success = true -- Trying to upload a cert/key -- The way haserl works, cert contains the temporary file name -- First, get the cert local cmd, f, cmdresult, errtxt if validator.is_valid_filename(newcert.value.cert.value, "/tmp/") and fs.is_file(newcert.value.cert.value) then cmdresult, errtxt = modelfunctions.run_executable({"openssl", "pkcs12", "-in", newcert.value.cert.value, "-out", newcert.value.cert.value.."cert.pem", "-password", "pass:"..newcert.value.password.value, "-nokeys", "-clcerts"}, true) local filestats = posix.stat(newcert.value.cert.value.."cert.pem") if not filestats or filestats.size == 0 then newcert.value.cert.errtxt = "Could not open certificate\n"..(errtxt or cmdresult) success = false end else newcert.value.cert.errtxt = "Invalid certificate" success = false end -- Now, get the key and the ca certs if success then cmdresult, errtxt = modelfunctions.run_executable({"openssl", "pkcs12", "-in", newcert.value.cert.value, "-out", newcert.value.cert.value.."key.pem", "-password", "pass:"..newcert.value.password.value, "-nocerts", "-nodes"}, true) filestats = posix.stat(newcert.value.cert.value.."key.pem") if not filestats or filestats.size == 0 then newcert.value.cert.errtxt = "Could not find key\n"..(errtxt or cmdresult) success = false end cmdresult, errtxt = modelfunctions.run_executable({"openssl", "pkcs12", "-in", newcert.value.cert.value, "-out", newcert.value.cert.value.."ca.pem", "-password", "pass:"..newcert.value.password.value, "-nokeys", "-cacerts"}, true) filestats = posix.stat(newcert.value.cert.value.."ca.pem") if not filestats or filestats.size == 0 then newcert.value.cert.errtxt = "Could not find CA certs\n"..(errtxt or cmdresult) success = false end end if newcert.value.name.value == "" then newcert.value.name.errtxt = "Cannot be blank" success = false elseif posix.stat(certurl..newcert.value.name.value.."-cert.pem") or posix.stat(certurl..newcert.value.name.value.."-key.pem") or posix.stat(certurl..newcert.value.name.value.."-ca.pem") then newcert.value.name.errtxt = "Certificate of this name already exists" success = false end if success then if not posix.stat(certurl) then fs.create_directory(certurl) end -- copy the keys fs.move_file(newcert.value.cert.value.."cert.pem", certurl..newcert.value.name.value.."-cert.pem") fs.move_file(newcert.value.cert.value.."key.pem", certurl..newcert.value.name.value.."-key.pem") fs.move_file(newcert.value.cert.value.."ca.pem", certurl..newcert.value.name.value.."-ca.pem") posix.chmod(certurl..newcert.value.name.value.."-key.pem", "rw-------") else newcert.errtxt = "Failed to upload certificate" end -- Delete the temporary files if validator.is_valid_filename(newcert.value.cert.value, "/tmp/") and fs.is_file(newcert.value.cert.value) then os.remove(newcert.value.cert.value.."cert.pem") os.remove(newcert.value.cert.value.."key.pem") os.remove(newcert.value.cert.value.."ca.pem") end return newcert end mymodule.view_cert = function(certname) local cmdresult = "Invalid cert name" local errtxt if not string.find(certname, "/") then certname = certurl..certname end if validator.is_valid_filename(certname, certurl) or validator.is_valid_filename(certname, baseurl) then cmdresult, errtxt = modelfunctions.run_executable({"openssl", "x509", "-in", certname, "-noout", "-text"}) cmdresult = cmdresult .. "Content:\n" .. (fs.read_file(certname) or "") end return cfe({ type="table", value={name=certname, value=cmdresult}, label="Certificate", errtxt=errtxt }) end mymodule.get_generate_dh_params = function(self, clientdata) local retval = {} return cfe({ type="group", value=retval, label="Generate Diffie Hellman parameters" }) end mymodule.generate_dh_params = function(self, gen) if not posix.stat(certurl) then fs.create_directory(certurl) end gen.descr, gen.errtxt = modelfunctions.run_executable({"openssl", "dhparam", "-out", certurl.."dh1024.pem", "1024"}, true) return gen end return mymodule