module(..., package.seeall) -- Load libraries require("modelfunctions") require("fs") require("format") require("validator") -- Set variables local configfile = "/etc/dnsmasq.conf" local processname = "dnsmasq" local packagename = "dnsmasq" -- ################################################################################ -- LOCAL FUNCTIONS local function update_file (file, search_name, value_in) if not file or not search_name or search_name == "" then return file, false end -- Since we clear out the value, this prevents us from changing parent's value local value = value_in if type(value_in) == "table" then value = {} for i,val in ipairs(value_in) do value[#value + 1] = val end end local new_conf_file = {} local skip_lines = {} for l in string.gmatch(file, "([^\n]*)\n?") do if string.find ( l, "\\%s*$" ) then skip_lines[#skip_lines+1] = string.match(l, "^(.*)\\%s*$") l = nil else if #skip_lines then skip_lines[#skip_lines+1] = l l = table.concat(skip_lines, " ") end -- check if comment line if not string.find ( l, "^%s*#" ) then -- find name local a = string.match ( l, "^%s*([^=]*%S)%s*=" ) if a and (search_name == a) then -- Figure out the value local b = string.match ( l, '=%s*(.*%S)%s*$' ) or "" -- remove comments from end of line if string.find ( b, '#' ) then b = string.match ( b, '^(.*%S)%s*#.*$' ) or "" end -- We found the name, change the value if not value then l = nil elseif type(value) == "string" then if value ~= b then l = search_name.."="..value end value = nil else local temp = l l = nil for i,val in ipairs(value) do if val == b then l = temp table.remove(value, i) break end end end skip_lines = {} -- replacing line end end if #skip_lines > 0 then for i,line in ipairs(skip_lines) do new_conf_file[#new_conf_file + 1] = line end skip_lines = {} l = nil end end new_conf_file[#new_conf_file + 1] = l end if value then -- we didn't find the searchname, add it now if type(value) == "string" then new_conf_file[#new_conf_file + 1] = search_name.."="..value else for i,val in ipairs(value) do new_conf_file[#new_conf_file + 1] = search_name.."="..val end end end file = table.concat(new_conf_file, '\n') return file, true end -- Parse string for name=value pairs, returned in a table local function parse_file (file) if not file or file == "" then return nil end local opts = nil local skip_lines = {} for l in string.gmatch(file, "([^\n]*)\n?") do if string.find ( l, "\\%s*$" ) then skip_lines[#skip_lines+1] = string.match(l, "^(.*)\\%s*$") else if #skip_lines then skip_lines[#skip_lines+1] = l l = table.concat(skip_lines, " ") skip_lines = {} end -- check if comment line if not string.find ( l, "^%s*#" ) then -- find name local a = string.match ( l, "^%s*([^=]*%S)%s*=" ) if a then -- Figure out the value local b = string.match ( l, '=%s*(.*%S)%s*$' ) or "" -- remove comments from end of line if string.find ( b, '#' ) then b = string.match ( b, '^(.*%S)%s*#.*$' ) or "" end if not (opts) then opts = {} end if not opts[a] then opts[a] = {b} else table.insert(opts[a], b) end end end end end return opts end local function validateconfig(config) local success = true function testlist(param, test, errtxt) if #param.value > 0 then for i,val in ipairs(param.value) do if test(val) then param.errtxt = errtxt success = false break end end end end testlist(config.value.domain, function(v) return string.find(v, "%s") end, "Cannot contain spaces") testlist(config.value.interface, function(v) return string.find(v, "%W") end, "Illegal character") testlist(config.value.listen_address, function(v) return not validator.is_ipv4(v) end, "Invalid IP Address") testlist(config.value.dhcp_range, function(v) return string.find(v, "%s") end, "Cannot contain spaces") testlist(config.value.no_dhcp_interface, function(v) return string.find(v, "%W") end, "Illegal character") testlist(config.value.dhcp_host, function(v) return string.find(v, "%s") end, "Cannot contain spaces") testlist(config.value.dhcp_option, function(v) return string.find(v, "%s") end, "Cannot contain spaces") testlist(config.value.mx_host, function(v) return string.find(v, "%s") end, "Cannot contain spaces") return success, config end -- ################################################################################ -- PUBLIC FUNCTIONS function startstop_service(action) return modelfunctions.startstop_service(processname, action) end function getstatus() return modelfunctions.getstatus(processname, packagename, "DNS Masq Status") end function getconfig() local conf = parse_file(fs.read_file(configfile) or "") or {} local output = {} output.domain = cfe({ type="list", value=conf.domain or {}, label="Local Domain", descr="List of internal domain(s) for your LAN 'domain[,address_range]'. Address range can be a single IP address, a range specified by 'IP,IP', or IP/netmask." }) output.interface = cfe({ type="list", value=conf.interface or {}, label="Interface", descr="List of interfaces to listen on." }) output.listen_address = cfe({ type="list", value=conf["listen-address"] or {}, label="List of IP addresses to listen on" }) output.dhcp_range = cfe ({ type="list", value=conf["dhcp-range"] or {}, label="Range of DHCP IPs", descr="List of Start,End,Netmask,Time in seconds/minutes(m)/hours(h) ie. 169.254.0.10,169.254.0.100,255.255.255.0,12h" }) output.no_dhcp_interface = cfe({ type="list", value=conf["no-dhcp-interface"] or {}, label="No DHCP Interface", descr="List of interfaces which should have DNS but not DHCP." }) output.dhcp_host = cfe({ type="list", value=conf["dhcp-host"] or {}, label="DHCP Host Parameter", descr="List of per host parameters for the DHCP server. See dnsmasq documentation." }) output.dhcp_option = cfe({ type="list", value=conf["dhcp-option"] or {}, label="DHCP Option", descr="List of different or extra options to DHCP clients. See dnsmasq documentation." }) output.mx_host = cfe({ type="list", value=conf["mx-host"] or {}, label="MX Record", descr="List of MX records 'mx_name,hostname'." }) -- APP.logevent(html.cfe_unpack(output)) return cfe({ type="group", value=output, label="DNS Masq Config" }) end function setconfig(config) local success, config = validateconfig(config) if success then local file = fs.read_file(configfile) file = update_file(file,"domain",config.value.domain.value) file = update_file(file,"interface",config.value.interface.value) file = update_file(file,"listen-address",config.value.listen_address.value) file = update_file(file,"dhcp-range",config.value.dhcp_range.value) file = update_file(file,"no-dhcp-interface",config.value.no_dhcp_interface.value) file = update_file(file,"dhcp-host",config.value.dhcp_host.value) file = update_file(file,"dhcp-option",config.value.dhcp_option.value) file = update_file(file,"mx-host",config.value.mx_host.value) fs.write_file(configfile, file) else config.errtxt = "Failed to set config" end return config end function getconfigfile() -- FIXME Validate return modelfunctions.getfiledetails(configfile) end function setconfigfile(filedetails) -- FIXME Validate return modelfunctions.setfiledetails(filedetails, {configfile}) end