From 9a0e4bae4720d45b8596f86c374a04d666490229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Klime=C5=A1?= Date: Mon, 23 Nov 2015 17:21:02 +0100 Subject: [PATCH] cli: add 'nmcli connection import' (rh #1034105) Synopsis: nmcli connection import [--temporary] type file Only VPN configurations can be imported at the moment. https://bugzilla.redhat.com/show_bug.cgi?id=1034105 --- clients/cli/Makefile.am | 2 + clients/cli/connections.c | 141 ++++++++++++++++++++++++++++++++++- clients/cli/nmcli-completion | 47 +++++++++++- man/nmcli.1.in | 19 +++++ 4 files changed, 205 insertions(+), 4 deletions(-) diff --git a/clients/cli/Makefile.am b/clients/cli/Makefile.am index a2250adc6..192920e6e 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 100d0a1a0..1fa93b94f 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? ") @@ -274,7 +275,8 @@ usage (void) " delete [id | uuid | path] \n\n" " monitor [id | uuid | path] ...\n\n" " reload\n\n" - " load [ ... ]\n\n")); + " load [ ... ]\n\n" + " import [--temporary] type file \n\n")); } static void @@ -522,6 +524,19 @@ 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 file \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 gboolean usage_connection_second_level (const char *cmd) { @@ -549,6 +564,8 @@ 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 ret = FALSE; return ret; @@ -9967,6 +9984,114 @@ 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; +} + typedef struct { NmCli *nmc; @@ -10067,6 +10192,11 @@ 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; } if (generator_func) @@ -10250,6 +10380,15 @@ 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 { 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 63050a36d..c3630c2c4 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 elif [[ ${#words[@]} -gt 2 ]]; then case "$command" in s|sh|sho|show) @@ -1315,6 +1327,35 @@ _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 + ;; + esac fi ;; diff --git a/man/nmcli.1.in b/man/nmcli.1.in index 6e8881b14..180dd1027 100644 --- a/man/nmcli.1.in +++ b/man/nmcli.1.in @@ -816,6 +816,21 @@ 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 file +.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. .RE .TP @@ -1187,6 +1202,10 @@ 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. + .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