summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJiří Klimeš <jklimes@redhat.com>2015-12-07 09:17:48 +0100
committerJiří Klimeš <jklimes@redhat.com>2015-12-07 09:17:48 +0100
commit4e9aa035c2d44ad5c1765e465d9cec00d17ddf3d (patch)
treec346b5ebcfd156577a030a389d6b2f367056698d
parentce238a7074e1a4b78e6381ad02dbb1d17c035958 (diff)
parent3892b839af4597c29ebff6d77666566dc4f94fb1 (diff)
downloadNetworkManager-4e9aa035c2d44ad5c1765e465d9cec00d17ddf3d.tar.gz
merge: implement nmcli con import/export for VPN connections (rh #1034105)
https://bugzilla.redhat.com/show_bug.cgi?id=1034105
-rw-r--r--clients/cli/Makefile.am2
-rw-r--r--clients/cli/connections.c327
-rw-r--r--clients/cli/nmcli-completion73
-rw-r--r--clients/common/nm-vpn-helpers.c16
-rw-r--r--clients/common/nm-vpn-helpers.h2
-rw-r--r--man/nmcli.1.in31
6 files changed, 432 insertions, 19 deletions
diff --git a/clients/cli/Makefile.am b/clients/cli/Makefile.am
index a2250adc6a..192920e6e5 100644
--- a/clients/cli/Makefile.am
+++ b/clients/cli/Makefile.am
@@ -40,6 +40,8 @@ nmcli_SOURCES = \
\
$(srcdir)/../common/nm-secret-agent-simple.c \
$(srcdir)/../common/nm-secret-agent-simple.h \
+ $(srcdir)/../common/nm-vpn-helpers.c \
+ $(srcdir)/../common/nm-vpn-helpers.h \
$(NULL)
nmcli_LDADD = \
diff --git a/clients/cli/connections.c b/clients/cli/connections.c
index 100d0a1a02..870b61ff0e 100644
--- a/clients/cli/connections.c
+++ b/clients/cli/connections.c
@@ -36,6 +36,7 @@
#include "connections.h"
#include "nm-secret-agent-simple.h"
#include "polkit-agent.h"
+#include "nm-vpn-helpers.h"
/* define some prompts for connection editor */
#define EDITOR_PROMPT_SETTING _("Setting name? ")
@@ -47,6 +48,7 @@
#define PROMPT_VPN_TYPE _("VPN type: ")
#define PROMPT_MASTER _("Master: ")
#define PROMPT_CONNECTION _("Connection (name, UUID, or path): ")
+#define PROMPT_VPN_CONNECTION _("VPN connection (name, UUID, or path): ")
#define PROMPT_CONNECTIONS _("Connection(s) (name, UUID, or path): ")
#define PROMPT_ACTIVE_CONNECTIONS _("Connection(s) (name, UUID, path or apath): ")
@@ -274,7 +276,9 @@ usage (void)
" delete [id | uuid | path] <ID>\n\n"
" monitor [id | uuid | path] <ID> ...\n\n"
" reload\n\n"
- " load <filename> [ <filename>... ]\n\n"));
+ " load <filename> [ <filename>... ]\n\n"
+ " import [--temporary] type <type> file <file to import>\n\n"
+ " export [id | uuid | path] <ID> [<output file>]\n\n"));
}
static void
@@ -522,6 +526,30 @@ usage_connection_load (void)
"state.\n\n"));
}
+static void
+usage_connection_import (void)
+{
+ g_printerr (_("Usage: nmcli connection import { ARGUMENTS | help }\n"
+ "\n"
+ "ARGUMENTS := [--temporary] type <type> file <file to import>\n"
+ "\n"
+ "Import an external/foreign configuration as a NetworkManager connection profile.\n"
+ "The type of the input file is specified by type option.\n"
+ "Only VPN configurations are supported at the moment. The configuration\n"
+ "is imported by NetworkManager VPN plugins.\n\n"));
+}
+
+static void
+usage_connection_export (void)
+{
+ g_printerr (_("Usage: nmcli connection export { ARGUMENTS | help }\n"
+ "\n"
+ "ARGUMENTS := [id | uuid | path] <ID> [<output file>]\n"
+ "\n"
+ "Export a connection. Only VPN connections are supported at the moment.\n"
+ "The data are directed to standard output or to a file if a name is given.\n\n"));
+}
+
static gboolean
usage_connection_second_level (const char *cmd)
{
@@ -549,6 +577,10 @@ usage_connection_second_level (const char *cmd)
usage_connection_reload ();
else if (matches (cmd, "load") == 0)
usage_connection_load ();
+ else if (matches (cmd, "import") == 0)
+ usage_connection_import ();
+ else if (matches (cmd, "export") == 0)
+ usage_connection_export ();
else
ret = FALSE;
return ret;
@@ -6868,33 +6900,59 @@ gen_compat_devices (const char *text, int state)
return ret;
}
-static char *
-gen_vpn_uuids (const char *text, int state)
+static const char **
+_create_vpn_array (const GPtrArray *connections, gboolean uuid)
{
- const GPtrArray *connections = nmc_tab_completion.nmc->connections;
- int c, u = 0;
- const char **uuids;
- char *ret;
+ int c, idx = 0;
+ const char **array;
if (connections->len < 1)
return NULL;
- uuids = g_new (const char *, connections->len + 1);
+ array = g_new (const char *, connections->len + 1);
for (c = 0; c < connections->len; c++) {
NMConnection *connection = NM_CONNECTION (connections->pdata[c]);
const char *type = nm_connection_get_connection_type (connection);
if (g_strcmp0 (type, NM_SETTING_VPN_SETTING_NAME) == 0)
- uuids[u++] = nm_connection_get_uuid (connection);
+ array[idx++] = uuid ? nm_connection_get_uuid (connection) : nm_connection_get_id (connection);
}
- uuids[u] = NULL;
+ array[idx] = NULL;
+ return array;
+}
- ret = nmc_rl_gen_func_basic (text, state, uuids);
+static char *
+gen_vpn_uuids (const char *text, int state)
+{
+ const GPtrArray *connections = nm_cli.connections;
+ const char **uuids;
+ char *ret;
+ if (connections->len < 1)
+ return NULL;
+
+ uuids = _create_vpn_array (connections, TRUE);
+ ret = nmc_rl_gen_func_basic (text, state, uuids);
g_free (uuids);
return ret;
}
+static char *
+gen_vpn_ids (const char *text, int state)
+{
+ const GPtrArray *connections = nm_cli.connections;
+ const char **ids;
+ char *ret;
+
+ if (connections->len < 1)
+ return NULL;
+
+ ids = _create_vpn_array (connections, FALSE);
+ ret = nmc_rl_gen_func_basic (text, state, ids);
+ g_free (ids);
+ return ret;
+}
+
static rl_compentry_func_t *
get_gen_func_cmd_nmcli (const char *str)
{
@@ -9967,6 +10025,235 @@ do_connection_load (NmCli *nmc, int argc, char **argv)
return nmc->return_value;
}
+// FIXME: change the text when non-VPN connection types are supported
+#define PROMPT_IMPORT_TYPE PROMPT_VPN_TYPE
+#define PROMPT_IMPORT_FILE _("File to import: ")
+
+static NMCResultCode
+do_connection_import (NmCli *nmc, gboolean temporary, int argc, char **argv)
+{
+ GError *error = NULL;
+ const char *type = NULL, *filename = NULL;
+ char *type_ask = NULL, *filename_ask = NULL;
+ AddConnectionInfo *info;
+ NMConnection *connection = NULL;
+ NMVpnEditorPlugin *plugin;
+
+ if (argc == 0) {
+ if (nmc->ask) {
+ type_ask = nmc_readline (PROMPT_IMPORT_TYPE);
+ filename_ask = nmc_readline (PROMPT_IMPORT_FILE);
+ type = type_ask = type_ask ? g_strstrip (type_ask) : NULL;
+ filename = filename_ask = filename_ask ? g_strstrip (filename_ask) : NULL;
+ } else {
+ g_string_printf (nmc->return_text, _("Error: No arguments provided."));
+ nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+ goto finish;
+ }
+ }
+
+ while (argc > 0) {
+ if (strcmp (*argv, "type") == 0) {
+ if (next_arg (&argc, &argv) != 0) {
+ g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1));
+ nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+ goto finish;
+ }
+ if (!type)
+ type = *argv;
+ else
+ g_printerr (_("Warning: 'type' already specified, ignoring extra one.\n"));
+
+ } else if (strcmp (*argv, "file") == 0) {
+ if (next_arg (&argc, &argv) != 0) {
+ g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1));
+ nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+ goto finish;
+ }
+ if (!filename)
+ filename = *argv;
+ else
+ g_printerr (_("Warning: 'file' already specified, ignoring extra one.\n"));
+ } else {
+ g_string_printf (nmc->return_text, _("Unknown parameter: %s\n"), *argv);
+ nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+ goto finish;
+ }
+
+ argc--;
+ argv++;
+ }
+
+ if (!type) {
+ g_string_printf (nmc->return_text, _("Error: 'type' argument is required."));
+ nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+ goto finish;
+ }
+ if (!filename) {
+ g_string_printf (nmc->return_text, _("Error: 'file' argument is required."));
+ nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+ goto finish;
+ }
+
+ /* Import VPN configuration */
+ plugin = nm_vpn_get_plugin_by_service (type, &error);
+ if (!plugin) {
+ g_string_printf (nmc->return_text, _("Error: failed to load VPN plugin: %s."),
+ error->message);
+ nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
+ goto finish;
+ }
+
+ connection = nm_vpn_editor_plugin_import (plugin, filename, &error);
+ if (!connection) {
+ g_string_printf (nmc->return_text, _("Error: failed to import '%s': %s."),
+ filename, error->message);
+ nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
+ goto finish;
+ }
+
+ info = g_malloc0 (sizeof (AddConnectionInfo));
+ info->nmc = nmc;
+ info->con_name = g_strdup (nm_connection_get_id (connection));
+
+ /* Add the new imported connection to NetworkManager */
+ add_new_connection (!temporary,
+ nmc->client,
+ connection,
+ add_connection_cb,
+ info);
+
+ nmc->should_wait = TRUE;
+finish:
+ if (connection)
+ g_object_unref (connection);
+ g_clear_error (&error);
+ g_free (type_ask);
+ g_free (filename_ask);
+ return nmc->return_value;
+}
+
+static NMCResultCode
+do_connection_export (NmCli *nmc, int argc, char **argv)
+{
+ NMConnection *connection = NULL;
+ const char *name;
+ const char *out_name = NULL;
+ char *name_ask = NULL;
+ char *out_name_ask = NULL;
+ const char *path = NULL;
+ const char *selector = NULL;
+ const char *type = NULL;
+ NMVpnEditorPlugin *plugin;
+ GError *error = NULL;
+
+ if (argc == 0) {
+ if (nmc->ask) {
+ name_ask = nmc_readline (PROMPT_VPN_CONNECTION);
+ name = name_ask = name_ask ? g_strstrip (name_ask) : NULL;
+ out_name = out_name_ask = nmc_readline (_("Output file name: "));
+ } else {
+ g_string_printf (nmc->return_text, _("Error: No arguments provided."));
+ nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+ goto finish;
+ }
+ } else {
+ if ( strcmp (*argv, "id") == 0
+ || strcmp (*argv, "uuid") == 0
+ || strcmp (*argv, "path") == 0) {
+
+ selector = *argv;
+ if (next_arg (&argc, &argv) != 0) {
+ g_string_printf (nmc->return_text, _("Error: %s argument is missing."),
+ selector);
+ nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+ goto finish;
+ }
+ }
+ name = *argv;
+ if (next_arg (&argc, &argv) == 0)
+ out_name = *argv;
+
+ if (next_arg (&argc, &argv) == 0) {
+ g_string_printf (nmc->return_text, _("Error: unknown extra argument: '%s'."), *argv);
+ nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+ goto finish;
+ }
+ }
+
+ if (!name) {
+ g_string_printf (nmc->return_text, _("Error: connection ID is missing."));
+ nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+ goto finish;
+ }
+ connection = nmc_find_connection (nmc->connections, selector, name, NULL);
+ if (!connection) {
+ g_string_printf (nmc->return_text, _("Error: Unknown connection '%s'."), name);
+ nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND;
+ goto finish;
+ }
+
+ type = nm_connection_get_connection_type (connection);
+ if (g_strcmp0 (type, NM_SETTING_VPN_SETTING_NAME) != 0) {
+ g_string_printf (nmc->return_text, _("Error: the connection is not VPN."));
+ nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+ goto finish;
+ }
+ type = nm_setting_vpn_get_service_type (nm_connection_get_setting_vpn (connection));
+
+ /* Export VPN configuration */
+ plugin = nm_vpn_get_plugin_by_service (type, &error);
+ if (!plugin) {
+ g_string_printf (nmc->return_text, _("Error: failed to load VPN plugin."));
+ nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
+ goto finish;
+ }
+
+ if (out_name)
+ path = out_name;
+ else {
+ int fd;
+ char tmpfile[] = "/tmp/nmcli-export-temp-XXXXXX";
+ fd = g_mkstemp (tmpfile);
+ if (fd == -1) {
+ g_string_printf (nmc->return_text, _("Error: failed to create temporary file %s."), tmpfile);
+ nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
+ goto finish;
+ }
+ close (fd);
+ path = tmpfile;
+ }
+
+ if (!nm_vpn_editor_plugin_export (plugin, path, connection, &error)) {
+ g_string_printf (nmc->return_text, _("Error: failed to export '%s': %s."),
+ nm_connection_get_id (connection), error ? error->message : "(unknown)");
+ nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
+ goto finish;
+ }
+
+ /* No output file -> copy data to stdout */
+ if (!out_name) {
+ char *contents = NULL;
+ gsize len = 0;
+ if (!g_file_get_contents (path, &contents, &len, &error)) {
+ g_string_printf (nmc->return_text, _("Error: failed to read temporary file '%s': %s."),
+ path, error->message);
+ nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
+ goto finish;
+ }
+ g_print ("%s", contents);
+ g_free (contents);
+ }
+
+finish:
+ if (!out_name && path)
+ unlink (path);
+ g_clear_error (&error);
+ g_free (name_ask);
+ g_free (out_name_ask);
+ return nmc->return_value;
+}
+
typedef struct {
NmCli *nmc;
@@ -10067,6 +10354,13 @@ nmcli_con_tab_completion (const char *text, int start, int end)
generator_func = gen_func_connection_names;
} else if (g_strcmp0 (rl_prompt, PROMPT_ACTIVE_CONNECTIONS) == 0) {
generator_func = gen_func_active_connection_names;
+ } else if (g_strcmp0 (rl_prompt, PROMPT_IMPORT_TYPE) == 0) {
+ generator_func = gen_func_vpn_types;
+ } else if (g_strcmp0 (rl_prompt, PROMPT_IMPORT_FILE) == 0) {
+ rl_attempted_completion_over = 0;
+ rl_complete_with_tilde_expansion = 1;
+ } else if (g_strcmp0 (rl_prompt, PROMPT_VPN_CONNECTION) == 0) {
+ generator_func = gen_vpn_ids;
}
if (generator_func)
@@ -10250,6 +10544,17 @@ do_connections (NmCli *nmc, int argc, char **argv)
next_arg (&argc, &argv);
}
nmc->return_value = do_connection_clone (nmc, temporary, argc, argv);
+ } else if (matches(*argv, "import") == 0) {
+ gboolean temporary = FALSE;
+
+ next_arg (&argc, &argv);
+ if (nmc_arg_is_option (*argv, "temporary")) {
+ temporary = TRUE;
+ next_arg (&argc, &argv);
+ }
+ nmc->return_value = do_connection_import (nmc, temporary, argc, argv);
+ } else if (matches(*argv, "export") == 0) {
+ nmc->return_value = do_connection_export (nmc, argc-1, argv+1);
} else {
usage ();
g_string_printf (nmc->return_text, _("Error: '%s' is not valid 'connection' command."), *argv);
diff --git a/clients/cli/nmcli-completion b/clients/cli/nmcli-completion
index 63050a36db..e5aae8dba6 100644
--- a/clients/cli/nmcli-completion
+++ b/clients/cli/nmcli-completion
@@ -282,6 +282,7 @@ _nmcli_compl_OPTIONS()
# expects several options with parameters. This function can parse them and remove them from the words array.
_nmcli_compl_ARGS()
{
+ local aliases=${@}
local OPTIONS_ALL N_REMOVE_WORDS REMOVE_OPTIONS OPTIONS_HAS_MANDATORY i
OPTIONS_ALL=("${OPTIONS[@]}")
OPTIONS_UNKNOWN_OPTION=
@@ -317,7 +318,17 @@ _nmcli_compl_ARGS()
N_REMOVE_WORDS=2
REMOVE_OPTIONS=("${words[0]}")
- case "${words[0]}" in
+
+ # change option name to alias
+ WORD0="${words[0]}"
+ for alias in "${aliases[@]}" ; do
+ if [[ "${WORD0}" == ${alias%%:*} ]]; then
+ WORD0=${alias#*:}
+ break
+ fi
+ done
+
+ case "${WORD0}" in
level)
if [[ "${#words[@]}" -eq 2 ]]; then
_nmcli_list "OFF ERR WARN INFO DEBUG TRACE"
@@ -560,7 +571,8 @@ _nmcli_compl_ARGS()
username| \
service| \
password| \
- passwd-file)
+ passwd-file| \
+ file)
if [[ "${#words[@]}" -eq 2 ]]; then
return 0
fi
@@ -870,7 +882,7 @@ _nmcli()
;;
c|co|con|conn|conne|connec|connect|connecti|connectio|connection)
if [[ ${#words[@]} -eq 2 ]]; then
- _nmcli_compl_COMMAND "$command" show up down add modify clone edit delete monitor reload load
+ _nmcli_compl_COMMAND "$command" show up down add modify clone edit delete monitor reload load import export
elif [[ ${#words[@]} -gt 2 ]]; then
case "$command" in
s|sh|sho|show)
@@ -1315,6 +1327,61 @@ _nmcli()
COMPREPLY=()
fi
;;
+ i|im|imp|impo|impor|import)
+ if [[ ${#words[@]} -eq 3 ]]; then
+ _nmcli_compl_COMMAND "${words[2]}" type file --temporary
+ elif [[ ${#words[@]} -gt 3 ]]; then
+ _nmcli_array_delete_at words 0 1
+
+ LONG_OPTIONS=(help temporary)
+ HELP_ONLY_AS_FIRST=1
+ _nmcli_compl_OPTIONS
+ case $? in
+ 0)
+ return 0
+ ;;
+ 1)
+ if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then
+ _nmcli_compl_COMMAND "${words[2]}" type file
+ fi
+ return 0
+ ;;
+ esac
+
+ OPTIONS=(type file)
+ OPTIONS_MANDATORY=(type file)
+ ALIASES=("type:vpn-type")
+ _nmcli_compl_ARGS ${ALIASES[@]}
+ return 0
+ fi
+ ;;
+ e|ex|exp|expo|expor|export)
+ if [[ ${#words[@]} -eq 3 ]]; then
+ _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")"
+ elif [[ ${#words[@]} -gt 3 ]]; then
+ _nmcli_array_delete_at words 0 1
+
+ LONG_OPTIONS=(help)
+ HELP_ONLY_AS_FIRST=1
+ _nmcli_compl_OPTIONS
+ case $? in
+ 0)
+ return 0
+ ;;
+ 1)
+ if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then
+ _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" "${LONG_OPTIONS[@]}"
+ fi
+ return 0
+ ;;
+ esac
+
+ OPTIONS=(id uuid path)
+ _nmcli_compl_ARGS_CONNECTION && return 0
+ return 0
+ fi
+ ;;
+
esac
fi
;;
diff --git a/clients/common/nm-vpn-helpers.c b/clients/common/nm-vpn-helpers.c
index 73e6600217..276df2b95c 100644
--- a/clients/common/nm-vpn-helpers.c
+++ b/clients/common/nm-vpn-helpers.c
@@ -37,22 +37,30 @@ static gboolean plugins_loaded;
static GSList *plugins = NULL;
NMVpnEditorPlugin *
-nm_vpn_get_plugin_by_service (const char *service)
+nm_vpn_get_plugin_by_service (const char *service, GError **error)
{
NMVpnEditorPlugin *plugin = NULL;
NMVpnPluginInfo *plugin_info;
+ char *type = NULL;
g_return_val_if_fail (service != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
if (G_UNLIKELY (!plugins_loaded))
nm_vpn_get_plugins ();
+ if (!g_str_has_prefix (service, NM_DBUS_INTERFACE))
+ service = type = g_strdup_printf ("%s.%s", NM_DBUS_INTERFACE, service);
+
plugin_info = nm_vpn_plugin_info_list_find_by_service (plugins, service);
if (plugin_info) {
plugin = nm_vpn_plugin_info_get_editor_plugin (plugin_info);
if (!plugin)
- plugin = nm_vpn_plugin_info_load_editor_plugin (plugin_info, NULL);
- }
+ plugin = nm_vpn_plugin_info_load_editor_plugin (plugin_info, error);
+ } else
+ g_set_error_literal (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_FAILED,
+ _("could not get VPN plugin info"));
+ g_free (type);
return plugin;
}
@@ -80,7 +88,7 @@ nm_vpn_supports_ipv6 (NMConnection *connection)
service_type = nm_setting_vpn_get_service_type (s_vpn);
g_return_val_if_fail (service_type != NULL, FALSE);
- plugin = nm_vpn_get_plugin_by_service (service_type);
+ plugin = nm_vpn_get_plugin_by_service (service_type, NULL);
g_return_val_if_fail (plugin != NULL, FALSE);
capabilities = nm_vpn_editor_plugin_get_capabilities (plugin);
diff --git a/clients/common/nm-vpn-helpers.h b/clients/common/nm-vpn-helpers.h
index bc857a67c3..9a2ff7b100 100644
--- a/clients/common/nm-vpn-helpers.h
+++ b/clients/common/nm-vpn-helpers.h
@@ -25,7 +25,7 @@
GSList *nm_vpn_get_plugins (void);
-NMVpnEditorPlugin *nm_vpn_get_plugin_by_service (const char *service);
+NMVpnEditorPlugin *nm_vpn_get_plugin_by_service (const char *service, GError **error);
gboolean nm_vpn_supports_ipv6 (NMConnection *connection);
diff --git a/man/nmcli.1.in b/man/nmcli.1.in
index 6e8881b142..35edc71846 100644
--- a/man/nmcli.1.in
+++ b/man/nmcli.1.in
@@ -816,6 +816,29 @@ then \fINetworkManager\fP will reload connection files any time they change
Load/reload one or more connection files from disk. Use this after manually
editing a connection file to ensure that \fBNetworkManager\fP is aware
of its latest state.
+.TP
+.B import [--temporary] type <type> file <file to import>
+.br
+Import an external/foreign configuration as a NetworkManager connection profile.
+The type of the input file is specified by \fItype\fP option.
+.br
+Only VPN configurations are supported at the moment. The configuration
+is imported by NetworkManager VPN plugins. \fItype\fP values are the same as for
+\fIvpn-type\fP option in \fBnmcli connection add\fP. VPN configurations are
+imported by VPN plugins. Therefore the proper VPN plugin has to be installed
+so that nmcli could import the data.
+.br
+The imported connection profile will be saved as persistent unless \fI--temporary\fP
+option is specified, in which case the new profile won't exist after NetworkManager
+restart.
+.TP
+.B export [ id | uuid | path ] <ID> [<output file>]
+.br
+Export a connection.
+.br
+Only VPN connections are supported at the moment. A proper VPN plugin has to be
+installed so that nmcli could export a connection. If no \fI<output file>\fP is
+provided, the VPN configuration data will be printed to standard output.
.RE
.TP
@@ -1187,6 +1210,14 @@ appends a Google public DNS server to DNS servers in ABC profile.
.IP
removes the specified IP address from (static) profile ABC.
+.IP "\fB\f(CWnmcli con import type openvpn file ~/Downloads/frootvpn.ovpn\fP\fP"
+.IP
+imports an OpenVPN configuration to NetworkManager.
+
+.IP "\fB\f(CWnmcli con export corp-vpnc /home/joe/corpvpn.conf\fP\fP"
+.IP
+exports NetworkManager VPN profile corp-vpnc as standard Cisco (vpnc) configuration.
+
.SH NOTES
\fInmcli\fP accepts abbreviations, as long as they are a unique prefix in the set
of possible options. As new options get added, these abbreviations are not guaranteed