mirror of https://github.com/aredn/aredn.git
573 lines
18 KiB
Lua
Executable File
573 lines
18 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 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 <http://www.gnu.org/licenses/>.
|
|
|
|
Additional Terms:
|
|
|
|
Additional use restrictions exist on the AREDN® 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")
|
|
require("aredn.info")
|
|
require("uci")
|
|
|
|
local html = aredn.html
|
|
|
|
local cursor = uci.cursor("/etc/config.mesh");
|
|
|
|
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(nixio.getenv(),
|
|
function()
|
|
local v = io.read(1024)
|
|
if not v then
|
|
io.close()
|
|
end
|
|
return v
|
|
end
|
|
)
|
|
parms = request:formvalue()
|
|
end
|
|
|
|
-- wireguard
|
|
local wireguard_alive_time = 300 -- 5 minutes
|
|
|
|
-- 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 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%-.*)%stun%stun.*")
|
|
if m then
|
|
tuns[#tuns + 1] = m:gsub("-", ".")
|
|
end
|
|
end
|
|
f:close()
|
|
end
|
|
return tuns
|
|
end
|
|
|
|
function get_active_wgtun()
|
|
local tuns = {}
|
|
local f = io.popen("/usr/bin/wg show all latest-handshakes")
|
|
if f then
|
|
for line in f:lines()
|
|
do
|
|
local k,v = line:match("^%S+%s+(%S+)%s+(%S+)%s*$")
|
|
if k then
|
|
tuns[k] = tonumber(v) -- time in seconds
|
|
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 is_wgtunnel_active(key, wgtunnels)
|
|
local key = key:match("^(.*=).*=.*=$")
|
|
local v = wgtunnels[key]
|
|
if v and v + wireguard_alive_time > os.time() then
|
|
return true
|
|
end
|
|
return false
|
|
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
|
|
aredn.html.reboot()
|
|
end
|
|
|
|
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("vpnc")
|
|
html.print("<table width=790>")
|
|
html.print("<tr><td align=center><br>")
|
|
if config == "" then
|
|
html.print("<b>This page is not available until the configuration has been set.</b>")
|
|
else
|
|
html.print("<b>The configuration has been changed.<br>This page will not be available until the node is rebooted.</b>")
|
|
html.print("<form method='post' action='/cgi-bin/vpnc' enctype='multipart/form-data'>")
|
|
html.print("<input type=submit name=button_reboot value='Click to REBOOT' />")
|
|
html.print("</form>")
|
|
end
|
|
html.print("</td></tr>")
|
|
html.print("</table></center></body></html>")
|
|
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 netip:match(":") then
|
|
if not passwd:match("^.+=.+=.+=$") then
|
|
err("The password is not a wireguard key")
|
|
end
|
|
elseif 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
|
|
if cursor:get("aredn", "@supernode[0]", "enable") == "1" then
|
|
cursor:set("vtun", "@options[0]", "port", "5526")
|
|
else
|
|
cursor:delete("vtun", "@options[0]", "port")
|
|
end
|
|
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
|
|
|
|
-- save the connections the uci vtun file
|
|
if parms.button_save and #conn_err == 0 then
|
|
cursor:commit("vtun")
|
|
os.execute("/usr/local/bin/node-setup > /dev/null 2>&1")
|
|
os.execute("/usr/local/bin/restart-services.sh network tunnels firewall olsrd > /dev/null 2>&1")
|
|
end
|
|
|
|
local active_tun = get_active_tun()
|
|
local active_wgtun = get_active_wgtun()
|
|
|
|
-- generate page
|
|
http_header()
|
|
html.header(node .. " setup", false)
|
|
|
|
html.print([[
|
|
<script>
|
|
function configPaste(e) {
|
|
const txt = e.clipboardData.getData("text/plain");
|
|
if (!txt) {
|
|
return;
|
|
}
|
|
const config = {};
|
|
txt.split("\n").forEach(line => {
|
|
if (line.startsWith("Password: ")) {
|
|
config.passwd = line.substring(10);
|
|
}
|
|
else if (line.startsWith("Network: ")) {
|
|
config.network = line.substring(9);
|
|
}
|
|
else if (line.startsWith("Server address: ")) {
|
|
config.server = line.substring(16);
|
|
}
|
|
});
|
|
if (!(config.passwd && config.network && config.server)) {
|
|
return;
|
|
}
|
|
document.forms[0].conn_add_host.value = config.server;
|
|
document.forms[0].conn_add_passwd.value = config.passwd;
|
|
document.forms[0].conn_add_netip.value = config.network;
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
}
|
|
</script>
|
|
]])
|
|
|
|
html.print("</head><body><center>")
|
|
html.alert_banner()
|
|
|
|
html.print("<form method=post action=/cgi-bin/vpnc enctype='multipart/form-data'>")
|
|
|
|
-- nav bar
|
|
html.navbar_admin("vpnc")
|
|
|
|
-- control buttons
|
|
html.print("<table width=790><tr><td align=center>")
|
|
html.print("<a href='/help.html#tunnels' target='_blank'>Help</a>")
|
|
html.print(" ")
|
|
html.print("<input type=submit name=button_save value='Save Changes' title='Save and use these settings now (takes about 20 seconds)'> ")
|
|
html.print("<input type=submit name=button_reset value='Reset Values' title='Revert to the last saved settings'> ")
|
|
html.print("<input type=button name=button_refresh value='Refresh' title='Refresh this page' onclick='window.location.reload();'> ")
|
|
html.print("<tr><td> </td></tr>")
|
|
hide("<input type=hidden name=reload value=1></td></tr>")
|
|
|
|
local notunnels = not nixio.fs.stat("/usr/sbin/vtund")
|
|
if notunnels then
|
|
html.print("<tr><td align=center><span style=background-color:cyan;font-size:140%;> Tunnels are no longer supported on this hardware </span></td></tr>")
|
|
config = "notunnels"
|
|
-- low memory warning
|
|
elseif isLowMemNode() then
|
|
html.print("<tr><td align=center><span style=background-color:cyan;font-size:140%;> Recommend not to use tunneling due to low memory on this node </span></td></tr>")
|
|
end
|
|
|
|
-- messages
|
|
if #conn_err > 0 then
|
|
html.print("<tr><td align=center><b>ERROR:<br>")
|
|
for _,msg in ipairs(conn_err)
|
|
do
|
|
html.print(msg .. "<br>")
|
|
end
|
|
html.print("</b></td></tr>")
|
|
end
|
|
|
|
if parms.button_save then
|
|
if #conn_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 _,msg in ipairs(errors)
|
|
do
|
|
html.print(msg .. "<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> </td></tr>")
|
|
end
|
|
|
|
-- everything else
|
|
if config == "mesh" then
|
|
html.print("<tr><td align=center valign=top>")
|
|
--
|
|
html.print("<table id=connection_section cellpadding=0 cellspacing=0>")
|
|
html.print("<tr><th colspan=6>Connect this node to the following servers:</th></tr>")
|
|
html.print("<tr><th colspan=6><hr></th></tr>")
|
|
html.print("<tr><th>Enabled?</th><th>Server</th><th>Pwd</th><th>Network</th><th>Active </th><th>Action</th></tr>")
|
|
|
|
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("<tr><td height=10></td></tr>")
|
|
end
|
|
html.print("<tr class='tun_client_list2 tun_client_row'>")
|
|
html.print("<td class='tun_client_center_item' rowspan='2'>")
|
|
|
|
html.print("<input type='checkbox' name='conn" .. val .. "_enabled' value='1'")
|
|
if val ~= "_add" then
|
|
html.print(" onChange='form.submit()'")
|
|
end
|
|
if enabled == "1" then
|
|
html.print(" checked='checked'")
|
|
end
|
|
html.print(" title='enable this connection'></td>")
|
|
|
|
html.print("<td><input type=text size=25 name=conn" .. val .. "_host value='" .. host .. "'")
|
|
if val ~= "_add" then
|
|
html.print(" onChange='form.submit()'")
|
|
else
|
|
html.print(" onPaste='configPaste(event)'")
|
|
end
|
|
html.print(" title='connection name'></td>")
|
|
|
|
html.print("<td><input type=text size=20 name=conn" .. val .. "_passwd value='" .. passwd .. "' ")
|
|
if val ~= "_add" then
|
|
html.print(" onChange='form.submit()'")
|
|
else
|
|
html.print(" onPaste='configPaste(event)'")
|
|
end
|
|
html.print(" title='connection password'")
|
|
html.print("></td>")
|
|
|
|
html.print("<td><input type=text size=14 name=conn" .. val .. "_netip value='" .. netip .. "'")
|
|
if val ~= "_add" then
|
|
html.print(" onChange='form.submit()'")
|
|
else
|
|
html.print(" onPaste='configPaste(event)'")
|
|
end
|
|
html.print(" title='connection network'></td>")
|
|
|
|
html.print("</td>")
|
|
html.print("<td class='tun_client_center_item' rowspan='2'> ")
|
|
|
|
if val ~= "_add" then
|
|
if is_tunnel_active(netip, active_tun) or is_wgtunnel_active(passwd, active_wgtun) then
|
|
html.print("<img class='tun_client_active_img' src='/connected.png' title='Connected' />")
|
|
else
|
|
html.print("<img class='tun_client_inactive_img' src='/disconnected.png' title='Not connected' />")
|
|
end
|
|
end
|
|
html.print("</td>")
|
|
html.print("<td class='tun_client_center_item' rowspan='2'> ")
|
|
|
|
html.print("<input type=submit name=")
|
|
if val ~= "_add" then
|
|
html.print("conn" .. val .. "_del value=Del title='Delete this connection'")
|
|
else
|
|
html.print("conn_add value=Add title='Add this connection'")
|
|
end
|
|
html.print("></td>")
|
|
-- contact info for this tunnel
|
|
html.print("</tr>")
|
|
html.print("<tr class='tun_client_list1 tun_client_row tun_loading_css_comment'><td colspan='3' align='right'>Contact Info/Comment (Optional): <input type=text maxlength='50' size=40 name=conn" .. val .. "_contact value='" .. contact .. "'")
|
|
if val == "_add" or val == "" then
|
|
html.print(" onChange='form.submit()' onPaste='configPaste(event)'")
|
|
end
|
|
html.print(" title='client contact info'></td>")
|
|
|
|
html.print("</tr>")
|
|
|
|
-- display any errors
|
|
while #conn_err > 0 and conn_err[1]:match("^" .. val .. " ")
|
|
do
|
|
html.print("<tr><th colspan=4>" .. err:gsub("^%S+ ", "") .. "</th></tr>")
|
|
conn_err:remove(i)
|
|
end
|
|
|
|
html.print("<tr><td colspan=6 height=4></td></tr>")
|
|
cnum = cnum + 1
|
|
end
|
|
html.print("</table>")
|
|
--
|
|
html.print("</td></tr><tr><td><hr></td></tr>")
|
|
end
|
|
html.print("</table>")
|
|
html.print("<p style='font-size:8px'>VPN v" .. VPNVER .. "</p>")
|
|
hide("<input type=hidden name=conn_num value=" .. parms.conn_num .. ">")
|
|
|
|
-- add hidden forms fields
|
|
for _, h in ipairs(hidden)
|
|
do
|
|
html.print(h)
|
|
end
|
|
|
|
html.print("</form></center>")
|
|
html.footer()
|
|
html.print("</body></html>")
|
|
http_footer()
|