#!/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 -- truncate node name down to 23 chars (max) to avoid vtun issues -- this becomes the vtun "username" node = node:sub(1, 23) local config = aredn.info.get_nvram("config"); local VPNVER = "1.0" -- 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 hidden = {} function hide(inp) hidden[#hidden + 1] = inp end local conn_err = {} function err(msg) conn_err[#conn_err + 1] = msg end local errors = {} function err2(msg) errors[#errors + 1] = msg 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
Server
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 install_vtun() local vfs = nixio.fs.statvfs("/overlay") local fspace = vfs.bfree * vfs.bsize / 1024 if fspace < 600 then err("Insufficient free disk space") return end if os.execute("opkg update > /dev/null 2>&1") ~= 0 then err("Package update failed!") return end if os.execute("opkg install kmod-tun zlib liblzo vtun > /dev/null 2>&1") ~= 0 then err("Package installation failed!") return end if not cursor:get("aredn", "@tunnel[0]") then cursor:add("aredn", "tunnel") end cursor:set("aredn", "@tunnel[0]", "maxclients", "10") cursor:set("aredn", "@tunnel[0]", "maxservers", "10") cursor:commit("aredn") write_all("/etc/config.mesh/aredn", read_all("/etc/config/aredn")) io.open("/etc/config/network_tun", "w"):close() for tunnum = 50,69 do cursor:set("network_tun", "tun" .. tunnum, "interface") cursor:set("network_tun", "tun" .. tunnum, "ifname", "tun" .. tunnum) cursor:set("network_tun", "tun" .. tunnum, "proto", "none") end cursor:commit("network_tun") write_all("/etc/config.mesh/network_tun", read_all("/etc/config/network_tun")) os.execute("cat /etc/config.mesh/network_tun >> /etc/config.mesh/network") os.execute("cat /etc/config.mesh/network_tun >> /etc/config/network") io.open("/etc/config/vtun", "a"):close() cursor:add("vtun", "options") cursor:add("vtun", "network") cursor:commit("vtun") write_all("/etc/config.mesh/vtun", read_all("/etc/config/vtun")) http_header() html.header("TUNNEL INSTALLATION IN PROGRESS", true) html.print("
") html.print("

Installing tunnel software...

") html.print("

DO NOT REMOVE POWER UNTIL THE INSTALLATION IS FINISHED

") html.print("

") html.print([[

The node is rebooting

When the node has fully rebooted you can reconnect with
http://]] .. node .. [[.local.mesh:8080/

]]) html.footer() html.print("") http_footer() luci.sys.reboot() os.exit() end -- helpers end local gci_vars = { "enabled", "host", "passwd", "netip", "contact" } function get_connection_info() local c = 0 cursor:foreach("vtun", "server", function(section) for _, var in ipairs(gci_vars) do local key = "conn" .. c .. "_" .. var parms[key] = section[var] if not parms[key] then parms[key] = "" end end c = c + 1 end ) parms.conn_num = c end if parms.button_reboot then luci.sys.reboot() os.exit() end if parms.button_install then install_vtun() 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("") navbar(); html.print("") 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 not nixio.fs.stat("/usr/sbin/vtund") then http_header(); html.header(node .. " setup", true); html.print("
") html.alert_banner() html.print("") html.print("") if #conn_err > 0 then html.print("") end html.print("") html.print("
") navbar(); html.print("
ERROR:
") for _, e in ipairs(conn_err) do html.print(e .. "
") end html.print("

") html.print("Tunnel software needs to be installed.
") html.print("
") html.print("") html.print("
") html.print("
") http_footer() os.exit(); end if parms.button_reset then cursor:revert("vtun") cursor:commit("vtun") end -- handle connection deletes local do_delete = false for i = 0,9 do local varname = "conn" .. i .. "_del" if parms[varname] then do_delete = true cursor:delete("vtun", "server_" .. i) for x=i+1,9 do cursor:rename("vtun", "server_" .. x, "server_" .. (x - 1)) end end end if do_delete then cursor:commit("vtun") end -- if RESET or FIRST TIME, load servers into parms if parms.button_reset or not parms.reload then cursor:revert("vtun") get_connection_info() -- initialzie the "add" entries to clear them parms.conn_add_enabled = "0" parms.conn_add_host = "" parms.conn_add_passwd = "" parms.conn_add_netip = "" parms.conn_add_contact = "" end -- load connetions from FORM and validate local list = {} for i = 0,parms.conn_num-1 do list[#list + 1] = i end list[#list + 1] = "_add" local conn_num = 0 local vars = { "enabled", "host", "passwd", "netip", "contact" } for _, val in ipairs(list) do for _ = 1, 1 do for _, var in ipairs(vars) do local varname = "conn" .. 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" then if not ((enabled ~= "0" or host ~= "" or passwd ~= "" or netip ~= "" or contact ~= "") and (parms.conn_add or parms.button_save)) then break end elseif parms["conn" .. val .. "_del"] then break end if val == "_add" and parms.button_save then err(val .. " this connection must be added or cleared out before saving changes") break end if passwd:match("%W") then err("The password cannot contain non-alphanumeric characters (#" .. conn_num .. ")") end if host == "" then err("A connection server is required") end if passwd == "" then err("A connection password is required") end if netip == "" then err("A connection network IP is required") end if val == "_add" and #conn_err > 0 and conn_err[#conn_err]:match("^" .. val .. " ") then break end parms["conn" .. conn_num .. "_enabled"] = enabled parms["conn" .. conn_num .. "_host"] = host parms["conn" .. conn_num .. "_passwd"] = passwd parms["conn" .. conn_num .. "_netip"] = netip parms["conn" .. conn_num .. "_contact"] = contact conn_num = conn_num + 1 -- clear out the ADD values if val == "_add" then for _, var in ipairs(vars) do parms["conn_add_" .. var] = "" end end end end parms.conn_num = conn_num -- save the connections local enabled_count = 0 for i = 0,parms.conn_num-1 do local connx_ = "conn" .. i .. "_" local conn_x = "server_" .. i local net = parms[connx_ .. "netip"] local vtun_node_name = (node .. "-" .. 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", conn_x) then cursor:set("vtun", conn_x, "server") end cursor:set("vtun", conn_x, "clientip", clientip) cursor:set("vtun", conn_x, "serverip", serverip) cursor:set("vtun", conn_x, "node", vtun_node_name) cursor:set("vtun", conn_x, "enabled", parms[connx_ .. "enabled"]) cursor:set("vtun", conn_x, "host", parms[connx_ .. "host"]) cursor:set("vtun", conn_x, "passwd", parms[connx_ .. "passwd"]) cursor:set("vtun", conn_x, "netip", parms[connx_ .. "netip"]) cursor:set("vtun", conn_x, "contact", parms[connx_ .. "contact"]) if parms[connx_ .. "enabled"] == "1" then enabled_count = enabled_count + 1 end end local maxservers = tonumber(cursor:get("aredn", "@tunnel[0]", "maxservers")) if not maxservers then maxservers = 10 end if enabled_count > maxservers then err("Number of servers enabled (" .. enabled_count .. " exceeds maxservers (" .. maxservers .. "); only the first 10 will activate.") end -- save the connections the uci vtun file if parms.button_save and #conn_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/vtund restart > /dev/null 2>&1") ~= 0 then err2("Problem restaring vtund") end end local active_tun = get_active_tun() -- generate page http_header() html.header(node .. " setup", true) html.print("
") html.alert_banner() html.print("
") -- nav bar html.print("") -- control buttons html.print("") hide("") -- messages if #conn_err > 0 then html.print("") end if parms.button_save then if #conn_err > 0 then html.print("") elseif #errors > 0 then html.print("") else html.print("") end html.print("") end -- everything else if config == "mesh" then html.print("") end html.print("
") navbar() html.print("
") html.print("Help") html.print("   ") html.print(" ") html.print(" ") html.print(" ") html.print("
 
ERROR:
") for _,msg in ipairs(conn_err) do html.print(msg .. "
") end html.print("
Configuration NOT saved!
Configuration saved, however:
") for _,msg in ipairs(errors) do html.print(msg .. "
") end html.print("
Configuration saved and is now active.
 
") -- html.print("") html.print("") html.print("") html.print("") local list = {} for i = 0,parms.conn_num-1 do list[#list+1] = i end if parms.conn_num < 10 then list[#list+1] = "_add" end local keys = { "enabled", "host", "passwd", "netip", "contact" } local cnum = 0 for _, val in ipairs(list) do for _, var in ipairs(keys) do _G[var] = parms["conn" .. val .. "_" .. var] end if val == "_add" and #list > 1 then html.print("") end html.print("") html.print("") html.print("") html.print("") html.print("") html.print("") html.print("") html.print("") -- contact info for this tunnel html.print("") html.print("") html.print("") -- display any errors while #conn_err > 0 and conn_err[1]:match("^" .. val .. " ") do html.print("") conn_err:remove(i) end html.print("") cnum = cnum + 1 end html.print("
Connect this node to the following servers:

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

") html.print("

VPN v" .. VPNVER .. "

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