mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager
synced 2024-10-14 20:18:39 +00:00
977ea352a0
These SPDX license identifiers are deprecated ([1]). Update them. [1] https://spdx.org/licenses/ sed \ -e '1 s%^/\* SPDX-License-Identifier: \(GPL-2.0\|LGPL-2.1\)+ \*/$%/* SPDX-License-Identifier: \1-or-later */%' \ -e '1,2 s%^\(--\|#\|//\) SPDX-License-Identifier: \(GPL-2.0\|LGPL-2.1\)+$%\1 SPDX-License-Identifier: \2-or-later%' \ -i \ $(git grep -l SPDX-License-Identifier -- \ ':(exclude)shared/c-*/' \ ':(exclude)shared/n-*/' \ ':(exclude)shared/systemd/src' \ ':(exclude)src/systemd/src')
417 lines
14 KiB
Lua
Executable file
417 lines
14 KiB
Lua
Executable file
#!/usr/bin/env lua
|
|
-- SPDX-License-Identifier: GPL-2.0-or-later
|
|
--
|
|
-- Copyright (C) 2015 Red Hat, Inc.
|
|
--
|
|
|
|
-- Script for importing/converting Cisco VPN configuration files (.pcf) to NetworkManager
|
|
-- In general, the implementation follows the logic of import() from
|
|
-- https://git.gnome.org/browse/network-manager-vpnc/tree/properties/nm-vpnc.c
|
|
|
|
----------------------
|
|
-- Helper functions --
|
|
----------------------
|
|
function read_all(in_file)
|
|
local f, msg = io.open(in_file, "r")
|
|
if not f then return nil, msg; end
|
|
local content = f:read("*all")
|
|
f:close()
|
|
return content
|
|
end
|
|
|
|
function uuid()
|
|
math.randomseed(os.time())
|
|
local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
|
|
local uuid = string.gsub(template, '[xy]', function (c)
|
|
local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb)
|
|
return string.format('%x', v)
|
|
end)
|
|
return uuid
|
|
end
|
|
|
|
function vpn_settings_to_text(vpn_settings)
|
|
local t = {}
|
|
for k,v in pairs(vpn_settings) do
|
|
t[#t+1] = k.."="..v
|
|
end
|
|
return table.concat(t, "\n")
|
|
end
|
|
|
|
function usage()
|
|
local basename = string.match(arg[0], '[^/\\]+$') or arg[0]
|
|
print(basename .. " - convert/import Cisco VPN (.pcf) configuration to NetworkManager")
|
|
print("Usage:")
|
|
print(" " .. basename .. " <input-file> <output-file>")
|
|
print(" - converts Cisco VPN config to NetworkManager keyfile")
|
|
print("")
|
|
print(" " .. basename .. " --import <input-file1> <input-file2> ...")
|
|
print(" - imports Cisco VPN config(s) to NetworkManager")
|
|
os.exit(1)
|
|
end
|
|
|
|
|
|
-------------------------------------------
|
|
-- Functions for VPN options translation --
|
|
-------------------------------------------
|
|
function set_option(t, option, value)
|
|
g_switches[value[1]] = value[2]
|
|
end
|
|
function handle_generic(t, option, value)
|
|
t[option] = value[2]
|
|
end
|
|
function handle_yes(t, option, value)
|
|
t[option] = "yes"
|
|
end
|
|
function handle_bool(t, option, value)
|
|
if tonumber(value[2]) == 1 then
|
|
t[option] = "true"
|
|
elseif tonumber(value[2]) == 0 then
|
|
t[option] = "false"
|
|
else
|
|
io.stderr:write(string.format("Warning: ignoring invalid option '%s'\n", value[1]))
|
|
end
|
|
end
|
|
function handle_DHGroup(t, option, value)
|
|
local dhgroups = { [1]="dh1", [2]="dh2", [5]="dh5" }
|
|
dhgroup = dhgroups[tonumber(value[2])]
|
|
if not dhgroup then io.stderr:write(string.format("Warning: invalid value for 'DHGroup': %s\n", value[2])) end
|
|
t[option] = dhgroup
|
|
end
|
|
function handle_PeerTimeout(t, option, value)
|
|
if not value[2] then io.stderr:write("Warning: ignoring invalid option 'PeerTimeout'\n") end
|
|
if tonumber(value[2]) == 0 or (tonumber(value[2]) >=10 and tonumber(value[2] <= 86400)) then
|
|
t[option] = value[2]
|
|
else io.stderr:write(string.format("Warning: invalid value for 'PeerTimeout': %s\n", value[2])) end
|
|
end
|
|
function handle_(t, option, value)
|
|
io.stderr:write("Warning: enc_GroupPwd: encrypted group passwords are not supported by this script.\n")
|
|
end
|
|
function handle_TunnelingMode(t, option, value)
|
|
if value[2] == 1 then
|
|
io.stderr:write("Warning: TCP tunneling is not supported by vpnc. " ..
|
|
"The connection will be used with TCP tunneling disabled, " ..
|
|
"however it may not work as expected.\n")
|
|
end
|
|
end
|
|
function handle_UseLegacyIKEPort(t, option, value)
|
|
if value[2] ~= 0 then
|
|
t[option] = 500
|
|
end
|
|
end
|
|
function handle_routes(t, option, value)
|
|
local function splitroutes(str)
|
|
local sep, fields = " ", {}
|
|
local pattern = string.format("([^%s]+)", sep)
|
|
str:gsub(pattern,
|
|
function(c)
|
|
local c1,c2 = c:match("^(%d+%.%d+%.%d+%.%d+)/(%d+)$")
|
|
if c1 then
|
|
fields[#fields+1] = { c1, c2 }
|
|
else
|
|
io.stderr:write("Warning: ignoring invalid route: '" .. c .. "'\n")
|
|
end
|
|
end)
|
|
return fields
|
|
end
|
|
t[option] = splitroutes(value[2])
|
|
end
|
|
|
|
-- global variables -
|
|
g_vpn_data = {}
|
|
g_vpn_pwds = {}
|
|
g_con_data = {}
|
|
g_ip4_data = {}
|
|
g_switches = {}
|
|
|
|
vpn2nm = {
|
|
["Description"] = { nm_opt="id", func=handle_generic, tbl=g_con_data },
|
|
["InterfaceName"] = { nm_opt="interface-name", func=handle_generic, tbl=g_con_data },
|
|
["EnableLocalLAN"] = { nm_opt="never-default", func=handle_bool, tbl=g_ip4_data },
|
|
["X-NM-Routes"] = { nm_opt="routes", func=handle_routes, tbl=g_ip4_data },
|
|
["Host"] = { nm_opt="IPSec gateway", func=handle_generic, tbl=g_vpn_data },
|
|
["GroupName"] = { nm_opt="IPSec ID", func=handle_generic, tbl=g_vpn_data },
|
|
["Username"] = { nm_opt="Xauth username", func=handle_generic, tbl=g_vpn_data },
|
|
["UserPassword"] = { nm_opt="Xauth password", func=handle_generic, tbl=g_vpn_pwds },
|
|
["SaveUserPassword"] = { nm_opt="", func=set_option, tbl={} },
|
|
["GroupPwd"] = { nm_opt="IPSec secret", func=handle_generic, tbl=g_vpn_pwds },
|
|
["DHGroup"] = { nm_opt="IKE DH Group", func=handle_DHGroup, tbl=g_vpn_data },
|
|
["NTDomain"] = { nm_opt="Domain", func=handle_generic, tbl=g_vpn_data },
|
|
["SingleDES"] = { nm_opt="Enable Single DES", func=handle_yes, tbl=g_vpn_data },
|
|
["EnableNat"] = { nm_opt="", func=set_option, tbl={} },
|
|
["X-NM-Use-NAT-T"] = { nm_opt="", func=set_option, tbl={} },
|
|
["X-NM-Force-NAT-T"] = { nm_opt="", func=set_option, tbl={} },
|
|
["X-NM-SaveGroupPassword"] = { nm_opt="", func=set_option, tbl={} },
|
|
["UseLegacyIKEPort"] = { nm_opt="Local Port", func=handle_UseLegacyIKEPort, tbl=g_vpn_data },
|
|
["PeerTimeout"] = { nm_opt="DPD idle timeout (our side)", func=handle_PeerTimeout, tbl=g_vpn_data },
|
|
["TunnelingMode"] = { nm_opt="", func=handle_TunnelingMode, tbl= {} },
|
|
["enc_UserPassword"] = { nm_opt="", func=handle_enc_pwd, tbl= {} },
|
|
["enc_GroupPwd"] = { nm_opt="", func=handle_enc_pwd, tbl= {} },
|
|
}
|
|
|
|
------------------------------------------------------
|
|
-- Read and convert the config into the global vars --
|
|
------------------------------------------------------
|
|
function read_and_convert(in_file)
|
|
local function line_split(str)
|
|
-- split at '=' character
|
|
local sep, fields = "=", {}
|
|
local pattern = string.format("([^%s]+)%s(.+)", sep, sep)
|
|
fields[1], fields[2] = str:match(pattern)
|
|
return fields
|
|
end
|
|
|
|
in_text, msg = read_all(in_file)
|
|
if not in_text then return false, msg end
|
|
|
|
-- loop through the config and convert it
|
|
for line in in_text:gmatch("[^\r\n]+") do
|
|
repeat
|
|
-- skip comments and empty lines
|
|
if line:find("^%s*[#;]") or line:find("^%s*$") then break end
|
|
-- trim leading and trailing spaces
|
|
line = line:find("^%s*$") and "" or line:match("^%s*(.*%S)")
|
|
|
|
local words = line_split(line)
|
|
local val = vpn2nm[words[1]]
|
|
if val then
|
|
if type(val) == "table" then val.func(val.tbl, val.nm_opt, words)
|
|
else print(string.format("debug: '%s': val=%s", line, val)) end
|
|
end
|
|
until true
|
|
end
|
|
|
|
-- check if mandatory options exist
|
|
if not g_vpn_data["IPSec gateway"] then
|
|
local msg = in_file .. ": Not a valid Cisco VPN configuration (no Host)"
|
|
return false, msg
|
|
end
|
|
if not g_vpn_data["IPSec ID"] then
|
|
local msg = in_file .. ": Not a valid OpenVPN configuration (no GroupName)"
|
|
return false, msg
|
|
end
|
|
|
|
-- process inter-option dependencies
|
|
-- NAT traversal mode
|
|
local natt_mode = {
|
|
NONE = "none",
|
|
NATT = "natt",
|
|
NATT_ALWAYS = "force-natt",
|
|
CISCO = "cisco-udp"
|
|
}
|
|
g_vpn_data["NAT Traversal Mode"] = natt_mode.CISCO
|
|
if tonumber(g_switches["EnableNat"]) == 0 then
|
|
g_vpn_data["NAT Traversal Mode"] = natt_mode.NONE
|
|
elseif tonumber(g_switches["EnableNat"]) == 1 then
|
|
if tonumber(g_switches["X-NM-Force-NAT-T"]) == 1 then
|
|
g_vpn_data["NAT Traversal Mode"] = natt_mode.NATT_ALWAYS
|
|
elseif tonumber(g_switches["X-NM-Use-NAT-T"]) == 1 then
|
|
g_vpn_data["NAT Traversal Mode"] = natt_mode.NATT
|
|
end
|
|
else
|
|
io.stderr:write("Warning: invalid value for EnableNat\n")
|
|
g_vpn_data["NAT Traversal Mode"] = natt_mode.CISCO
|
|
end
|
|
|
|
-- set secret flags
|
|
g_vpn_data["Xauth password-flags"] = 1
|
|
if tonumber(g_switches["SaveUserPassword"]) == 1 then
|
|
g_vpn_data["xauth-password-type"] = "save"
|
|
else
|
|
g_vpn_data["Xauth password-flags"] = 3
|
|
end
|
|
if g_vpn_data["IPSec ID"] then
|
|
g_vpn_data["IPSec ID-flags"] = 1
|
|
end
|
|
if g_switches["X-NM-SaveGroupPassword"] then
|
|
if tonumber(g_switches["X-NM-SaveGroupPassword"]) == 1 then
|
|
g_vpn_data["ipsec-secret-type"] = "save"
|
|
g_vpn_data["IPSec ID-flags"] = 1
|
|
else
|
|
g_vpn_data["IPSec ID-flags"] = 3
|
|
end
|
|
else
|
|
g_vpn_data["ipsec-secret-type"] = "save"
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
|
|
--------------------------------------------------------
|
|
-- Create and write connection file in keyfile format --
|
|
--------------------------------------------------------
|
|
function write_vpn_to_keyfile(in_file, out_file)
|
|
connection = [[
|
|
[connection]
|
|
id=__NAME_PLACEHOLDER__
|
|
uuid=__UUID_PLACEHOLDER__
|
|
__IFNAME_PLACEHOLDER__
|
|
type=vpn
|
|
autoconnect=no
|
|
|
|
[ipv4]
|
|
method=auto
|
|
never-default=__NEVER_DEFAULT_PLACEHOLDER__
|
|
__ROUTES_PLACEHOLDER__
|
|
|
|
[ipv6]
|
|
method=auto
|
|
|
|
[vpn]
|
|
service-type=org.freedesktop.NetworkManager.vpnc
|
|
]]
|
|
connection = connection .. vpn_settings_to_text(g_vpn_data)
|
|
connection = connection .. "\n\n[vpn-secrets]\n"
|
|
connection = connection .. vpn_settings_to_text(g_vpn_pwds)
|
|
|
|
local con_name = g_con_data["id"] or (out_file:gsub(".*/", ""))
|
|
local ifname = g_con_data["interface-name"]
|
|
local never_default = g_ip4_data["never-default"] or "false"
|
|
local routes = ""
|
|
if ifname then ifname = "interface-name="..ifname.."\n" else ifname = "" end
|
|
for idx, r in ipairs(g_ip4_data["routes"] or {}) do
|
|
routes = routes .. string.format("routes%d=%s/%s\n", idx, r[1], r[2])
|
|
end
|
|
|
|
connection = string.gsub(connection, "__NAME_PLACEHOLDER__", con_name)
|
|
connection = string.gsub(connection, "__UUID_PLACEHOLDER__", uuid())
|
|
connection = string.gsub(connection, "__IFNAME_PLACEHOLDER__\n", ifname)
|
|
connection = string.gsub(connection, "__NEVER_DEFAULT_PLACEHOLDER__", never_default)
|
|
connection = string.gsub(connection, "__ROUTES_PLACEHOLDER__\n", routes)
|
|
|
|
-- write output file
|
|
local f, err = io.open(out_file, "w")
|
|
if not f then io.stderr:write(err) return false end
|
|
f:write(connection)
|
|
f:close()
|
|
|
|
local ofname = out_file:gsub(".*/", "")
|
|
io.stderr:write("Successfully converted VPN configuration: " .. in_file .. " => " .. out_file .. "\n")
|
|
io.stderr:write("To use the connection, do:\n")
|
|
io.stderr:write("# cp " .. out_file .. " /etc/NetworkManager/system-connections\n")
|
|
io.stderr:write("# chmod 600 /etc/NetworkManager/system-connections/" .. ofname .. "\n")
|
|
io.stderr:write("# nmcli con load /etc/NetworkManager/system-connections/" .. ofname .. "\n")
|
|
return true
|
|
end
|
|
|
|
---------------------------------------------
|
|
-- Import VPN connection to NetworkManager --
|
|
---------------------------------------------
|
|
function import_vpn_to_NM(filename)
|
|
local lgi = require 'lgi'
|
|
local GLib = lgi.GLib
|
|
local NM = lgi.NM
|
|
|
|
-- function creating NMConnection
|
|
local function create_profile(name)
|
|
local profile = NM.SimpleConnection.new()
|
|
local never_default = g_ip4_data["never-default"] == "true"
|
|
|
|
s_con = NM.SettingConnection.new()
|
|
s_vpn = NM.SettingVpn.new()
|
|
s_ip4 = NM.SettingIP4Config.new()
|
|
|
|
s_con[NM.SETTING_CONNECTION_ID] = name
|
|
s_con[NM.SETTING_CONNECTION_UUID] = uuid()
|
|
s_con[NM.SETTING_CONNECTION_INTERFACE_NAME] = g_con_data["interface-name"]
|
|
s_con[NM.SETTING_CONNECTION_TYPE] = "vpn"
|
|
s_vpn[NM.SETTING_VPN_SERVICE_TYPE] = "org.freedesktop.NetworkManager.vpnc"
|
|
s_ip4[NM.SETTING_IP_CONFIG_METHOD] = NM.SETTING_IP4_CONFIG_METHOD_AUTO
|
|
s_ip4[NM.SETTING_IP_CONFIG_NEVER_DEFAULT] = never_default
|
|
|
|
-- add routes
|
|
local AF_INET = 2
|
|
for _, r in ipairs(g_ip4_data["routes"] or {}) do
|
|
route = NM.IPRoute.new(AF_INET, r[1], r[2], nil, -1)
|
|
s_ip4:add_route(route)
|
|
end
|
|
|
|
-- add vpn data
|
|
for k,v in pairs(g_vpn_data) do
|
|
s_vpn:add_data_item(k, v)
|
|
end
|
|
-- add vpn secrets
|
|
for k,v in pairs(g_vpn_pwds) do
|
|
s_vpn:add_secret(k, v)
|
|
end
|
|
|
|
profile:add_setting(s_con)
|
|
profile:add_setting(s_vpn)
|
|
profile:add_setting(s_ip4)
|
|
return profile
|
|
end
|
|
|
|
-- callback function for add_connection()
|
|
local function added_cb(client, result, data)
|
|
local con,err,code = client:add_connection_finish(result)
|
|
if con then
|
|
print(string.format("%s: Imported to NetworkManager: %s - %s",
|
|
filename, con:get_uuid(), con:get_id()))
|
|
else
|
|
io.stderr:write(code .. ": " .. err .. "\n");
|
|
return false
|
|
end
|
|
main_loop:quit()
|
|
end
|
|
|
|
local profile_name = g_con_data["id"] or string.match(filename, '[^/\\]+$') or filename
|
|
main_loop = GLib.MainLoop(nil, false)
|
|
local con = create_profile(profile_name)
|
|
local client = NM.Client.new()
|
|
|
|
-- send the connection to NetworkManager
|
|
client:add_connection_async(con, true, nil, added_cb, nil)
|
|
|
|
-- run main loop so that the callback could be called
|
|
main_loop:run()
|
|
return true
|
|
end
|
|
|
|
|
|
---------------------------
|
|
-- Main code starts here --
|
|
---------------------------
|
|
local import_mode = false
|
|
local infile, outfile
|
|
|
|
-- parse command-line arguments
|
|
if not arg[1] or arg[1] == "--help" or arg[1] == "-h" then usage() end
|
|
if arg[1] == "--import" or arg[1] == "-i" then
|
|
infile = arg[2]
|
|
if not infile then usage() end
|
|
import_mode = true
|
|
else
|
|
infile = arg[1]
|
|
outfile = arg[2]
|
|
if not infile or not outfile then usage() end
|
|
if arg[3] then usage() end
|
|
end
|
|
|
|
if import_mode then
|
|
-- check if lgi is available
|
|
local success,msg = pcall(require, 'lgi')
|
|
if not success then
|
|
io.stderr:write("Lua lgi module is not available, please install it (usually lua-lgi package)\n")
|
|
-- print(msg)
|
|
os.exit(1)
|
|
end
|
|
-- read configs, convert them and import to NM
|
|
for i = 2, #arg do
|
|
ok, err_msg = read_and_convert(arg[i])
|
|
if ok then import_vpn_to_NM(arg[i])
|
|
else io.stderr:write(err_msg .. "\n") end
|
|
-- reset global vars
|
|
g_vpn_data = {}
|
|
g_vpn_pwds = {}
|
|
g_con_data = {}
|
|
g_ip4_data = {}
|
|
g_switches = {}
|
|
end
|
|
else
|
|
-- read configs, convert them and write as NM keyfile connection
|
|
ok, err_msg = read_and_convert(infile)
|
|
if ok then write_vpn_to_keyfile(infile, outfile)
|
|
else io.stderr:write(err_msg .. "\n") end
|
|
end
|
|
|