diff options
-rw-r--r-- | clients/cli/connections.c | 188 | ||||
-rw-r--r-- | clients/cli/nmcli-completion | 28 | ||||
-rw-r--r-- | man/nmcli.1.in | 12 |
3 files changed, 216 insertions, 12 deletions
diff --git a/clients/cli/connections.c b/clients/cli/connections.c index 1fa93b94f3..870b61ff0e 100644 --- a/clients/cli/connections.c +++ b/clients/cli/connections.c @@ -48,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): ") @@ -276,7 +277,8 @@ usage (void) " monitor [id | uuid | path] <ID> ...\n\n" " reload\n\n" " load <filename> [ <filename>... ]\n\n" - " import [--temporary] type <type> file <file to import>\n\n")); + " import [--temporary] type <type> file <file to import>\n\n" + " export [id | uuid | path] <ID> [<output file>]\n\n")); } static void @@ -537,6 +539,17 @@ usage_connection_import (void) "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) { @@ -566,6 +579,8 @@ usage_connection_second_level (const char *cmd) 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; @@ -6885,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) { @@ -10092,6 +10133,127 @@ finish: 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; @@ -10197,6 +10359,8 @@ nmcli_con_tab_completion (const char *text, int start, int end) } 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) @@ -10389,6 +10553,8 @@ do_connections (NmCli *nmc, int argc, char **argv) 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 c3630c2c4c..e5aae8dba6 100644 --- a/clients/cli/nmcli-completion +++ b/clients/cli/nmcli-completion @@ -882,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 import + _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) @@ -1355,6 +1355,32 @@ _nmcli() 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/man/nmcli.1.in b/man/nmcli.1.in index 180dd1027b..35edc71846 100644 --- a/man/nmcli.1.in +++ b/man/nmcli.1.in @@ -831,6 +831,14 @@ so that nmcli could import the data. 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 @@ -1206,6 +1214,10 @@ removes the specified IP address from (static) profile ABC. .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 |