aredn/files/www/cgi-bin/ports

1830 lines
65 KiB
Lua
Executable File

#!/usr/bin/lua
--[[
Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
Copyright (C) 2021 Tim Wilkinson
Original Perl Copyright (C) 2015 Conrad Lara
See Contributors file for additional contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Additional Terms:
Additional use restrictions exist on the AREDN(TM) trademark and logo.
See AREDNLicense.txt for more info.
Attributions to the AREDN Project must be retained in the source code.
If importing this code into a new or existing project attribution
to the AREDN project must be added to the source code.
You must not misrepresent the origin of the material contained within.
Modified versions must be modified to attribute to the original source
and be marked in reasonable ways as differentiate it from the original
version
--]]
require("nixio")
require("aredn.hardware")
require("aredn.http")
require("aredn.utils")
require("aredn.html")
require("uci")
require("aredn.info")
require("aredn.olsr")
local html = aredn.html
local cursor = uci.cursor()
local config = aredn.info.get_nvram("config")
local node = aredn.info.get_nvram("node")
if not node or node == "" then
node = "NOCALL"
end
local tactical = aredn.info.get_nvram("tactical")
if not tactical then
tactical = ""
end
-- post_data
local parms = {}
if os.getenv("REQUEST_METHOD") == "POST" then
require('luci.http')
local request = luci.http.Request(nixio.getenv(),
function()
local v = io.read(1024)
if not v then
io.close()
end
return v
end
)
parms = request:formvalue()
end
function validate_service_name(name)
if not name or name == "" or name:match("[:-\"|<>]") then
return false
else
return true
end
end
function validate_service_protocol(proto)
if not proto or proto == "" or proto:match("[:-\"|<>]") or not proto:match("^%w+") then
return false
else
return true
end
end
function validate_service_suffix(suffix)
if not suffix or suffix:match("[:-\"|<>]") or not suffix:match("^[%w/?&._=#-]*$") then
return false
else
return true
end
end
local mac_pattern = "%x%x:%x%x:%x%x:%x%x:%x%x:%x%x"
local mac_wildcard = "*:*:*:*:*:*"
-- accepts nonempty strings
local function validate_nonempty_matcher(matcher)
return matcher ~= ""
end
-- accepts MAC addresses with optional bytewise wildcard substitution
local function validate_mac_matcher(matcher)
return matcher:gsub("%x%x", "*") == mac_wildcard
end
-- accepts host names with optional trailing wildcard
local function validate_host_matcher(matcher)
return matcher:match("^%w[%w.-]*%*?$")
end
local dhcp_tag_conds = {
{ key = "vendorclass",
name = "Vendor Class",
validator = validate_nonempty_matcher,
hint = "all or part of the class value sent by the client",
jsPlaceholder = "subpart of class string",
},
{ key = "userclass",
name = "User Class",
validator = validate_nonempty_matcher,
hint = "all or part of the class value sent by the client",
jsPlaceholder = "subpart of class string",
},
{ key = "mac",
name = "MAC Address",
validator = validate_mac_matcher,
hint = "six bytes, each either two hexadecimal digits or a single asterisk, separated by colons",
jsPattern = [[^(?:(?:[0-9a-fA-F]{2}|\\*):){5}(?:[0-9a-fA-F]{2}|\\*)$]],
jsPlaceholder = "*:*:*:*:*:*",
},
{ key = "name",
name = "Host Name",
validator = validate_host_matcher,
hint = "alphanumerics, hyphens, and dots, with an optional trailing asterisk",
jsPattern = [[^[a-zA-Z0-9][a-zA-Z0-9.\\-]*\\*?$]],
jsPlaceholder = "client.host-name*",
},
}
local dhcp_tag_validators = {}
for _, cond in ipairs(dhcp_tag_conds)
do
dhcp_tag_validators[cond.key] = {
validator = cond.validator,
hint = cond.hint,
jsPattern = cond.jsPattern,
jsPlaceholder = cond.jsPlaceholder}
end
function html_safe(s)
return s:gsub("&", "&amp;"):gsub(">", "&gt;"):gsub("<", "&lt;")
end
local serv_err = {}
function serverr(msg)
serv_err[#serv_err + 1] = msg:gsub(">", "&gt;"):gsub("<", "&lt;")
end
local port_err = {}
function porterr(msg)
port_err[#port_err + 1] = msg
end
local dmz_err = {}
function dmzerr(msg)
dmz_err[#dmz_err + 1] = msg
end
local dhcp_err = {}
function dhcperr(msg)
dhcp_err[#dhcp_err + 1] = msg
end
local dhcptag_err = {}
function dhcptagerr(msg)
dhcptag_err[#dhcptag_err + 1] = msg
end
local dhcpopt_err = {}
function dhcpopterr(msg)
dhcpopt_err[#dhcpopt_err + 1] = msg
end
local alias_err = {}
function aliaserr(msg)
alias_err[#alias_err + 1] = msg
end
local errors = {}
function err(msg)
errors[#errors + 1] = msg
end
local hidden = {}
function hide(m)
hidden[#hidden + 1] = m
end
local hosts = {}
local addrs = {}
local macs = {}
if config == "" or nixio.fs.stat("/tmp/reboot-required") then
http_header()
html.header(node .. " setup", true)
html.print("<body><center>")
html.alert_banner()
html.navbar_admin("ports")
html.print("<table width=820><tr><td align=center><br><b>")
if config == "" then
html.print("This page is not available until the configuration has been set.")
else
html.print("The specified configuration is invalid, try flushing your browser cache or reboot the mesh node.")
end
html.print("</b></td></tr>")
html.print("</table></center>")
html.footer();
html.print("</body></html>")
http_footer()
os.exit()
end
-- check for dmz mode
local dmz_mode = tonumber(cursor:get("aredn", "@dmz[0]", "mode"))
if not dmz_mode then
dmz_mode = 2
end
local lanip, lanbcast, lanmask = aredn.hardware.get_interface_ip4(aredn.hardware.get_iface_name("lan"))
local lannet_d = nixio.bit.band(ip_to_decimal(lanip), ip_to_decimal(lanmask))
local tmpdir = "/tmp/web/ports"
if not parms.reload then
remove_all(tmpdir)
end
nixio.fs.mkdir(tmpdir)
local fsuffix = dmz_mode == 0 and ".nat" or ".dmz"
local portfile = "/etc/config.mesh/_setup.ports" .. fsuffix
local dhcpfile = "/etc/config.mesh/_setup.dhcp" .. fsuffix
local dhcptagsfile = "/etc/config.mesh/_setup.dhcptags" .. fsuffix
local dhcpoptionsfile = "/etc/config.mesh/_setup.dhcpoptions" .. fsuffix
local servfile = "/etc/config.mesh/_setup.services" .. fsuffix
local aliasfile = "/etc/config.mesh/aliases" .. fsuffix
-- if a reset or a first time page load
-- read the data from the config files
if parms.button_reset or not parms.reload then
local i = 0
if nixio.fs.stat(portfile) then
for line in io.lines(portfile)
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local k, v = line:match("(%S+)%s+=%s+(%S+)")
if v then
parms[k] = v
else
local intf, type, out, ip, _in, enable = line:match("(.*):(.*):(.*):(.*):(.*):(.*)")
if intf then
i = i + 1
parms["port" .. i .. "_intf"] = intf
parms["port" .. i .. "_type"] = type
parms["port" .. i .. "_out"] = out
parms["port" .. i .. "_ip"] = ip
parms["port" .. i .. "_in"] = _in
parms["port" .. i .. "_enable"] = enable
end
end
end
end
end
parms.port_num = i
-- set dhcp reservations
-- ip addresses are stored as offsets from the lan network address
i = 0
if nixio.fs.stat(dhcpfile) then
for line in io.lines(dhcpfile)
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local a, b, x = line:match("(%S*)%s+(%S*)%s+(.*)")
if x then
local c, d = x:match("(%S*)%s+(%S*)")
if not c then
c = x
d = ""
end
i = i + 1
local prefix = "dhcp" .. i .. "_"
parms[prefix .. "mac"] = a
parms[prefix .. "ip"] = decimal_to_ip(lannet_d + tonumber(b))
parms[prefix .. "host"] = c
parms[prefix .. "noprop"] = d
end
end
end
end
parms.dhcp_num = i
-- set dhcp tags
i = 0
if nixio.fs.stat(dhcptagsfile) then
for line in io.lines(dhcptagsfile)
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local name, condition, pattern = line:match("(%S+)%s+(%S+)%s+(.*)")
if pattern then
i = i + 1
local prefix = "dhcptag" .. i .. "_"
parms[prefix .. "name"] = name
parms[prefix .. "cond"] = condition
parms[prefix .. "pat"] = pattern
end
end
end
end
parms.dhcptags_num = i
-- set dhcp options
i = 0
if nixio.fs.stat(dhcpoptionsfile) then
for line in io.lines(dhcpoptionsfile)
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local tag, force, opt_num, opt_val = line:match("(%S*)%s+(%w+)%s+(%d+)%s+(.*)")
if force then
i = i + 1
local prefix = "dhcpopt" .. i .. "_"
parms[prefix .. "tag"] = tag
parms[prefix .. "force"] = force
parms[prefix .. "num"] = opt_num
parms[prefix .. "val"] = opt_val
end
end
end
end
parms.dhcpoptions_num = i
-- services
i = 0
if nixio.fs.stat(servfile) then
for line in io.lines(servfile)
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local a, b, c, d, x = line:match("([^|]*)|([^|]*)|([^|]*)|([^|]*)|(.*)")
if x then
local e, f = x:match("(.*)|(.*)")
if not e then
e = x
f = ""
end
i = i + 1
local prefix = "serv" .. i .. "_"
parms[prefix .. "name"] = a
parms[prefix .. "link"] = b
parms[prefix .. "proto"] = c
parms[prefix .. "host"] = d
parms[prefix .. "port"] = e
parms[prefix .. "suffix"] = f
end
end
end
end
parms.serv_num = i
-- aliases
i = 0
if nixio.fs.stat(aliasfile) then
for line in io.lines(aliasfile)
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local a, b = line:match("(.*)%s+(.*)")
if b then
i = i + 1
parms["alias" .. i .. "_ip"] = a
parms["alias" .. i .. "_host"] = b
end
end
end
end
parms.alias_num = i
-- sanitize the 'add' values
parms.port_add_intf = dmz_mode ~= 0 and "wan" or "wifi"
parms.port_add_type = "tcp"
if not parms.dmz_ip then
parms.dmz_ip = ""
end
parms.port_add_out = ""
parms.port_add_ip = ""
parms.port_add_in = ""
parms.dhcp_add_host = ""
parms.dhcp_add_ip = ""
parms.dhcp_add_mac = ""
parms.dhcp_add_noprop = ""
parms.dhcptag_add_name = ""
parms.dhcptag_add_cond = ""
parms.dhcptag_add_pat = ""
parms.dhcpopt_add_tag = ""
parms.dhcpopt_add_num = ""
parms.dhcpopt_add_val = ""
parms.serv_add_name = ""
parms.serv_add_proto = ""
parms.serv_add_host = ""
parms.serv_add_port = ""
parms.serv_add_suffix = ""
parms.alias_add_host = ""
parms.alias_add_ip = ""
end
local dhcp_start = tonumber(cursor:get("dhcp", "@dhcp[0]", "start"))
local dhcp_limit = tonumber(cursor:get("dhcp", "@dhcp[0]", "limit"))
local dhcp_end = dhcp_start + dhcp_limit - 1
-- load and validate the ports
function make_addable_list(max_row)
local list = {}
for i = 1,max_row
do
list[#list + 1] = i
end
list[#list + 1] = "_add"
return list
end
local list = make_addable_list(parms.port_num)
local port_num = 0
local usedports = {}
local f = io.open(tmpdir .. "/ports", "w")
if f then
local vars = { "_intf", "_type", "_out", "_ip", "_in" }
for _, val in ipairs(list)
do
for _ = 1,1 -- a non-loop so we can break out to continue the parent loop
do
for _, var in ipairs(vars)
do
local varname = "port" .. val .. var
local v = parms[varname]
if v then
v = v:gsub("^%s+", ""):gsub("%s+$", "")
else
v = ""
end
parms[varname] = v
_G[var] = v
end
local varname = "port" .. val .. "_enable"
if not parms[varname] then
parms[varname] = "0"
end
_enable = parms[varname]
if val == "_add" then
_enable = "1"
end
local continue = false
if val == "_add" then
if not ((_out ~= "" or _ip ~= "" or _in ~= "") and (parms.port_add or parms.button_save)) then
break
end
else
if parms["port" .. val .. "_del"] then
break
end
end
if val == "_add" and parms.button_save then
porterr(val .. " this rule must be added or cleared out before saving changes")
break
end
if _out:match("-") then
if validate_port_range(_out) then
_in = _out:match("^%s*(%d+)")
else
porterr(val .. "'" .. _out .. "' is not a valid port range")
end
else
if _out == "" then
porterr(val .. " an outside port is required")
elseif not validate_port(_out) then
porterr(val .. "'" .. _out .. "' is not a valid port")
end
end
if _ip == "" then
porterr(val .. " an address must be selected")
elseif not validate_ip(_ip) then
porterr(val .. "'" .. _ip .. "' is not a valid address")
end
if _in == "" then
porterr(val .. "a LAN port is required")
elseif not validate_port(_in) then
porterr(val .. "'" .. _in .. "' is not a valid port")
end
if val == "_add" and #port_err > 0 then
break
end
-- commit the data for this rule
port_num = port_num + 1
usedports[_out] = true
if _type ~= "tcp" and _type ~= "udp" then
_type = "both"
end
f:write(_intf .. ":" .. _type .. ":" .. _out .. ":" .. _ip .. ":" .. _in .. ":" .. _enable .. "\n")
for _, var in ipairs(vars)
do
parms["port" .. port_num .. var] = _G[var]
end
if val == "_add" then
parms.port_add_intf = "wifi"
parms.port_add_out = ""
parms.port_add_ip = ""
parms.port_add_in = ""
end
end
end
if parms.dmz_ip and parms.dmz_ip ~= "" then
f:write("dmz_ip = " .. parms.dmz_ip .. "\n")
if not validate_ip(parms.dmz_ip) then
dmzerr(parms.dmz_ip .. " is not a valid address")
end
end
f:close()
end
parms.port_num = port_num
-- load and validate the dhcp reservations
local list = make_addable_list(parms.dhcp_num)
local dhcp_num = 0
for _, val in ipairs(list)
do
for _ = 1,1
do
local host = parms["dhcp" .. val .. "_host"]
local ip = parms["dhcp" .. val .. "_ip"]
local mac = parms["dhcp" .. val .. "_mac"]
local noprop = parms["dhcp" .. val .. "_noprop"]
if not noprop then
noprop = ""
end
local foundhost = false
if val == "_add" then
if host ~= "" then
local pattern = "%s" .. host .. "%s"
for line in aredn.olsr.getHostAsLines()
do
if line:lower():match(pattern) then
foundhost = true
dhcperr(val .. [[ <font color='red'>Warning!</font> ']] .. host .. [[ is already in use!<br>
Please choose another hostname.<br>
Prefixing the hostname with your callsign will help prevent duplicates on the network.
]])
break
end
end
end
if not ((host ~= "" or ip ~= "" or mac ~= "" or foundhost) and (parms.dhcp_add or parms.button_save)) then
break
end
elseif parms["dhcp" .. val .. "_del"] then
break
end
if val == "_add" and parms.button_save then
dhcperr(val .. " this reservation must be added or cleared before saving changes")
break
end
if validate_hostname(host) then
if not foundhost then
local lhost = host:lower()
if lhost == node:lower() or lhost == tactical:lower() then
dhcperr(val .. " hostname '" .. host .. "' is already in use")
end
for key, _ in pairs(hosts)
do
if key:lower() == lhost then
dhcperr(val .. " hostname '" .. host .. "' is already in use")
break
end
end
end
else
if host then
dhcperr(val .. " '" .. host .. "' is not a valid hostname")
else
dhcperr(val .. " a hostname is required")
end
end
if validate_ip(ip) then
if addrs[ip] then
dhcperr(val .. " '" .. ip .. "' is already in use")
elseif ip == lanip or not validate_same_subnet(ip, lanip, lanmask) or not validate_ip_netmask(ip, lanmask) then
dhcperr(val .. " '" .. ip .. "' is not a valid LAN address")
end
else
if ip then
dhcperr(val .. " '" .. ip .. "' is not a valid address")
else
dhcperr(val .. " an IP Address must be selected")
end
end
if mac:match(mac_pattern) then
if macs[mac] then
dhcperr(val .. " MAC " .. mac .. " is already in use")
end
else
if mac then
dhcperr(val .. " '" .. mac .. "' is not a valid mac address")
else
dhcperr(val .. " a MAC address is required")
end
end
if val == "_add" and #dhcp_err > 0 then
break
end
dhcp_num = dhcp_num + 1
local prefix = "dhcp" .. dhcp_num .. "_"
parms[prefix .. "host"] = host
parms[prefix .. "ip"] = ip
parms[prefix .. "mac"] = mac
parms[prefix .. "noprop"] = noprop
hosts[host] = true
addrs[ip] = true
macs[mac] = true
if val == "_add" then
parms.dhcp_add_host = ""
parms.dhcp_add_ip = ""
parms.dhcp_add_mac = ""
parms.dhcp_add_noprop = ""
end
end
end
-- add existing leases
for lease, lval in pairs(parms)
do
local n = lease:match("^lease(%d+)_add$")
if n and lval ~= "" then
local found = false
for k, v in pairs(parms)
do
if k:match("dhcp%d+_mac") then
if v == parms["lease" .. n .. "_mac"] then
found = true
break
end
end
end
if not found then
dhcp_num = dhcp_num + 1
local prefix1 = "lease" .. n .. "_"
local prefix2 = "dhcp" .. dhcp_num .. "_"
local host = parms[prefix1 .. "host"]
local ip = parms[prefix1 .. "ip"]
local mac = parms[prefix1 .. "mac"]
local noprop = parms[prefix1 .. "noprop"]
parms[prefix2 .. "host"] = host
parms[prefix2 .. "ip"] = ip
parms[prefix2 .. "mac"] = mac
parms[prefix2 .. "noprop"] = noprop and noprop or "0"
local lhost = host:lower()
if lhost == node:lower() or lhost == tactical:lower() then
dhcperr(dhcp_num .. " hostname '" .. host .. "' is already in use")
end
for key, _ in pairs(hosts)
do
if key:lower() == lhost then
dhcperr(dhcp_num .. " hostname '" .. host .. "' is already in use")
break
end
end
if addrs[ip] then
dhcperr(dhcp_num .. " is already in use")
end
if macs[mac] then
dhcperr(dhcp_num .. " MAC " .. mac .. " is already in use")
end
end
end
end
parms.dhcp_num = dhcp_num
dhcphosts = {}
dhcphosts[lanip] = "localhost"
-- replace "blank" dhcp hostname and save the dhcp info into the tmpdir
local f = io.open(tmpdir .. "/dhcp", "w")
if f then
local nn = 1
for i = 1,parms.dhcp_num
do
if parms["dhcp" .. i .. "_host"] == "*" then
while hosts["noname" .. nn]
do
nn = nn + 1
end
parms["dhcp" .. i .. "_host"] = "noname" .. nn
hosts["noname" .. nn] = true
end
f:write(parms["dhcp" .. i .. "_mac"] .. " " .. (ip_to_decimal(parms["dhcp" .. i .. "_ip"]) - lannet_d) .. " " .. parms["dhcp" .. i .. "_host"] .. " " .. parms["dhcp" .. i .. "_noprop"] .. "\n")
if not (dhcphosts[parms["dhcp" .. i .. "_ip"]]) then
dhcphosts[parms["dhcp" .. i .. "_ip"]] = parms["dhcp" .. i .. "_host"]
end
end
f:close()
end
function write_temp_records(filename, group_name, record_count, fields)
local f = io.open(tmpdir .. "/" .. filename, "w")
if f then
for i = 1,record_count
do
local prefix = group_name .. i .. "_"
local record = {}
for _, field in ipairs(fields)
do
table.insert(record, parms[prefix .. field])
end
f:write(table.concat(record, " ") .. "\n")
end
f:close()
end
end
-- DHCP tags
local list = make_addable_list(parms.dhcptags_num)
local dhcptags_num = 0
for _, val in ipairs(list)
do
for _ = 1,1
do
local prefix = "dhcptag" .. val .. "_"
local name = parms[prefix .. "name"]
local cond_key = parms[prefix .. "cond"]
local matcher = parms[prefix .. "pat"]
if val == "_add" then
if not ((name ~= "" or cond_key ~= "" or matcher ~= "") and (parms.dhcptag_add or parms.button_save)) then
break
end
elseif parms[prefix .. "del"] then
break
end
if val == "_add" and parms.button_save then
dhcptagerr(val .. " this tag must be added or cleared out before saving changes")
break
end
if name and not name:match("^%w+$") then
dhcptagerr(val .. [[ <font color='red'>Warning!</font> The tag name "]] .. html_safe(name)
.. [[" is invalid; tag names must contain only letters and digits.]])
break
end
if cond_key == "" then
dhcptagerr(val .. [[ <font color='red'>Warning!</font> Please choose a client parameter to match.]])
break
end
if matcher == "" then
dhcptagerr(val .. [[ <font color='red'>Warning!</font> Please supply a value to match to the client parameter.]])
break
else
local tag_validator = dhcp_tag_validators[cond_key]
if not tag_validator then
dhcptagerr(string.format([[%s <font color='red'>Warning!</font> "%s" is not a known DHCP parameter.]], val, cond_key))
break
end
if not tag_validator.validator(matcher) then
dhcptagerr(string.format([[%s <font color='red'>Warning!</font> "%s" is not a valid match for %s; must be %s]],
val, html_safe(matcher), cond_key, tag_validator.hint))
break
end
end
if val == "_add" and #dhcptag_err > 0 and dhcptag_err[#dhcptag_err]:match("^" .. val .. " ") then
break
end
dhcptags_num = dhcptags_num + 1
prefix = "dhcptag" .. dhcptags_num .. "_"
parms[prefix .. "name"] = name
parms[prefix .. "cond"] = cond_key
parms[prefix .. "pat"] = matcher
if val == "_add" then
parms.dhcptag_add_name = ""
parms.dhcptag_add_cond = ""
parms.dhcptag_add_pat = ""
end
end
end
-- write to temp file
write_temp_records("dhcptags", "dhcptag", dhcptags_num, {"name", "cond", "pat"})
parms.dhcptags_num = dhcptags_num
-- DHCP options
local list = make_addable_list(parms.dhcpoptions_num)
local dhcpoptions_num = 0
for _, val in ipairs(list)
do
for _ = 1,1
do
local prefix = "dhcpopt" .. val .. "_"
local tag = parms[prefix .. "tag"]
local force = parms[prefix .. "force"]
if force ~= "force" then
force = "onrequest"
end
local opt_num = parms[prefix .. "num"]
local opt_val = parms[prefix .. "val"]
if val == "_add" then
if not ((tag ~= "" or opt_num ~= "" or opt_val ~= "") and (parms.dhcpopt_add or parms.button_save)) then
break
end
elseif parms[prefix .. "del"] then
break
end
if val == "_add" and parms.button_save then
dhcpopterr(val .. " this option must be added or cleared out before saving changes")
break
end
if tag ~= "" and not tag:match("^%w+$") then
dhcpopterr(val .. [[ <font color='red'>Warning!</font> The tag name "]] .. html_safe(tag) .. [[" is invalid; tag names must contain only letters and digits.]])
break
end
local nnum = tonumber(opt_num)
if not nnum or nnum < 1 or nnum > 254 then
dhcpopterr(val .. [[ <font color='red'>Warning!</font> Option number must be an integer in the range [1..254].]])
break
end
-- TODO check opt_val for suitability for opt_num
if opt_val == "" then
dhcpopterr(val .. [[ <font color='red'>Warning!</font> Please supply a value to send for this option.]])
break
end
if val == "_add" and #dhcpopt_err > 0 and dhcpopt_err[#dhcpopt_err]:match("^" .. val .. " ") then
--if val == "_add" and #dhcpopt_err > 0 then
break
end
dhcpoptions_num = dhcpoptions_num + 1
prefix = "dhcpopt" .. dhcpoptions_num .. "_"
parms[prefix .. "tag"] = tag
parms[prefix .. "force"] = force
parms[prefix .. "num"] = opt_num
parms[prefix .. "val"] = opt_val
if val == "_add" then
parms.dhcpopt_add_tag = ""
parms.dhcpopt_add_force = ""
parms.dhcpopt_add_num = ""
parms.dhcpopt_add_val = ""
end
end
end
-- write to temp file
write_temp_records("dhcpoptions", "dhcpopt", dhcpoptions_num, {"tag", "force", "num", "val"})
parms.dhcpoptions_num = dhcpoptions_num
-- aliases
local list = make_addable_list(parms.alias_num)
local alias_num = 0
for _, val in ipairs(list)
do
for _ = 1,1
do
local foundhost = false
local host = parms["alias" .. val .. "_host"]
local ip = parms["alias" .. val .. "_ip"]
-- if adding aliases check the name is not already in use,
-- also check that it dos not contain anything that will be weird on the mesh
if val == "_add" then
if host ~= "" then
local pattern = "%s" .. host .. "%s"
for line in aredn.olsr.getHostAsLines()
do
if line:lower():match(pattern) then
foundhost = true
aliaserr(val .. [[ <font color='red'>Warning!</font> ']] .. host .. [[ is already in use!<br>
Please choose another hostname.<br>
Prefixing the hostname with your callsign will help prevent duplicates on the network.
]])
break
end
end
if not host:match("^%*%.") and (host:match("_") or not host:match("^[%w%-%.]+$")) then
aliaserr(val .. " <font color='red'>Warning!</font> The alias name: '" .. host .."' is invalid")
end
end
if not ((host ~= "" or ip ~= "" or foundhost) and (parms.alias_add or parms.button_save)) then
break
end
elseif parms["alias" .. val .. "_del"] then
break
end
if val == "_add" and parms.button_save then
aliaserr(val .. " this alias must be added or cleared out before saving changes")
break
end
if val == "_add" and #alias_err > 0 and alias_err[#alias_err]:match("^" .. val .. " ") then
break
end
alias_num = alias_num + 1
parms["alias" .. alias_num .. "_host"] = host
parms["alias" .. alias_num .. "_ip"] = ip
hosts[host] = true
if val == "_add" then
parms.alias_add_host = ""
parms.alias_add_ip = ""
end
end
end
-- write to temp file
write_temp_records("aliases", "alias", alias_num, {"ip", "host"})
parms.alias_num = alias_num
-- load and validate services
local list = make_addable_list(parms.serv_num)
local serv_num = 0
hosts[""] = true
hosts[node] = true
usedports[""] = true
local servicenames = {}
local vars = { "name", "link", "proto", "host", "port", "suffix" }
local f = io.open(tmpdir .. "/services", "w")
if f then
for _, val in ipairs(list)
do
for _ = 1,1
do
for _, var in ipairs(vars)
do
local varname = "serv" .. val .. "_" .. var
local v = parms[varname]
if v then
v = v:gsub("^%s+", ""):gsub("%s+$", "")
else
v = ""
end
parms[varname] = v
_G[var] = v
end
if link == "" then
link = "0"
end
if dmz_mode == 0 then
host = node
end
-- remove services that have had their host or port deleted
if val ~= "_add" and not (dmz_mode == 0 and true or hosts[host]) then
break
end
if val == "_add" then
if not ((name ~= "" or proto ~= "" or port ~= "" or suffix ~= "") and (parms.serv_add or parms.button_save)) then
break
end
else
if parms["serv" .. val .. "_del"] or not (name ~= "" or proto ~= "" or port ~= "" or suffix ~= "") then
break
end
end
if val == "_add" and parms.button_save then
serverr(val .. " this service must be added or cleared out before saving changes")
break
end
if name == "" then
serverr(val .. " a name is required")
elseif not validate_service_name(name) then
serverr(val .. " '" .. name .. "' is not a valid service name")
elseif servicenames[name] then
serverr(val .. " the name '" .. name .. "' is already in use")
end
if link == "1" then
if proto == "" then
proto = "http"
end
parms["serv" .. val .. "_proto"] = proto
if not validate_service_protocol(proto) then
serverr(val .. " '" .. proto .. "' is not a valid protocol")
elseif port == "" then
serverr(val .. " a port number is required")
elseif not validate_port(port) then
serverr(val .. " '" .. port .. "' is not a valid port")
elseif not validate_service_suffix(suffix) then
serverr(val .. " '" .. suffix .. "' is not a valid service suffix")
end
elseif val == "_add" then
proto = ""
port = ""
suffix = ""
end
if val == "_add" and #serv_err > 0 then
break
end
-- commit the data for this service
serv_num = serv_num + 1
servicenames[name] = true
f:write(name .. "|" .. link .. "|" .. proto .. "|" .. host .. "|" .. port .. "|" .. suffix .. "\n")
for _, var in ipairs(vars)
do
parms["serv" .. serv_num .. "_" .. var] = _G[var]
end
if val == "_add" then
parms.serv_add_name = ""
parms.serv_add_link = ""
parms.serv_add_proto = ""
parms.serv_add_host = ""
parms.serv_add_port = ""
parms.serv_add_suffix = ""
end
end
end
f:close()
end
parms.serv_num = serv_num
-- save configuration
if parms.button_save and not (#port_err > 0 or #dhcp_err > 0 or #dhcptag_err > 0 or #dhcpopt_err > 0 or #dmz_err > 0 or #serv_err > 0 or #alias_err > 0) then
filecopy(tmpdir .. "/ports", portfile)
filecopy(tmpdir .. "/dhcp", dhcpfile)
filecopy(tmpdir .. "/dhcptags", dhcptagsfile)
filecopy(tmpdir .. "/dhcpoptions", dhcpoptionsfile)
filecopy(tmpdir .. "/services", servfile)
filecopy(tmpdir .. "/aliases", aliasfile)
os.remove("/tmp/service-validation-state")
if os.execute("/usr/local/bin/node-setup > /dev/null 2>&1") ~= 0 then
err("problem with configuration")
else
os.execute("/usr/local/bin/restart-services.sh dnsmasq firewall olsrd > /dev/null 2>&1")
end
end
-- generate the page
http_header()
html.header(node .. " setup", false)
do
local function generateValidatorCaseJS(cond)
local caseTab = {' case "',
cond.key,
'":\n newPlaceholder = "',
cond.jsPlaceholder,
'";\n newTitle = "',
cond.hint}
if cond.jsPattern then
table.insert(caseTab, '";\n newPattern = "')
table.insert(caseTab, cond.jsPattern)
end
table.insert(caseTab, '";\n break;\n')
return table.concat(caseTab, '')
end
-- Note: toggleField comes from https://jsfiddle.net/6nq7w/4/
local scriptTab = {[[
<script>
function toggleField(hideObj,showObj){
hideObj.disabled=true;
hideObj.style.display='none';
showObj.disabled=false;
showObj.style.display='inline';
showObj.focus();
}
function setOrRemoveAttribute(elem, attrName, value) {
console.log(`setOrRemoveAttribute(${elem.name},${attrName},${value})`)
if (value == null)
elem.removeAttribute(attrName);
else
elem.setAttribute(attrName, value)
}
function setMatcherValidator(inputId, matcherType) {
console.log(`setMatcherValidator(${inputId},${matcherType})`)
const matcherElem = document.getElementById(inputId);
newPattern = null;
newPlaceholder = null;
newTitle = null;
switch (matcherType) {
]]}
for _, cond in ipairs(dhcp_tag_conds)
do
table.insert(scriptTab, generateValidatorCaseJS(cond))
end
table.insert(scriptTab, [[
default:
break;
}
setOrRemoveAttribute(matcherElem, "placeholder", newPlaceholder);
setOrRemoveAttribute(matcherElem, "pattern", newPattern);
setOrRemoveAttribute(matcherElem, "title", newTitle);
}
</script>
<style>
input:invalid {
border: 2px dashed red;
}
</style>]])
html.print(table.concat(scriptTab, ''))
end
html.print("</head>")
html.print("<body><center>")
html.alert_banner()
html.print("<form method=post action=/cgi-bin/ports enctype='multipart/form-data'>")
html.navbar_admin("ports")
html.print("<table width=820>")
-- control buttons
html.print([[<tr><td align=center>
<a href='/help.html#ports' target='_blank'>Help</a>
&nbsp;&nbsp;&nbsp;
<input type=submit name=button_save value='Save Changes' title='Save and use these settings now (takes about 20 seconds)'>&nbsp;
<input type=submit name=button_reset value='Reset Values' title='Revert to the last saved settings'>&nbsp;
<input type=button name=button_refresh value='Refresh' title='Refresh this page' onclick='window.location.reload();'>&nbsp;
<tr><td>&nbsp;</td></tr>]])
hide("<input type=hidden name=reload value=1>")
-- messages
if parms.button_save then
if #port_err > 0 or #dhcp_err > 0 or #dhcptag_err > 0 or #dhcpopt_err > 0 or #dmz_err > 0 or #serv_err > 0 then
html.print("<tr><td align=center><b>Configuration NOT saved!</b></td></tr>")
elseif #errors > 0 then
html.print("<tr><td align=center><b>Configuration saved, however:<br>")
for _, err in ipairs(errors)
do
html.print(err .. "<br>")
end
html.print("</b></td></tr>")
else
html.print("<tr><td align=center><b>Configuration saved and is now active.</b></td></tr>")
end
html.print("<tr><td>&nbsp;</td></tr>")
end
-- everything else
local js_mac_pattern = [[ pattern='^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$' ]]
local js_host_pattern = [[ pattern='^[a-zA-Z0-9][a-zA-Z0-9\-]*$' ]]
local js_port_range_pattern = [[ pattern='^\s*\d{1,5}(?:\s*-\s*\d{1,5})?\s*$' ]]
function print_heading_vsep()
html.print("<tr><td colspan=4 height=5></td></tr>")
end
function print_new_entry_vsep(val, list, columns)
if val == "_add" and #list > 1 then
html.print("<tr><td colspan=" .. columns .. " height=10></td></tr>")
end
end
-- mark input fields of already-added entries as "required"
function require_existing(val)
return val == "_add" and "" or " required "
end
function print_errors(error_list)
for _, e in ipairs(error_list)
do
html.print("<tr><th colspan=8>" .. e .. "</th></tr>")
end
end
function print_reservations()
html.print("<table cellpadding=0 cellspacing=0><tr><th colspan=4>DHCP Address Reservations</th></tr>")
print_heading_vsep()
html.print("<tr><td align=center>Hostname</td><td align=center>IP Address</td><td align=center>MAC Address</td>")
if dmz_mode ~= 0 then
html.print("<td align=center style='font-size:10px;'>Do Not<br>Propagate</td><td></td></tr>")
else
html.print("<td></td><td></td></tr>")
end
print_heading_vsep()
local list = make_addable_list(parms.dhcp_num)
local mac_list = {}
for _, val in ipairs(list)
do
local host = parms["dhcp" .. val .. "_host"]
local ip = parms["dhcp" .. val .. "_ip"]
local mac = parms["dhcp" .. val .. "_mac"]:lower()
local noprop = parms["dhcp" .. val .. "_noprop"]
mac_list[mac] = true
print_new_entry_vsep(val, list, 5)
html.print("<tr><td><input style='width=180;' type=text placeholder='host-name' title='alphanumerics and embedded hyphens' name=dhcp" .. val .. "_host value='" .. host .. "'" .. js_host_pattern .. require_existing(val) .. "></td>")
html.print("<td align=center style='padding:0 4px'><select style='width:130px' name=dhcp" .. val .. "_ip>")
if val == "_add" then
html.print("<option value=''>- IP Address -</option>\n")
end
for i = dhcp_start,dhcp_end
do
local selip = decimal_to_ip(lannet_d + i - lannet_d % 256)
if selip ~= lanip then
local ipname = dhcphosts[selip]
if not ipname or selip == ip then
ipname = selip
end
html.print("<option " .. (ip == selip and "selected" or "") .. " value='" .. selip .. "'>" .. ipname .. "</option>")
end
end
html.print("</select></td>")
html.print("<td><input style='width:120;' type=text placeholder='aa:aa:aa:aa:aa:aa' name=dhcp" .. val .. "_mac value='" .. mac .. "'" .. js_mac_pattern .. require_existing(val) .. "></td>")
if dmz_mode ~= 0 then
html.print("<td align=center><input type=checkbox id=dhcp" .. val .. "_noprop name=dhcp" .. val .. "_noprop value='#NOPROP'"
.. (noprop == "#NOPROP" and " checked" or "") .. "></td>")
else
html.print("<td></td>")
end
html.print("<td><nobr>&nbsp;<input type=submit name=")
if val == "_add" then
html.print("dhcp_add value=Add title='Add this as a DHCP reservation'")
else
html.print("dhcp" .. val .. "_del value='Del ' title='Remove this reservation'")
end
html.print("></nobr></td></tr>")
while #dhcp_err > 0 and dhcp_err[1]:match("^" .. val .. " ")
do
html.print("<tr><th colspan=4>" .. dhcp_err[1]:gsub("^%S+ ", "") .. "</th></tr>")
table.remove(dhcp_err, 1)
end
html.print("<tr><td height=5></td></tr>")
end
html.print("<tr><td>&nbsp;</td></tr>")
html.print("<tr><th colspan=4>Current DHCP Leases</th></tr>\n<tr>")
local i = 0
if nixio.fs.stat("/tmp/dhcp.leases") then
for line in io.lines("/tmp/dhcp.leases")
do
i = i + 1
local _, mac, ip, host = line:match("(%S+)%s+(%S+)%s+(%S+)%s+(%S+)")
html.print("<tr><td height=5></td></tr>")
html.print("<tr><td align=center>" .. host .. "</td><td align=center><small>" .. ip .. "</small></td>")
html.print("<td align=center><small>" .. mac .. "</small></td><td></td><td><nobr>&nbsp;<input " .. (mac_list[mac] and "disabled " or "") .. "type=submit name=lease" .. i .. "_add value=Add ")
html.print("title='Use these values as an address reservation'></nobr></td></tr>")
hide("<input type=hidden name=lease" .. i .. "_host value=" .. host .. ">")
hide("<input type=hidden name=lease" .. i .. "_ip value=" .. ip .. ">")
hide("<input type=hidden name=lease" .. i .. "_mac value=" .. mac .. ">")
end
end
if i == 0 then
html.print("<tr><td align=center colspan=4>there are no active leases</td></tr>")
end
html.print("</table>")
end
function print_forwarding()
html.print("<table cellpadding=0 cellspacing=0>")
html.print("<tr><th colspan=6>Port Forwarding</th></tr>")
html.print("<tr><td colspan=6 height=5></td></tr>")
html.print("<tr><td align=center>Interface</td><td align=center>Type</td>")
html.print("<td align=center>Outside Port</td><td align=center>LAN IP</td>")
html.print("<td align=center>LAN Port</td><td>&nbsp;</td></tr>")
html.print("<tr><td colspan=6 height=5></td></tr>")
local list = make_addable_list(parms.port_num)
local vars = { "_intf", "_type", "_out", "_ip", "_in", "_enable", "_adv", "_link", "_proto", "_suffix", "_name" }
for _, val in ipairs(list)
do
for _, var in ipairs(vars)
do
_G[var] = parms["port" .. val .. var]
end
print_new_entry_vsep(val, list, 6)
html.print("<tr>")
hide("<input style='width:90;' type=hidden name=port" .. val .. "_enable value=1>")
-- port forwarding settings
html.print("<td align=center valign=top><select name=port" .. val .. "_intf title='forward inbound packets from this interface'>")
if dmz_mode == 0 then
html.print("<option " .. (_intf == "wifi" and "selected" or "") .. " value='wifi'>WiFi</option>")
html.print("<option " .. (_intf == "wan" and "selected" or "") .. " value='wan'>WAN</option>")
html.print("<option " .. (_intf == "both" and "selected" or "") .. " value='both'>Both</option>")
else
html.print("<option " .. (_intf == "wan" and "selected" or "") .. " value='wan'>WAN</option>")
end
html.print("</select></td>")
html.print("<td align=center valign=top><select name=port" .. val .. "_type>")
html.print("<option " .. (_type == "tcp" and "selected" or "") .. " value='tcp'>TCP</option>")
html.print("<option " .. (_type == "udp" and "selected" or "") .. " value='udp'>UDP</option>")
html.print("<option " .. (_type == "both" and "selected" or "") .. " value='both'>Both</option>")
html.print("</select></td>")
html.print("<td align=center valign=top><input style='width:90;' type=text placeholder='9998-9999' title='integer 1-65535 or two integers separated by a hyphen' name=port" .. val .. "_out value='"
.. _out .. "'" .. js_port_range_pattern .. require_existing(val) .. "></td>")
html.print("<td align=center valign=top><select name=port" .. val .. "_ip>")
if val == "_add" then
html.print("<option value=''>- IP Address -</option>")
end
local lanlimit = nixio.bit.lshift(1, 32 - netmask_to_cidr(lanmask)) - 2
for i = 1,lanlimit
do
local selip = decimal_to_ip(lannet_d + i)
local ipname = dhcphosts[selip]
if not ipname then
ipname = selip
end
html.print("<option " .. (_ip == selip and "selected" or "") .. " value='" .. selip .. "'>" .. ipname .. "</option>")
end
html.print("</select></td>")
html.print("<td align=left valign=top><input style='width:90;' type='number' min='1' max='65535' placeholder='9998' title='integer 1-65535' name=port"
.. val .. "_in value='" .. _in .. "'" .. require_existing(val) .. "></td>")
html.print("<td><nobr>&nbsp;<input type=submit name=")
if val == "_add" then
html.print("port_add value=Add title='Add this as a port forwarding rule'")
else
html.print("port" .. val .. "_del value='Del ' title='Remove this rule'")
end
html.print("></nobr></td></tr>")
-- display any errors
while #port_err > 0 and port_err[1]:match("^" .. val .. " ")
do
local err = port_err[1]:gsub("^%S+ ", "")
html.print("<tr><th colspan=7>" .. err .. "</th></tr>")
table.remove(port_err, 1)
end
html.print("<tr><td colspan=6 height=5></td></tr>")
end
-- dmz server for nat mode
if dmz_mode == 0 then
html.print("<tr><td colspan=7 height=10></td></tr>")
html.print("<tr><td colspan=4 align=right>DMZ Server &nbsp; </td>")
html.print("<td colspan=3><select name=dmz_ip onChange='form.submit()' ")
html.print("title='Send all other inbound traffic to this host'>")
html.print("<option value=''>None</option>")
local lanlimit = nixio.bit.lshift(1, 32 - netmask_to_cidr(lanmask)) - 2
for i = 1,lanlimit
do
local selip = decimal_to_ip(lannet_d + i)
if selip ~= lanip then
local ipname = dhcphosts[selip]
if not ipname then
ipname = selip
end
html.print("<option " .. (parms.dmz_ip == selip and "selected" or "") .. " value='" .. selip .. "'>" .. ipname .. "</option>")
end
end
html.print("</select></td>")
print_errors(dmz_err)
end
html.print("</table>")
end
function print_services()
local activesvc = nil
if nixio.fs.stat("/var/etc/olsrd.conf") then
activesvc = {}
for line in io.lines("/var/etc/olsrd.conf")
do
local plparam = line:match([[^%s*(PlParam "service" ".*")%s*$]])
if plparam then
activesvc[plparam] = true
end
end
end
html.print("<table cellpadding=0 cellspacing=0><tr><th colspan=4>Advertised Services</th></tr>")
if not (dmz_mode ~= 0 or parms.port_num ~= 0 or parms.dmz_ip) then
if dmz_mode ~= 0 then
html.print("<tr><td>&nbsp;</td></tr><tr><td height=10></td></tr>")
else
html.print("<tr><td>&nbsp;<br><br></td></tr>")
end
html.print("<tr><td colspan=4 align=center>none</td></tr>")
html.print("</table>")
return;
end
if dmz_mode ~= 0 then
html.print("<tr><td height=5></td></tr>")
html.print("<tr><td>Name</td><td>Link</td><td>URL</td><td></td></tr>")
html.print("<tr><td height=5></td></tr>")
else
html.print("<tr><td>Name</td><td>Link</td><td>URL</td><td><br><br></td></tr>")
end
local list = make_addable_list(parms.serv_num)
local vars = { "_name", "_link", "_proto", "_host", "_port", "_suffix" }
for _, val in ipairs(list)
do
for _, var in ipairs(vars)
do
_G[var] = parms["serv" .. val .. var]
end
if dmz_mode == 0 then
_host = node
parms["serv" .. val .. "_host"] = node
end
print_new_entry_vsep(val, list, 4)
html.print("<tr>")
html.print("<td><input type=text style='width:120;' placeholder='Service Name' name=serv" .. val .. "_name value='" .. _name .. "' title='what to call this service'></td>")
html.print("<td><nobr><input type=checkbox name=serv" .. val .. "_link value=1")
if val ~= "_add" then
html.print(" onChange='form.submit()'")
end
if _link == "1" then
html.print(" checked")
end
html.print(" title='create a clickable link for this service'>")
html.print("<input type=text style='width:40;' name=serv" .. val .. "_proto value='" .. _proto .. "' title='URL Protocol'")
if val ~= "_add" and _link ~= "1" then
html.print(" disabled")
end
html.print("></nobr></td>")
if dmz_mode ~= 0 then
html.print("<td><nobr><b>:</b>//<select name=serv" .. val .. "_host")
if val ~= "_add" and _link ~= "1" then
html.print(" disabled")
end
html.print(">")
html.print("<option " .. (node == _host and "selected" or "") .. " value='" .. node .. "'>" .. node .. "</option>")
for i = 1,parms.alias_num
do
html.print("<option " .. (parms["alias" .. i .. "_host"] == _host and "selected" or "") .. " value='" .. parms["alias" .. i .. "_host"] .. "'>" .. parms["alias" .. i .. "_host"] .. "</option>")
end
for i = 1,parms.dhcp_num
do
if parms["dhcp" .. i .. "_noprop"] ~= "#NOPROP" then
html.print("<option " .. (parms["dhcp" .. i .. "_host"] == _host and "selected" or "") .. " value='" .. parms["dhcp" .. i .. "_host"] .. "'>" .. parms["dhcp" .. i .. "_host"] .. "</option>")
end
end
html.print("</select>")
else
html.print("<td style='width:99%'><nobr><b>:</b>//<small>" .. _host .. "</small>")
end
html.print("<b>:</b><input type=text style='width:40;' name=serv" .. val .. "_port value='" .. _port .. "' title='port number'")
if val ~= "_add" and _link ~= "1" then
html.print(" disabled")
end
html.print("> / <input type=text name=serv" .. val .. "_suffix value='" .. _suffix .. "' ")
html.print("title='leave blank unless the URL needs a more specific path'")
if val ~= "_add" and _link ~= "1" then
html.print(" disabled")
end
html.print("></nobr></td>")
html.print("<td><nobr>&nbsp;<input type=submit name=")
if val == "_add" then
html.print("serv_add value=Add title='Add this as a service'>")
else
html.print("serv" .. val .. "_del value='Del ' title='Remove this service'>")
end
local svc = string.format([[PlParam "service" "%s://%s:%s/%s|tcp|%s"]], (_link == "1" and _proto or "http"), _host, (_link == "1" and _port or "0"), _suffix, _name)
if val ~= "_add" and activesvc and not activesvc[svc] then
html.print("<img style='vertical-align:middle' src='/alert-circle-outline.png' title='Service is not being advertised'>")
end
html.print("</nobr></td></tr>")
-- display any errors
while #serv_err > 0 and serv_err[1]:match("^" .. val .. " ")
do
html.print("<tr><th colspan=4>" .. serv_err[1]:gsub("^%S+ ", "") .. "</th></tr>")
table.remove(serv_err, 1)
end
if _link ~= "1" and val ~= "_add" then
hide("<input type=hidden name=serv" .. val .. "_proto value='" .. _proto .. "'>")
hide("<input type=hidden name=serv" .. val .. "_host value='" .. _host .. "'>")
hide("<input type=hidden name=serv" .. val .. "_port value='" .. _port .. "'>")
hide("<input type=hidden name=serv" .. val .. "_suffix value='" .. _suffix .. "'>")
end
html.print("<tr><td colspan=4 height=4></td></tr>")
end
html.print("</table>")
end
function print_aliases()
html.print("<table cellpadding=0 cellspacing=0><tr><th colspan=4>DNS Aliases</th></tr>")
html.print("<tr><td colspan=3 height=5></td></tr>")
html.print("<tr><td align=center>Alias Name</td><td></td><td align=center>IP Address</td></tr>")
html.print("<tr><td colspan=3 height=5></td></tr>")
local list = make_addable_list(parms.alias_num)
for _, val in ipairs(list)
do
local host = parms["alias" .. val .. "_host"]
local ip = parms["alias" .. val .. "_ip"]
print_new_entry_vsep(val, list , 4)
html.print("<tr><td align=center><input type=text placeholder='alias-name' title='alphanumerics and embedded hyphens' name=alias" .. val .. "_host value='" .. host .. "' size=20" .. js_host_pattern .. require_existing(val) .. "></td>")
html.print("<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>")
html.print("<td align=center><select name=alias" .. val .. "_ip>")
if val == "_add" then
html.print("<option value=''>- IP Address -</option>")
end
for i = dhcp_start,dhcp_end
do
local selip = decimal_to_ip(lannet_d + i - lannet_d % 256)
if selip ~= lanip then
local ipname = dhcphosts[selip]
if not ipname then
ipname = selip
end
html.print("<option " .. (ip == selip and "selected" or "") .. " value='" .. selip .. "'>" .. ipname .. "</option>")
end
end
html.print("</select></td>")
html.print("<td><nobr>&nbsp;<input type=submit name=")
if val == "_add" then
html.print("alias_add value=Add title='Add Alias'")
else
html.print("alias" .. val .. "_del value='Del ' title='Remove Alias'")
end
html.print("></nobr></td></tr>")
end
for _, e in ipairs(alias_err)
do
html.print("<tr><th colspan=4>" .. e:gsub("^%S+ ", "") .. "</th></tr>")
end
html.print("</table>")
end
function print_dhcp_cond_selector(row, selected_key, allow_unset, patname)
local field_name = "dhcptag" .. row .. "_cond"
html.print("<td align=center style='padding:0 4px'><select name='" .. field_name
.. "' title='Type of identifying information sent by the client' "
.. "onchange='setMatcherValidator(\"" .. patname .. "\",this.value);'>")
if allow_unset then
html.print("<option value=''>-Parameter-</option>")
end
for _, condition in ipairs(dhcp_tag_conds)
do
html.print("<option " .. (condition.key == selected_key and "selected " or "")
.. "value=\"" .. condition.key .. "\">" .. condition.name .. "</option>")
end
html.print("</select></td>")
end
function dhcp_tag_used(tag_name)
if tag_name == "" then
return false
end
for i = 1,parms.dhcpoptions_num
do
if tag_name == parms["dhcpopt" .. i .. "_tag"] then
return true
end
end
return false
end
function print_dhcp_tags()
html.print("<table cellpadding=0 cellspacing=0><tr><th colspan=4>Tags for Advanced DHCP Options</th></tr>")
print_heading_vsep()
html.print("<tr><td align=center>Set a Tag Named</td><td align=center>When Client's</td><td align=center style='width:99%'>Matches</td>")
local list = make_addable_list(parms.dhcptags_num)
for _, val in ipairs(list)
do
local prefix = "dhcptag" .. val .. "_"
local name = parms[prefix .. "name"]
local cond_key = parms[prefix .. "cond"]
local matcher = parms[prefix .. "pat"]
print_new_entry_vsep(val, list, 4)
html.print("<tr><td><input style='width:110px' type='text' placeholder='tagname' name='" .. prefix .. "name' value='" .. name
.. "' pattern='^[a-zA-Z0-9]+$' title='Alphanumeric string identifying the tag to assign'"
.. require_existing(val) .. "></td>")
local patname = prefix .. "pat"
print_dhcp_cond_selector(val, cond_key, val == "_add", patname)
local t = {"<td><input style='width:99%' name='",
patname,
"' value='",
matcher,
"' id='",
patname,}
if val ~= "_add" then
local cond = dhcp_tag_validators[cond_key]
table.insert(t, "' required title='")
table.insert(t, cond.hint)
table.insert(t, "' placeholder='")
table.insert(t, cond.jsPlaceholder)
if cond.jsPattern ~= nil then
table.insert(t, "' pattern='")
table.insert(t, (cond.jsPattern:gsub("\\\\", "\\")))
end
end
table.insert(t, "'></td>")
html.print(table.concat(t, ''))
html.print("<td><nobr>&nbsp;<input type=submit name=")
if val == "_add" then
html.print("dhcptag_add value=Add title='Add DHCP Tag'")
else
html.print(prefix .. "del value='Del ' title=")
if dhcp_tag_used(name) then
html.print("'Cannot remove; tag is in use by an option.' disabled='disabled'")
else
html.print("'Remove DHCP Tag'")
end
end
html.print("></nobr></td></tr>")
end
print_errors(dhcptag_err)
html.print("</table>")
end
function get_dhcp_tag_names()
local tag_hash = {}
local names = {}
for val = 1,parms.dhcptags_num
do
local name = parms["dhcptag" .. val .. "_name"]
if name and not tag_hash[name] then
tag_hash[name] = 0
table.insert(names, name)
end
end
table.sort(names)
return names
end
function print_dhcp_tag_selector(row, tag_name, all_tags)
local field_name = "dhcpopt" .. row .. "_tag"
html.print("<td><select name='" .. field_name .. "' title='Only send this option to clients with this tag'>")
html.print("<option value=''>[any]</option>")
for _, name in ipairs(all_tags)
do
local sel = name == tag_name and "selected " or ""
html.print("<option " .. sel .. "value=\"" .. name .. "\">" .. name .. "</option>")
end
html.print("</select></td>")
end
function print_dhcp_option_selector(known_options, row, opt_num)
local field_name = "dhcpopt" .. row .. "_num"
-- custom-value option adapted from https://jsfiddle.net/6nq7w/4/
html.print("<td align=center style='padding:0 4px'><select name=\"" .. field_name .. '"')
html.print([[ onchange="if(this.options[this.selectedIndex].value=='[numeric]'){
toggleField(this,this.nextSibling);
this.selectedIndex='0';
}">
<option value="">- DHCP Option Number -</option>
<option value="[numeric]">[other option number]</option>]])
local found = false
for _, option in ipairs(known_options)
do
local sel = ""
local nnum = tonumber(opt_num)
if option.num == nnum then
found = true
sel = "selected "
elseif nnum and not found and nnum < option.num then -- not a known option
found = true
html.print("<option selected value=" .. nnum .. ">" .. nnum .. "</option>")
end
html.print("<option " .. sel .. "value=" .. option.num .. ">" .. option.num .. " (" .. option.name .. ")</option>")
end
-- do not insert any whitespace between the following two HTML tags
html.print("</select><input name=\"" .. field_name .. '"')
html.print([[ type="number" min=1 max=254 style="display:none;" disabled="disabled"
onblur="if(this.value==''){toggleField(this,this.previousSibling);}"></td>]])
end
-- load all known DHCP option names
function load_known_options()
local known_options = {}
local optlist = io.popen("/usr/sbin/dnsmasq --help dhcp")
if optlist then
for line in optlist:lines()
do
local num, name = line:match("%s*(%d+)%s+(%S+)")
if name then
table.insert(known_options, {num = tonumber(num), name = name})
end
end
optlist:close()
end
return known_options
end
function print_dhcp_options()
html.print("<table cellpadding=0 cellspacing=0><tr><th colspan=5>Advanced DHCP Options</th></tr>")
print_heading_vsep()
html.print("<tr><td align=center>For Tag</td><td align=center>Always</td><td align=center>Send DHCP Option</td><td align=center style='width:99%'>With Value</td></tr>")
local known_options = load_known_options()
local all_tags = get_dhcp_tag_names()
local list = make_addable_list(parms.dhcpoptions_num)
for _, val in ipairs(list)
do
local prefix = "dhcpopt" .. val .. "_"
local tag = parms[prefix .. "tag"] or ""
local force = parms[prefix .. "force"]
local opt_num = tonumber(parms[prefix .. "num"])
local opt_val = parms[prefix .. "val"] or ""
print_new_entry_vsep(val, list, 5)
html.print("<tr>")
print_dhcp_tag_selector(val, tag, all_tags)
html.print("<td align=center><input type='checkbox' name='" .. prefix .. "force' value='force' "
.. (force == "force" and "checked " or "")
.. "title='Send option even when not requested by client'/></td>")
print_dhcp_option_selector(known_options, val, opt_num)
html.print("<td><input type='text' style='width:99%' name='" .. prefix .. "val' value='" .. opt_val .. "'></td>")
html.print("<td><nobr>&nbsp;<input type=submit name=")
if val == "_add" then
html.print("dhcpopt_add value=Add title='Add DHCP Option'")
else
html.print(prefix .. "del value='Del ' title='Remove DHCP Option'")
end
html.print("></nobr></td></tr>")
end
print_errors(dhcpopt_err)
html.print("</table>")
end
html.print("<tr><td align=center>")
html.print("<table width=100%><tr><td align=center valign=top>")
if dmz_mode ~= 0 then
print_reservations()
else
print_forwarding()
end
html.print("</td>")
html.print("<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>")
html.print("<td align=center valign=top>")
print_services()
html.print("</td>")
html.print("</tr>")
html.print("<tr><td colspan=3><hr></td></tr>")
html.print("<tr><td valign=top>")
print_dhcp_tags()
html.print("</td><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td><td valign=top>")
print_dhcp_options()
html.print("</td></tr>")
html.print("<tr><td colspan=3><hr></td></tr>")
html.print("<tr><td>")
if dmz_mode ~= 0 then
print_forwarding()
else
print_reservations()
end
html.print("</td>")
html.print("<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>")
html.print("<td align=center valign=top>")
print_aliases()
html.print("</td></tr>")
html.print("</table></td></tr>")
html.print("<tr><td>")
html.footer()
html.print(" </td></tr></table>")
hide("<input type=hidden name=port_num value=" .. parms.port_num .. ">")
hide("<input type=hidden name=dhcp_num value=" .. parms.dhcp_num .. ">")
hide("<input type=hidden name=dhcptags_num value=" .. parms.dhcptags_num .. ">")
hide("<input type=hidden name=dhcpoptions_num value=" .. parms.dhcpoptions_num .. ">")
hide("<input type=hidden name=serv_num value=" .. parms.serv_num .. ">")
hide("<input type=hidden name=alias_num value=" .. parms.alias_num .. ">")
for _, h in ipairs(hidden)
do
html.print(h)
end
html.print("</form></center></body></html>")
http_footer()