-- acf model for squid local mymodule = {} -- Load libraries modelfunctions = require("modelfunctions") validator = require("acf.validator") format = require("acf.format") fs = require("acf.fs") -- Set variables local squidconf = "/etc/squid/squid.conf" local squiddigestusers = "/etc/squid/users.list" local processname = "squid" local packagename = "squid" local baseurl = "/etc/squid/" local config, configcontent local validate_config = function(newconfig) local success = true success = modelfunctions.validatemulti(newconfig.value.authmethod) for i,val in ipairs(newconfig.value.httpports.value) do local before, after = string.match(val, "^([^:]+):([^:]+)$") if not (before and after) then after = val end if not validator.is_port(after) then newconfig.value.httpports.errtxt = "Invalid port" success = false break end end return success, newconfig end mymodule.getstatus = function() return modelfunctions.getstatus(processname, packagename, "Squid 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 mymodule.get_configfile = function() return modelfunctions.getfiledetails(squidconf) end mymodule.update_configfile = function(self, filedetails) return modelfunctions.setfiledetails(self, filedetails, {squidconf}) end mymodule.read_digest_userlist = function() local retval = modelfunctions.getfiledetails(squiddigestusers) retval.value.filecontent.descr = "List of username:password(plaintext) entries, one per line." -- check to see if the file is being used configcontent = configcontent or fs.read_file(squidconf) or "" config = config or format.parse_linesandwords(configcontent) retval.value.status = cfe({ errtxt="Digest authentication not enabled", label="User list status" }) for i,line in ipairs(config) do if line[1] == "auth_param" and line[2] == "digest" and line[3] == "program" then if line[#line] == squiddigestusers then retval.value.status.value = "User list in use" retval.value.status.errtxt = nil else retval.value.status.errtxt = "Digest authentication not using this user list" end end end return retval end mymodule.update_digest_userlist = function(self, userlistdetails) -- FIXME - validate modelfunctions.setfiledetails(self, userlistdetails, {squiddigestusers}) return mymodule.read_digest_userlist() end mymodule.get_enable_digest_userlist = function(self, clientdata) local retval = {} return cfe({ type="group", value=retval, label="Enable Digest User List" }) end mymodule.enable_digest_userlist = function(self, endigest) configcontent = configcontent or fs.read_file(squidconf) or "" local newline = "auth_param digest program /usr/sbin/digest_pw_auth "..squiddigestusers local lastauth, authline local lines = {} -- first loop to comment out / uncomment existing lines local linenum = 0 for line in string.gmatch(configcontent, "([^\n]*)\n?") do linenum = linenum + 1 if not authline and not string.match(line, "^[%s#]*$") then local first, second, third = string.match(line, "([^%s#]+)%s+(%S+)%s+(%S+)") if first == "auth_param" then lastauth = linenum if second == "digest" and third == "program" then authline = linenum line = string.gsub(line, "^[%s#]+", "") local fifth = string.match(line, "^%S+%s+%S+%s+%S+%s+%S+%s+(%S+)") line = string.gsub(line, fifth, squiddigestusers) end end end lines[#lines+1] = line end if not authline then if not lastauth then lines[#lines+1] = newline else table.insert(lines, lastauth+1, newline) end end fs.write_file(squidconf, string.gsub(table.concat(lines, "\n"), "\n+$", "")) config = nil configcontent = nil return endigest end mymodule.read_config = function() local retval = { httpports = { type="list", value={}, label="HTTP Ports", descr="List of port, IP:port, or hostname:port entries that Squid will listen on", seq=0 }, accesslog = { type="boolean", value=false, label="Log Access", seq=1 }, diskcache = { type="boolean", value=false, label="Disk Cache", seq=2 }, authmethod = { type="multi", value={}, label="Authentication Methods", option={"negotiate", "ntlm", "digest", "basic"}, seq=3 }, } configcontent = configcontent or fs.read_file(squidconf) or "" config = config or format.parse_linesandwords(configcontent) if config then for i,line in ipairs(config) do if line[1] == "http_port" then table.insert(retval.httpports.value, line[2]) elseif line[1] == "access_log" then retval.accesslog.value = (line[2] ~= "none") elseif line[1] == "cache_dir" then retval.diskcache.value = (line[2] ~= "null") elseif line[1] == "auth_param" and line[3] == "program" then table.insert(retval.authmethod.value, line[2]) end end end return cfe({ type="group", value=retval, label="Squid Config" }) end mymodule.update_config = function(self, newconfig) local success, newconfig = validate_config(newconfig) local lastport, lastlog, lastcache, lastauth local didports = {} local didlog, didcache local didauths = {} local lines = {} if success then configcontent = configcontent or fs.read_file(squidconf) or "" local reverseports = {} for i,port in ipairs(newconfig.value.httpports.value) do reverseports[port] = i end local reverseauths = {} for i,auth in ipairs(newconfig.value.authmethod.value) do reverseauths[auth] = i end -- first loop to comment out / uncomment existing lines local linenum = 0 for line in string.gmatch(configcontent, "([^\n]*)\n?") do linenum = linenum + 1 if not string.match(line, "^[%s#]*$") then local first, second = string.match(line, "([^%s#]+)%s+(%S+)") if first == "http_port" then lastport = linenum line = string.gsub(line, "^[%s#]+", "") if not reverseports[second] then line = "# "..line else didports[second] = true end elseif first == "access_log" then lastlog = linenum line = string.gsub(line, "^[%s#]+", "") if (newconfig.value.accesslog.value and second == "none") or (not newconfig.value.accesslog.value and second ~= "none") then line = "# "..line elseif didlog then success = false newconfig.value.accesslog.errtxt = "There are multiple access_log lines" break else didlog = true end elseif first == "cache_dir" then lastcache = linenum line = string.gsub(line, "^[%s#]+", "") if (newconfig.value.diskcache.value and second == "null") or (not newconfig.value.diskcache.value and second ~= "null") then line = "# "..line elseif didcache then success = false newconfig.value.diskcache.errtxt = "There are multiple cache_dir lines" break else didcache = true end elseif first == "auth_param" then lastauth = linenum if string.match(line, "[^%s#]+%s+%S+%s+(%S+)") == "program" then line = string.gsub(line, "^[%s#]+", "") if not reverseauths[second] then line = "# "..line else didauths[second] = true end end end end lines[#lines+1] = line end end if success then -- We've gone through the file, now check to see if everything is done local tobedone = {} local line for i,port in ipairs(newconfig.value.httpports.value) do if not didports[port] then line = "http_port "..port if lastport then table.insert(tobedone, {linenum=lastport+1, line=line}) else table.insert(lines, line) end end end if not didlog then if newconfig.value.accesslog.value then line = "access_log /var/log/squid/access.log" else line = "access_log none" end if lastlog then table.insert(tobedone, {linenum=lastlog+1, line=line}) else table.insert(lines, line) end end if not didcache then if newconfig.value.diskcache.value then line = "cache_dir diskd /var/cache/squid 400 16 256" else line = "cache_dir null" end if lastcache then table.insert(tobedone, {linenum=lastcache+1, line=line}) else table.insert(lines, line) end end for i,auth in ipairs(newconfig.value.authmethod.value) do if not didauths[auth] then line = "auth_param "..auth.." program " -- These entries have not been tested and probably don't work if auth == "basic" then line = line .. "/usr/libexec/ncsa_auth /usr/etc/passwd" elseif auth == "digest" then line = line .. "/usr/sbin/digest_pw_auth /etc/squid/users.list" elseif auth == "ntlm" then line = line .. "/usr/sbin/wb_ntlmauth" elseif auth == "negotiate" then line = line .. "/usr/sbin/ntlm_auth --helper-protocol=gss-spnego" end if lastauth then table.insert(tobedone, {linenum=lastauth+1, line=line}) else table.insert(lines, line) end end end if #tobedone > 0 then table.sort(tobedone, function(a,b) return (a.linenum > b.linenum) end) for i,entry in ipairs(tobedone) do table.insert(lines, entry.linenum, entry.line) end end -- finally, write the file fs.write_file(squidconf, string.gsub(table.concat(lines, "\n"), "\n+$", "")) config = nil configcontent = nil else newconfig.errtxt = "Failed to set config" end return newconfig end --[[ mymodule.read_acls = function() local acls = cfe({ type="structure", value={}, label="Squid Access Lists" }) configcontent = configcontent or fs.read_file(squidconf) or "" config = config or format.parse_linesandwords(configcontent) for i,line in ipairs(config) do if line[1] == "acl" then table.insert(acls.value, {line=line.line, linenum=line.linenum}) end end return acls end mymodule.read_acl = function(linenum) local line = cfe({ label="Squid Access List" }) local linecfe = cfe({ value=linenum, label="Line number" }) configcontent = configcontent or fs.read_file(squidconf) or "" line.value = format.getline(configcontent, linenum) or "" return cfe({ type="group", value={line=line, linenum=linecfe}, label="Squid Access List" }) end mymodule.update_acl = function(acl) -- local success, acl = validate_acl(acl) configcontent = configcontent or fs.read_file(squidconf) or "" configcontent = format.replaceline(configcontent, acl.value.linenum.value, acl.value.line.value) fs.write_file(squidconf, string.gsub(configcontent, "\n+$", "")) config = nil configcontent = nil return acl end mymodule.create_acl = function(acl) -- local success, acl = validate_acl(acl) configcontent = configcontent or fs.read_file(squidconf) or "" config = config or format.parse_linesandwords(configcontent) local linenum = -1 for i=#config,1,-1 do if config[i][1] == "acl" then linenum = config[i].linenum configcontent = format.insertline(configcontent, linenum, acl.value.line.value) break end end if linenum == -1 then configcontent = string.gsub(configcontent, "\n?$", "\n") .. acl.value.line.value end fs.write_file(squidconf, string.gsub(configcontent, "\n+$", "")) config = nil configcontent = nil return acl end mymodule.delete_acl = function(linenum) configcontent = configcontent or fs.read_file(squidconf) or "" configcontent = format.replaceline(configcontent, linenum) fs.write_file(squidconf, string.gsub(configcontent, "\n+$", "")) config = nil configcontent = nil return acl end --]] function mymodule.listfiles() local retval = cfe({ type="list", value={}, label="Squid Files" }) if not fs.is_dir(baseurl) then fs.create_directory(baseurl) end for file in posix.files(baseurl) do file = baseurl..file if fs.is_file(file) and file ~= squidconf and file ~= squiddigestusers then table.insert(retval.value, file) end end table.sort(retval.value) return retval end function mymodule.getnewfile() local filename = cfe({ label="File Name", descr="Must be in "..baseurl }) return cfe({ type="group", value={filename=filename}, label="Squid File" }) end function mymodule.createfile(self, filedetails) local success = true local path = string.match(filedetails.value.filename.value, "^%s*(.*%S)%s*$") or "" if not string.find(path, "/") then path = baseurl..path end if not validator.is_valid_filename(path, baseurl) then success = false filedetails.value.filename.errtxt = "Invalid filename" else if not fs.is_dir(baseurl) then fs.create_directory(baseurl) end if posix.stat(path) then success = false filedetails.value.filename.errtxt = "Filename already exists" end end if success then fs.create_file(path) else filedetails.errtxt = "Failed to Create File" end return filedetails end function mymodule.readfile(filename) return modelfunctions.getfiledetails(filename, mymodule.listfiles().value) end function mymodule.updatefile(self, filedetails) return modelfunctions.setfiledetails(self, filedetails, mymodule.listfiles().value) end function mymodule.getdeletefile(self, clientdata) local retval = {} retval.filename = cfe({ value=clientdata.filename or "", label="File Name" }) return cfe({ type="group", value=retval, label="Delete Squid File" }) end function mymodule.deletefile(self, delfile) delfile.errtxt = "Failed to delete Squid File" delfile.value.filename.errtxt = "Invalid filename" for i,file in ipairs(mymodule.listfiles().value) do if delfile.value.filename.value == file then delfile.errtxt = nil delfile.value.filename.errtxt = nil os.remove(file) break end end return delfile end return mymodule