#!/usr/bin/lua --[[ Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks Copyright (C) 2021 Tim Wilkinson Original Perl Copyright (c) 2015 Darryl Quinn 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 . 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.http") require("aredn.utils") require("aredn.html") require("aredn.hardware") aredn.info = require("aredn.info") require("uci") require("luci.sys") local html = aredn.html local cursor = uci.cursor(); local node = aredn.info.get_nvram("node") if node == "" then node = "NOCALL" end local config = aredn.info.get_nvram("config"); local VPNVER = "1.1" -- post_data local parms = {} if os.getenv("REQUEST_METHOD") == "POST" then require('luci.http') local request = luci.http.Request(luci.sys.getenv(), function() local v = io.read(1024) if not v then io.close() end return v end ) parms = request:formvalue() end -- helpers start local cli_err = {} function err(msg) cli_err[#cli_err + 1] = msg end local errors = {} function err2(msg) errors[#errors + 1] = msg end local hidden = {} function hide(inp) hidden[#hidden + 1] = inp end function navbar() html.print("") html.print("
") html.print("") html.print("") html.print("") html.print("") html.print("") html.print("") html.print("") html.print("
Node StatusBasic SetupPort Forwarding,
DHCP, and Services
Tunnel
Client
AdministrationAdvanced
Configuration

") end function get_active_tun() local tuns = {} local f = io.popen("ps -w | grep vtun | grep ' tun '") if f then for line in f:lines() do local m = line:match(".*:.*-(172%-31%-.*)%stun%stun.*") if m then tuns[#tuns + 1] = m:gsub("-", ".") end end f:close() end return tuns end function is_tunnel_active(ip, tunnels) for _, aip in ipairs(tunnels) do if ip == aip then return true end end return false end function get_server_network_address() local server_net = cursor:get("vtun", "@network[0]", "start") if not server_net then local mac = aredn.hardware.get_interface_mac("eth0") local a, b = mac:match("^..:..:..:..:(..):(..)$") server_net = "172.31." .. tonumber(b, 16) .. "." .. ((tonumber(a, 16) * 4) % 256) cursor:set("vtun", "@network[0]", "start", server_net) cursor:commit("vtun") end local a, b, c, d = server_net:match("^(%d+).(%d+).(%d+).(%d+)$") return { a, b, c, d } end function get_server_dns() local dns = cursor:get("vtun", "@network[0]", "dns") return dns and dns or "" end -- helper end -- load client info from uci local gci_vars = { "enabled", "name", "passwd", "netip", "contact" } function get_client_info() local c = 0 cursor:foreach("vtun", "client", function(section) for _, var in ipairs(gci_vars) do local key = "client" .. c .. "_" .. var parms[key] = section[var] if not parms[key] then parms[key] = "" end end c = c + 1 end ) parms.client_num = c end if parms.button_reboot then luci.sys.reboot() os.exit() end if config == "" or nixio.fs.stat("/tmp/reboot-required") then http_header(); html.header(node .. " setup", true); html.print("
") html.alert_banner() html.print("") html.print("
") navbar(); html.print("

") if config == "" then html.print("This page is not available until the configuration has been set.") else html.print("The configuration has been changed.
This page will not be available until the node is rebooted.
") html.print("
") html.print("") html.print("
") end html.print("
") http_footer() os.exit(); end if parms.button_reset then cursor:revert("vtun") cursor:delete("vtun", "@network[0]", "start") cursor:delete("vtun", "@network[0]", "dns") cursor:commit("vtun") end -- get vtun network address local netw = get_server_network_address() local dns = get_server_dns() -- if RESET or FIRST TIME load client/servers from file into parms if parms.button_reset or not parms.reload then cursor:revert("vtun") get_client_info() parms.server_net1 = netw[3] parms.server_net2 = netw[4] parms.dns = dns -- initialzie the "add" entries to clear them parms.client_add_enabled = "0" parms.client_add_name = "" parms.client_add_passwd = "" end local list = {} for i = 0,parms.client_num-1 do list[#list + 1] = i end list[#list + 1] = "_add" local client_num = 0 local vars = { "enabled", "name", "passwd", "netip", "contact" } local vars2 = { "net", "enabled", "name", "passwd", "netip", "contact" } for _, val in ipairs(list) do for _ = 1,1 do for _, var in ipairs(vars) do local varname = "client" .. val .. "_" .. var if var == "enabled" and not parms[varname] then parms[varname] = "0" elseif not parms[varname] then parms[varname] = "" elseif var == "contact" then parms[varname] = parms[varname]:gsub("^%s+", ""):gsub("%s+$", ""):sub(1,210):gsub('"',"""):gsub("'","'"):gsub("<","<"):gsub(">",">") else parms[varname] = parms[varname]:gsub("^%s+", ""):gsub("%s+$", "") end if val ~= "_add" and parms[varname] == "" and var == "enabled" then parms[varname] = "0" end _G[var] = parms[varname] end if val == "_add" and not ((enabled ~= "0" or name ~= "" or passwd ~= "" or contact ~= "") and (parms.client_add or parms.button_save)) then break end if val == "_add" and parms.button_save then err(val .. " this client must be added or cleared out before saving changes") break end if passwd:match("[^%w@]") then err("The password cannot contain non-alphanumeric characters (#" .. client_num .. ")") end if not passwd:match("%a") then err("The password must contain at least one alphabetic character (#" .. client_num .. ")") end if name == "" then err("A client name is required") end if passwd == "" then err("A client password is required") end if val == "_add" and #cli_err > 0 and cli_err[#cli_err]:match("^" .. val .. " ") then break end parms["client" .. client_num .. "_enabled"] = enabled parms["client" .. client_num .. "_name"] = name:upper() parms["client" .. client_num .. "_passwd"] = passwd parms["client" .. client_num .. "_netip"] = netip parms["client" .. client_num .. "_contact"] = contact -- commit the data from this client client_num = client_num + 1 -- clear out the ADD values if val == "_add" then for _, var in ipairs(vars2) do parms["client_add_" .. var] = "" end end end end parms.client_num = client_num -- SAVE the server network numbers and dns into the UCI netw[3] = parms.server_net1 netw[4] = parms.server_net2 dns = parms.dns if not tonumber(parms.server_net1) or tonumber(parms.server_net1) < 0 or tonumber(parms.server_net1) > 255 then err("The third octet of the network MUST be from 0 to 255") end if not tonumber(parms.server_net2) or tonumber(parms.server_net2) < 0 or tonumber(parms.server_net2) > 255 then err("The last octet of the network MUST be from 0 to 255") end if not tonumber(parms.server_net2) or tonumber(parms.server_net2) %4 ~= 0 then err("The last octet of the network MUST be a multiple of 4 (ie. 0,4,8,12,16,...)") end if not validate_fqdn(dns) then err("Not a valid DNS name") end if #cli_err == 0 then local net = "172.31." .. parms.server_net1 .. "." .. parms.server_net2 cursor:set("vtun", "@network[0]", "start", net) cursor:set("vtun", "@network[0]", "dns", dns) end -- SAVE the clients local enabled_count = 0 for i = 0,client_num-1 do local clientx = "client" .. i local client_x = "client_" .. i local net = parms[clientx .. "_netip"] local vtun_node_name = (parms[clientx .. "_name"]:sub(1,23) .. "-" .. net:gsub("%.", "-")):upper() local base = ip_to_decimal(net) local clientip = decimal_to_ip(base + 1) local serverip = decimal_to_ip(base + 2) if not cursor:get("vtun", client_x) then cursor:set("vtun", client_x, 'client') end cursor:set("vtun", client_x, "netip", net) cursor:set("vtun", client_x, "enabled", parms[clientx .. "_enabled"]) cursor:set("vtun", client_x, "name", parms[clientx .. "_name"]) cursor:set("vtun", client_x, "contact", parms[clientx .. "_contact"]) cursor:set("vtun", client_x, "passwd", parms[clientx .. "_passwd"]) cursor:set("vtun", client_x, "clientip", clientip) cursor:set("vtun", client_x, "serverip", serverip) cursor:set("vtun", client_x, "node", vtun_node_name) if parms[clientx .. "_enabled"] == "1" then enabled_count = enabled_count + 1 end end local maxclients = tonumber(cursor:get("aredn", "@tunnel[0]", "maxclients")) if not maxclients then maxclients = 10 end if enabled_count > maxclients then err("Number of clients enabled (" .. enabled_count .. " exceeds maxclients (" .. maxclients .. "); only the first 100 will activate.") end -- save configuration (commit) if parms.button_save and #cli_err == 0 then cursor:commit("vtun") write_all("/etc/config.mesh/vtun", read_all("/etc/config/vtun")) if os.execute("/etc/init.d/olsrd restart > /dev/null 2>&1") ~= 0 then err2("Problem restarting olsrd") end if os.execute("/etc/init.d/vtundsrv restart > /dev/null 2>&1") ~= 0 then err2("Problem restaring vtundsrv") end end local active_tun = get_active_tun() -- generate the page http_header() html.header(node .. " setup", true) html.print("
") html.alert_banner() html.print("
") html.print("") html.print("") -- navigation bar navbar() html.print("") -- control buttons html.print("") hide("") -- messages if #cli_err > 0 then html.print("") end if parms.button_save then if #cli_err > 0 then html.print("") for _,msg in ipairs(errors) do html.print(msg .. "
") end html.print("") else html.print("") end html.print("") end -- everything else if config == "mesh" then html.print("") end html.print("
") html.print("Help") html.print("   ") html.print(" ") html.print(" ") html.print(" ") html.print("
 
ERROR:
") for _,msg in ipairs(cli_err) do html.print(msg .. "
") end html.print("
Configuration NOT saved!
Configuration saved and is now active.
 
") -- print vpn clients html.print("") html.print("
") html.print("
Tunnel Server Network: ") html.print(netw[1] .. "." .. netw[2] .. "..") html.print("

Tunnel Server DNS Name: ") html.print("
") html.print("") html.print("") html.print("") html.print("") html.print("") html.print("") -- loop local list = {} for i = 0,client_num-1 do list[#list+1] = i end if client_num < 100 then list[#list+1] = "_add" end local keys = { "enabled", "name", "passwd", "contact" } local cnum = 0 for _, val in ipairs(list) do for _, var in ipairs(keys) do _G[var] = parms["client" .. val .. "_" .. var] end if val == "_add" and #list > 1 then html.print("") end html.print("") html.print("") html.print("") html.print("") -- handle rollover of netw local net if netw[4] + cnum * 4 > 252 then netw[3] = netw[3] + 1 netw[4] = 0 net = 0 cnum = 0 else net = cnum end local lastnet = netw[4] + net * 4 local fullnet = netw[1] .. "." .. netw[2] .. "." .. netw[3] .. "." .. lastnet html.print("") html.print("") if val == "_add" then html.print("") else html.print("") end html.print("") -- display any errors while #cli_err > 0 and cli_err[1]:match("^" .. val .. " ") do html.print("") cli_err:remove(1) end html.print("") cnum = cnum + 1 end html.print("
 
Allow the following clients to connect to this server:

Enabled?ClientPwdNetActive Action
") html.print(" " .. fullnet) html.print(" ") if val ~= "_add" and is_tunnel_active(fullnet, active_tun) then html.print("") else html.print("") end html.print("
Contact Info/Comment (Optional):
" .. err:gsub("^%S+ ", "") .. "
") -- html.print("

Tunnel v" .. VPNVER .. "

") hide("") -- add hidden forms fields for _, h in ipairs(hidden) do html.print(h) end -- close the form html.print("
") html.footer() html.print("") http_footer()