diff options
author | Jiřà Klimeš <jklimes@redhat.com> | 2015-08-19 13:16:44 +0200 |
---|---|---|
committer | Jiřà Klimeš <jklimes@redhat.com> | 2015-08-19 13:16:44 +0200 |
commit | 9377c08d10954c39c5e65d3e41700b0fa21b3fcf (patch) | |
tree | fc84b17bce17cfd67e8d8a7685a86ecf1d6b3d5a | |
parent | 0cbe203d7c32c1c05007e51e01b7de60cb41df40 (diff) | |
parent | 239bb736bcd29c2f78f47b601083e21fce8e41b9 (diff) | |
download | NetworkManager-9377c08d10954c39c5e65d3e41700b0fa21b3fcf.tar.gz |
merge: merge contrib/scripts directory with VPN import scripts
-rwxr-xr-x | contrib/scripts/nm-import-openconnect | 277 | ||||
-rwxr-xr-x | contrib/scripts/nm-import-openvpn | 423 | ||||
-rwxr-xr-x | contrib/scripts/nm-import-vpnc | 432 |
3 files changed, 1132 insertions, 0 deletions
diff --git a/contrib/scripts/nm-import-openconnect b/contrib/scripts/nm-import-openconnect new file mode 100755 index 0000000000..f81665e587 --- /dev/null +++ b/contrib/scripts/nm-import-openconnect @@ -0,0 +1,277 @@ +#!/usr/bin/env lua +-- -*- Mode: Lua; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +-- vim: ft=lua ts=2 sts=2 sw=2 et ai +-- +-- 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; either version 2 of the License, or +-- (at your option) any later version. +-- +-- 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, write to the Free Software Foundation, Inc., +-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +-- +-- Copyright 2015 Red Hat, Inc. +-- +-- +-- Script for importing/converting OpenConnect VPN configuration files for NetworkManager +-- In general, the implementation follows the logic of import() from +-- https://git.gnome.org/browse/network-manager-openconnect/tree/properties/nm-openconnect.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 OpenConnect VPN configuration to NetworkManager") + print("Usage:") + print(" " .. basename .. " <input-file> <output-file>") + print(" - converts OpenConnect VPN config to NetworkManager keyfile") + print("") + print(" " .. basename .. " --import <input-file1> <input-file2> ...") + print(" - imports OpenConnect VPN config(s) to NetworkManager") + os.exit(1) +end + + +------------------------------------------- +-- Functions for VPN options translation -- +------------------------------------------- +function handle_yes(t, option, value) + t[option] = "yes" +end +function handle_generic(t, option, value) + if not value[2] then io.stderr:write(string.format("Warning: ignoring invalid option '%s'\n", value[1])) end + t[option] = value[2] +end + +-- global variables +g_con_data = {} +g_vpn_data = {} + +vpn2nm = { + ["Description"] = { nm_opt="id", func=handle_generic, tbl=g_con_data }, + ["Host"] = { nm_opt="gateway", func=handle_generic, tbl=g_vpn_data }, + ["CACert"] = { nm_opt="cacert", func=handle_generic, tbl=g_vpn_data }, + ["Proxy"] = { nm_opt="proxy", func=handle_generic, tbl=g_vpn_data }, + ["CSDEnable"] = { nm_opt="enable_csd_trojan", func=handle_yes, tbl=g_vpn_data }, + ["CSDWrapper"] = { nm_opt="csd_wrapper", func=handle_generic, tbl=g_vpn_data }, + ["UserCertificate"] = { nm_opt="usercert", func=handle_generic, tbl=g_vpn_data }, + ["PrivateKey"] = { nm_opt="userkey", func=handle_generic, tbl=g_vpn_data }, + ["FSID"] = { nm_opt="pem_passphrase_fsid", func=handle_yes, tbl=g_vpn_data }, + ["StokenSource"] = { nm_opt="stoken_source", func=handle_generic, tbl=g_vpn_data }, + ["StokenString"] = { nm_opt="stoken_string", func=handle_generic, tbl=g_vpn_data }, +} + +------------------------------------------------------ +-- 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"..val)) end + end + until true + end + + -- check mandatory parameters + if not g_vpn_data["gateway"] then + local msg = in_file .. ": Not a valid OpenConnect VPN configuration" + return false, msg + 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__ +type=vpn +autoconnect=no + +[ipv4] +method=auto +never-default=true + +[ipv6] +method=auto + +[vpn] +service-type=org.freedesktop.NetworkManager.openconnect +]] + + connection = connection .. vpn_settings_to_text(g_vpn_data) + + local con_name = g_con_data["id"] or (out_file:gsub(".*/", "")) + connection = string.gsub(connection, "__NAME_PLACEHOLDER__", con_name) + connection = string.gsub(connection, "__UUID_PLACEHOLDER__", uuid()) + + -- 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() + + s_con = NM.SettingConnection.new() + s_vpn = NM.SettingVpn.new() + s_con[NM.SETTING_CONNECTION_ID] = name + s_con[NM.SETTING_CONNECTION_UUID] = uuid() + s_con[NM.SETTING_CONNECTION_TYPE] = "vpn" + s_vpn[NM.SETTING_VPN_SERVICE_TYPE] = "org.freedesktop.NetworkManager.openconnect" + for k,v in pairs(g_vpn_data) do + s_vpn:add_data_item(k, v) + end + + profile:add_setting(s_con) + profile:add_setting(s_vpn) + 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_con_data = {} + g_vpn_data = {} + 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 + diff --git a/contrib/scripts/nm-import-openvpn b/contrib/scripts/nm-import-openvpn new file mode 100755 index 0000000000..d01140dd3b --- /dev/null +++ b/contrib/scripts/nm-import-openvpn @@ -0,0 +1,423 @@ +#!/usr/bin/env lua +-- -*- Mode: Lua; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +-- vim: ft=lua ts=2 sts=2 sw=2 et ai +-- +-- 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; either version 2 of the License, or +-- (at your option) any later version. +-- +-- 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, write to the Free Software Foundation, Inc., +-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +-- +-- Copyright 2015 Red Hat, Inc. +-- +-- +-- Script for importing/converting OpenVPN configuration files for NetworkManager +-- In general, the implementation follows the logic of import() from +-- https://git.gnome.org/browse/network-manager-openvpn/tree/properties/import-export.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 unquote(str) + return (string.gsub(str, "^([\"\'])(.*)%1$", "%2")) +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 OpenVPN configuration to NetworkManager") + print("Usage:") + print(" " .. basename .. " <input-file> <output-file>") + print(" - converts OpenVPN config to NetworkManager keyfile") + print("") + print(" " .. basename .. " --import <input-file1> <input-file2> ...") + print(" - imports OpenVPN config(s) to NetworkManager") + os.exit(1) +end + + +------------------------------------------- +-- Functions for VPN options translation -- +------------------------------------------- +function set_bool(t, option, value) + g_switches[option] = true +end +function handle_yes(t, option, value) + t[option] = "yes" +end +function handle_generic(t, option, value) + if not value[2] then io.stderr:write(string.format("Warning: ignoring invalid option '%s'\n", value[1])) return end + t[option] = value[2] +end +function handle_number(t, option, value) + if not value[2] then io.stderr:write(string.format("Warning: ignoring invalid option '%s'\n", value[1])) return end + if not tonumber(value[2]) then + io.stderr:write(string.format("Warning: ignoring not numeric value '%s' for option '%s'\n", value[2], value[1])) + return + end + t[option] = value[2] +end +function handle_proto(t, option, value) + if not value[2] then io.stderr:write("Warning: ignoring invalid option 'proto'\n") end + if value[2] == "tcp" or value[3] == "tcp-client" or value[2] == "tcp-server" then + t[option] = "yes" + end +end +--[[ +function handle_dev_old(t, option, value) + if not value[2] then io.stderr:write(string.format("Warning: ignoring invalid option '%s'\n", value[1])) end + if value[2] == "tap" then + t[option] = "yes" + end +end +--]] +function handle_dev_type(t, option, value) + if value[2] ~= "tun" and value[2] ~= "tap" then + io.stderr:write(string.format("Warning: ignoring invalid option '%s'\n", value[1])) + end + t[option] = value[2] +end +function handle_remote(t, option, value) + local rem + if not value[2] then io.stderr:write("Warning: ignoring invalid option 'remote'\n") return end + rem = value[2] + if tonumber(value[3]) then + rem = rem .. ":" .. value[3] + end + if value[4] == "udp" or value[4] == "tcp" then + rem = rem .. ":" .. value[4] + end + if t[option] then + t[option] = t[option] .. " " .. rem + else + t[option] = rem + end + g_switches[value[1]] = true +end +function handle_port(t, option, value) + if tonumber(value[2]) then + t[option] = value[2] + end +end +function handle_proxy(t, option, value) + if not value[2] then io.stderr:write(string.format("Warning: ignoring invalid option '%s'\n", value[1])) return end + if value[4] then io.stderr:write(string.format("Warning: the third argument of '%s' is not supported yet\n", value[1])) end + t[option[1]] = string.gsub(value[1], "-proxy", "") + t[option[2]] = value[2] + t[option[3]] = value[3] +end +function handle_ifconfig(t, option, value) + if not (value[2] and value[3]) then io.stderr:write("Warning: ignoring invalid option 'ifconfig'\n") return end + t[option[1]] = value[2] + t[option[2]] = value[3] +end +function handle_keepalive(t, option, value) + if (not (value[2] and value[3])) or (not tonumber(value[2]) or not tonumber(value[3])) then + io.stderr:write("Warning: ignoring invalid option 'keepalive'; two numbers required\n") + return + end + t[option[1]] = value[2] + t[option[2]] = value[3] +end +function handle_path(t, option, value) + if value[1] == "pkcs12" then + t["ca"] = value[2] + t["cert"] = value[2] + t["key"] = value[2] + else + t[option] = value[2] + end +end +function handle_secret(t, option, value) + t[option[1]] = value[2] + t[option[2]] = value[3] + g_switches[value[1]]= true +end +function handle_tls_remote(t, option, value) + t[option] = unquote(value[2]) +end +function handle_remote_cert_tls(t, option, value) + if value[2] ~= "client" and value[2] ~= "server" then + io.stderr:write(string.format("Warning: ignoring invalid option '%s'\n", value[1])) + return + end + t[option] = value[2] +end + +-- global variables +g_vpn_data = {} +g_switches = {} + +vpn2nm = { + ["auth"] = { nm_opt="auth", func=handle_generic }, + ["auth-user-pass"] = { nm_opt="auth-user-pass", func=set_bool }, + ["ca"] = { nm_opt="ca", func=handle_path }, + ["cert"] = { nm_opt="cert", func=handle_path }, + ["cipher"] = { nm_opt="cipher", func=handle_generic }, + ["keysize"] = { nm_opt="keysize", func=handle_generic }, + ["keepalive"] = { nm_opt={"ping", "ping-restart"}, func=handle_keepalive }, + ["client"] = { nm_opt="client", func=set_bool }, + ["comp-lzo"] = { nm_opt="comp-lzo", func=handle_yes }, + ["float"] = { nm_opt="float", func=handle_yes }, +-- ["dev"] = { nm_opt="tap-dev", func=handle_dev_old }, + ["dev"] = { nm_opt="dev", func=handle_generic }, + ["dev-type"] = { nm_opt="dev-type", func=handle_dev_type }, + ["fragment"] = { nm_opt="fragment-size", func=handle_generic }, + ["ifconfig"] = { nm_opt={"local-ip", "remote-ip"}, func=handle_ifconfig }, + ["key"] = { nm_opt="key", func=handle_path }, + ["mssfix"] = { nm_opt="mssfix", func=handle_yes }, + ["ping"] = { nm_opt="ping", func=handle_number }, + ["ping-exit"] = { nm_opt="ping-exit", func=handle_number }, + ["ping-restart"] = { nm_opt="ping-restart", func=handle_number }, + ["pkcs12"] = { nm_opt="client", func=handle_path }, + ["port"] = { nm_opt="port", func=handle_port }, + ["rport"] = { nm_opt="port", func=handle_port }, + ["proto"] = { nm_opt="proto-tcp", func=handle_proto }, + ["http-proxy"] = { nm_opt={"proxy-type", "proxy-server", "proxy-port"}, func=handle_proxy }, + ["http-proxy-retry"] = { nm_opt="proxy-retry", func=handle_yes }, + ["socks-proxy"] = { nm_opt={"proxy-type", "proxy-server", "proxy-port"}, func=handle_proxy }, + ["socks-proxy-retry"] = { nm_opt="proxy-retry", func=handle_yes }, + ["remote"] = { nm_opt="remote", func=handle_remote }, + ["remote-random"] = { nm_opt="remote-random", func=handle_yes }, + ["reneg-sec"] = { nm_opt="reneg-seconds", func=handle_generic }, + ["secret"] = { nm_opt={"static-key", "static-key-direction"}, func=handle_secret }, + ["tls-auth"] = { nm_opt={"ta", "ta-dir"}, func=handle_secret }, + ["tls-client"] = { nm_opt="client", func=set_bool }, + ["tls-remote"] = { nm_opt="tls-remote", func=handle_tls_remote }, + ["remote-cert-tls"] = { nm_opt="remote-cert-tls", func=handle_remote_cert_tls }, + ["tun-mtu"] = { nm_opt="tunnel-mtu", func=handle_generic } +} + +------------------------------------------------------------ +-- Read and convert the config into the global g_vpn_data -- +----------------------------------------------------------- +function read_and_convert(in_file) + local function line_split(str) + t={}; i = 1 + for str in str:gmatch("%S+") do + t[i] = str + i = i + 1 + end + return t + 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(g_vpn_data, val.nm_opt, words) + else print(string.format("debug: '%s' : val=%s"..val)) end + end + until true + end + + -- check some inter-option dependencies + if not g_switches["client"] and not g_switches["secret"] then + local msg = in_file .. ": Not a valid OpenVPN client configuration" + return false, msg + end + if not g_switches["remote"] then + local msg = in_file .. ": Not a valid OpenVPN configuration (no remote)" + return false, msg + end + + -- set 'connection-type' + g_vpn_data["connection-type"] = "tls" + have_sk = g_switches["secret"] ~= nil + have_ca = g_vpn_data["ca"] ~= nil + have_certs = ve_ca and g_vpn_data["cert"] and g_vpn_data["key"] + if g_switches["auth-user-pass"] then + if have_certs then + g_vpn_data["connection-type"] = "password-tls" + elseif have_ca then + g_vpn_data["connection-type"] = "tls" + end + elseif have_certs then g_vpn_data["connection-type"] = "tls" + elseif have_sk then g_vpn_data["connection-type"] = "static-key" + 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__ +type=vpn +autoconnect=no + +[ipv4] +method=auto +never-default=true + +[ipv6] +method=auto + +[vpn] +service-type=org.freedesktop.NetworkManager.openvpn +]] + connection = connection .. vpn_settings_to_text(g_vpn_data) + + connection = string.gsub(connection, "__NAME_PLACEHOLDER__", (out_file:gsub(".*/", ""))) + connection = string.gsub(connection, "__UUID_PLACEHOLDER__", uuid()) + + -- 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() + + s_con = NM.SettingConnection.new() + s_vpn = NM.SettingVpn.new() + s_con[NM.SETTING_CONNECTION_ID] = name + s_con[NM.SETTING_CONNECTION_UUID] = uuid() + s_con[NM.SETTING_CONNECTION_TYPE] = "vpn" + s_vpn[NM.SETTING_VPN_SERVICE_TYPE] = "org.freedesktop.NetworkManager.openvpn" + for k,v in pairs(g_vpn_data) do + s_vpn:add_data_item(k, v) + end + + profile:add_setting(s_con) + profile:add_setting(s_vpn) + 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 = 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_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 + diff --git a/contrib/scripts/nm-import-vpnc b/contrib/scripts/nm-import-vpnc new file mode 100755 index 0000000000..18554efb1f --- /dev/null +++ b/contrib/scripts/nm-import-vpnc @@ -0,0 +1,432 @@ +#!/usr/bin/env lua +-- -*- Mode: Lua; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +-- vim: ft=lua ts=2 sts=2 sw=2 et ai +-- +-- 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; either version 2 of the License, or +-- (at your option) any later version. +-- +-- 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, write to the Free Software Foundation, Inc., +-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +-- +-- Copyright 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 + |