/* nmcli - command-line tool to control NetworkManager * * Jiri Klimes * * 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 2010 - 2015 Red Hat, Inc. */ /* Generated configuration file */ #include "nm-default.h" #include #include #include #include #include #include #include #include #include #include #include "polkit-agent.h" #include "nmcli.h" #include "utils.h" #include "common.h" #include "connections.h" #include "devices.h" #include "general.h" #include "agent.h" #if defined(NM_DIST_VERSION) # define NMCLI_VERSION NM_DIST_VERSION #else # define NMCLI_VERSION VERSION #endif /* Global NmCli object */ // FIXME: Currently, we pass NmCli over in most APIs, but we might refactor // that and use the global variable directly instead. NmCli nm_cli; typedef struct { NmCli *nmc; int argc; char **argv; } ArgsInfo; /* --- Global variables --- */ GMainLoop *loop = NULL; static sigset_t signal_set; struct termios termios_orig; /* Get an error quark for use with GError */ GQuark nmcli_error_quark (void) { static GQuark error_quark = 0; if (G_UNLIKELY (error_quark == 0)) error_quark = g_quark_from_static_string ("nmcli-error-quark"); return error_quark; } static void usage (void) { g_printerr (_("Usage: nmcli [OPTIONS] OBJECT { COMMAND | help }\n" "\n" "OPTIONS\n" " -t[erse] terse output\n" " -p[retty] pretty output\n" " -m[ode] tabular|multiline output mode\n" " -c[olors] auto|yes|no whether to use colors in output\n" " -f[ields] |all|common specify fields to output\n" " -e[scape] yes|no escape columns separators in values\n" " -a[sk] ask for missing parameters\n" " -s[how-secrets] allow displaying passwords\n" " -w[ait] set timeout waiting for finishing operations\n" " -v[ersion] show program version\n" " -h[elp] print this help\n" "\n" "OBJECT\n" " g[eneral] NetworkManager's general status and operations\n" " n[etworking] overall networking control\n" " r[adio] NetworkManager radio switches\n" " c[onnection] NetworkManager's connections\n" " d[evice] devices managed by NetworkManager\n" " a[gent] NetworkManager secret agent or polkit agent\n" " m[onitor] monitor NetworkManager changes\n" "\n")); } static const NMCCommand nmcli_cmds[] = { { "general", do_general, NULL }, { "monitor", do_monitor, NULL }, { "networking", do_networking, NULL }, { "radio", do_radio, NULL }, { "connection", do_connections, NULL }, { "device", do_devices, NULL }, { "agent", do_agent, NULL }, { NULL, do_overview, usage } }; static NMCResultCode parse_command_line (NmCli *nmc, int argc, char **argv) { char *base; base = strrchr (argv[0], '/'); if (base == NULL) base = argv[0]; else base++; if (argc > 1 && nm_streq (argv[1], "--complete-args")) { nmc->complete = TRUE; argv[1] = argv[0]; argc--; argv++; } /* parse options */ while (argc > 1) { char *opt = argv[1]; /* '--' ends options */ if (strcmp (opt, "--") == 0) { argc--; argv++; break; } if (opt[0] != '-') break; if (opt[1] == '-') opt++; if (matches (opt, "-terse") == 0) { if (nmc->print_output == NMC_PRINT_TERSE) { g_string_printf (nmc->return_text, _("Error: Option '--terse' is specified the second time.")); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return nmc->return_value; } else if (nmc->print_output == NMC_PRINT_PRETTY) { g_string_printf (nmc->return_text, _("Error: Option '--terse' is mutually exclusive with '--pretty'.")); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return nmc->return_value; } else nmc->print_output = NMC_PRINT_TERSE; } else if (matches (opt, "-pretty") == 0) { if (nmc->print_output == NMC_PRINT_PRETTY) { g_string_printf (nmc->return_text, _("Error: Option '--pretty' is specified the second time.")); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return nmc->return_value; } else if (nmc->print_output == NMC_PRINT_TERSE) { g_string_printf (nmc->return_text, _("Error: Option '--pretty' is mutually exclusive with '--terse'.")); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return nmc->return_value; } else nmc->print_output = NMC_PRINT_PRETTY; } else if (matches (opt, "-mode") == 0) { nmc->mode_specified = TRUE; next_arg (&argc, &argv); if (argc <= 1) { g_string_printf (nmc->return_text, _("Error: missing argument for '%s' option."), opt); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return nmc->return_value; } if (matches (argv[1], "tabular") == 0) nmc->multiline_output = FALSE; else if (matches (argv[1], "multiline") == 0) nmc->multiline_output = TRUE; else { g_string_printf (nmc->return_text, _("Error: '%s' is not valid argument for '%s' option."), argv[1], opt); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return nmc->return_value; } } else if (matches (opt, "-colors") == 0) { next_arg (&argc, &argv); if (argc <= 1) { g_string_printf (nmc->return_text, _("Error: missing argument for '%s' option."), opt); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return nmc->return_value; } if (matches (argv[1], "auto") == 0) nmc->use_colors = NMC_USE_COLOR_AUTO; else if (matches (argv[1], "yes") == 0) nmc->use_colors = NMC_USE_COLOR_YES; else if (matches (argv[1], "no") == 0) nmc->use_colors = NMC_USE_COLOR_NO; else { g_string_printf (nmc->return_text, _("Error: '%s' is not valid argument for '%s' option."), argv[1], opt); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return nmc->return_value; } } else if (matches (opt, "-escape") == 0) { next_arg (&argc, &argv); if (argc <= 1) { g_string_printf (nmc->return_text, _("Error: missing argument for '%s' option."), opt); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return nmc->return_value; } if (matches (argv[1], "yes") == 0) nmc->escape_values = TRUE; else if (matches (argv[1], "no") == 0) nmc->escape_values = FALSE; else { g_string_printf (nmc->return_text, _("Error: '%s' is not valid argument for '%s' option."), argv[1], opt); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return nmc->return_value; } } else if (matches (opt, "-fields") == 0) { next_arg (&argc, &argv); if (argc <= 1) { g_string_printf (nmc->return_text, _("Error: fields for '%s' options are missing."), opt); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return nmc->return_value; } nmc->required_fields = g_strdup (argv[1]); } else if (matches (opt, "-nocheck") == 0) { /* ignore for backward compatibility */ } else if (matches (opt, "-ask") == 0) { nmc->ask = TRUE; } else if (matches (opt, "-show-secrets") == 0) { nmc->show_secrets = TRUE; } else if (matches (opt, "-wait") == 0) { unsigned long timeout; next_arg (&argc, &argv); if (argc <= 1) { g_string_printf (nmc->return_text, _("Error: missing argument for '%s' option."), opt); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return nmc->return_value; } if (!nmc_string_to_uint (argv[1], TRUE, 0, G_MAXINT, &timeout)) { g_string_printf (nmc->return_text, _("Error: '%s' is not a valid timeout for '%s' option."), argv[1], opt); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return nmc->return_value; } nmc->timeout = (int) timeout; } else if (matches (opt, "-version") == 0) { g_print (_("nmcli tool, version %s\n"), NMCLI_VERSION); return NMC_RESULT_SUCCESS; } else if (matches (opt, "-help") == 0) { usage (); return NMC_RESULT_SUCCESS; } else { g_string_printf (nmc->return_text, _("Error: Option '%s' is unknown, try 'nmcli -help'."), opt); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return nmc->return_value; } argc--; argv++; } /* Now run the requested command */ return nmc_do_cmd (nmc, nmcli_cmds, argv[1], argc-1, argv+1); } static gboolean nmcli_sigint = FALSE; static pthread_mutex_t sigint_mutex = PTHREAD_MUTEX_INITIALIZER; static gboolean nmcli_sigquit_internal = FALSE; gboolean nmc_seen_sigint (void) { gboolean sigint; pthread_mutex_lock (&sigint_mutex); sigint = nmcli_sigint; pthread_mutex_unlock (&sigint_mutex); return sigint; } void nmc_clear_sigint (void) { pthread_mutex_lock (&sigint_mutex); nmcli_sigint = FALSE; pthread_mutex_unlock (&sigint_mutex); } void nmc_set_sigquit_internal (void) { nmcli_sigquit_internal = TRUE; } static int event_hook_for_readline (void) { /* Make readline() exit on SIGINT */ if (nmc_seen_sigint ()) { rl_echo_signal_char (SIGINT); rl_stuff_char ('\n'); } return 0; } void *signal_handling_thread (void *arg); /* * Thread function waiting for signals and processing them. * Wait for signals in signal set. The semantics of sigwait() require that all * threads (including the thread calling sigwait()) have the signal masked, for * reliable operation. Otherwise, a signal that arrives while this thread is * not blocked in sigwait() might be delivered to another thread. */ void * signal_handling_thread (void *arg) { int signo; while (1) { sigwait (&signal_set, &signo); switch (signo) { case SIGINT: if (nmc_get_in_readline ()) { /* Don't quit when in readline, only signal we received SIGINT */ pthread_mutex_lock (&sigint_mutex); nmcli_sigint = TRUE; pthread_mutex_unlock (&sigint_mutex); } else { /* We can quit nmcli */ tcsetattr (STDIN_FILENO, TCSADRAIN, &termios_orig); nmc_cleanup_readline (); g_print (_("\nError: nmcli terminated by signal %s (%d)\n"), strsignal (signo), signo); exit (1); } break; case SIGQUIT: case SIGTERM: tcsetattr (STDIN_FILENO, TCSADRAIN, &termios_orig); nmc_cleanup_readline (); if (!nmcli_sigquit_internal) g_print (_("\nError: nmcli terminated by signal %s (%d)\n"), strsignal (signo), signo); exit (1); break; default: break; } } return NULL; } /* * Mask the signals we are interested in and create a signal handling thread. * Because all threads inherit the signal mask from their creator, all threads * in the process will have the signals masked. That's why setup_signals() has * to be called before creating other threads. */ static gboolean setup_signals (void) { pthread_t signal_thread_id; int status; sigemptyset (&signal_set); sigaddset (&signal_set, SIGINT); sigaddset (&signal_set, SIGQUIT); sigaddset (&signal_set, SIGTERM); /* Block all signals of interest. */ status = pthread_sigmask (SIG_BLOCK, &signal_set, NULL); if (status != 0) { g_printerr (_("Failed to set signal mask: %d\n"), status); return FALSE; } /* Create the signal handling thread. */ status = pthread_create (&signal_thread_id, NULL, signal_handling_thread, NULL); if (status != 0) { g_printerr (_("Failed to create signal handling thread: %d\n"), status); return FALSE; } return TRUE; } static void nmc_convert_strv_to_string (const GValue *src_value, GValue *dest_value) { char **strings; strings = g_value_get_boxed (src_value); if (strings) g_value_take_string (dest_value, g_strjoinv (",", strings)); else g_value_set_string (dest_value, ""); } static void nmc_convert_string_hash_to_string (const GValue *src_value, GValue *dest_value) { GHashTable *hash; GHashTableIter iter; const char *key, *value; GString *string; hash = (GHashTable *) g_value_get_boxed (src_value); string = g_string_new (NULL); if (hash) { g_hash_table_iter_init (&iter, hash); while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) { if (string->len) g_string_append_c (string, ','); g_string_append_printf (string, "%s=%s", key, value); } } g_value_take_string (dest_value, g_string_free (string, FALSE)); } static void nmc_convert_bytes_to_string (const GValue *src_value, GValue *dest_value) { GBytes *bytes; const guint8 *array; gsize length; GString *printable; guint i = 0; bytes = g_value_get_boxed (src_value); printable = g_string_new ("["); if (bytes) { array = g_bytes_get_data (bytes, &length); while (i < MIN (length, 35)) { if (i > 0) g_string_append_c (printable, ' '); g_string_append_printf (printable, "0x%02X", array[i++]); } if (i < length) g_string_append (printable, " ... "); } g_string_append_c (printable, ']'); g_value_take_string (dest_value, g_string_free (printable, FALSE)); } static void nmc_value_transforms_register (void) { g_value_register_transform_func (G_TYPE_STRV, G_TYPE_STRING, nmc_convert_strv_to_string); /* This depends on the fact that all of the hash-table-valued properties * in libnm-core are string->string. */ g_value_register_transform_func (G_TYPE_HASH_TABLE, G_TYPE_STRING, nmc_convert_string_hash_to_string); g_value_register_transform_func (G_TYPE_BYTES, G_TYPE_STRING, nmc_convert_bytes_to_string); } static NMClient * nmc_get_client (NmCli *nmc) { GError *error = NULL; if (!nmc->client) { nmc->client = nm_client_new (NULL, &error); if (!nmc->client) { g_critical (_("Error: Could not create NMClient object: %s."), error->message); g_clear_error (&error); exit (NMC_RESULT_ERROR_UNKNOWN); } } return nmc->client; } /* Initialize NmCli structure - set default values */ static void nmc_init (NmCli *nmc) { nmc->client = NULL; nmc->get_client = &nmc_get_client; nmc->return_value = NMC_RESULT_SUCCESS; nmc->return_text = g_string_new (_("Success")); nmc->timeout = -1; nmc->connections = NULL; nmc->secret_agent = NULL; nmc->pwds_hash = NULL; nmc->pk_listener = NULL; nmc->should_wait = 0; nmc->nowait_flag = TRUE; nmc->print_output = NMC_PRINT_NORMAL; nmc->multiline_output = FALSE; nmc->mode_specified = FALSE; nmc->escape_values = TRUE; nmc->required_fields = NULL; nmc->output_data = g_ptr_array_new_full (20, g_free); memset (&nmc->print_fields, '\0', sizeof (NmcPrintFields)); nmc->ask = FALSE; nmc->complete = FALSE; nmc->show_secrets = FALSE; nmc->use_colors = NMC_USE_COLOR_AUTO; nmc->in_editor = FALSE; nmc->editor_status_line = FALSE; nmc->editor_save_confirmation = TRUE; nmc->editor_show_secrets = FALSE; nmc->editor_prompt_color = NMC_TERM_COLOR_NORMAL; } static void nmc_cleanup (NmCli *nmc) { if (nmc->client) g_object_unref (nmc->client); g_string_free (nmc->return_text, TRUE); if (nmc->secret_agent) { /* Destroy secret agent if we have one. */ nm_secret_agent_old_unregister (nmc->secret_agent, NULL, NULL); g_object_unref (nmc->secret_agent); } if (nmc->pwds_hash) g_hash_table_destroy (nmc->pwds_hash); g_free (nmc->required_fields); nmc_empty_output_fields (nmc); g_ptr_array_unref (nmc->output_data); nmc_polkit_agent_fini (nmc); } static gboolean start (gpointer data) { ArgsInfo *info = (ArgsInfo *) data; info->nmc->return_value = parse_command_line (info->nmc, info->argc, info->argv); if (!info->nmc->should_wait) g_main_loop_quit (loop); return FALSE; } int main (int argc, char *argv[]) { ArgsInfo args_info = { &nm_cli, argc, argv }; /* Set up unix signal handling */ if (!setup_signals ()) exit (NMC_RESULT_ERROR_UNKNOWN); /* Set locale to use environment variables */ setlocale (LC_ALL, ""); #ifdef GETTEXT_PACKAGE /* Set i18n stuff */ bindtextdomain (GETTEXT_PACKAGE, NMCLI_LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); #endif nm_g_type_init (); /* Save terminal settings */ tcgetattr (STDIN_FILENO, &termios_orig); /* readline init */ rl_event_hook = event_hook_for_readline; /* Set 0.01s timeout to mitigate slowness in readline when a broken version is used. * See https://bugzilla.redhat.com/show_bug.cgi?id=1109946 */ rl_set_keyboard_input_timeout (10000); nmc_value_transforms_register (); nmc_init (&nm_cli); g_idle_add (start, &args_info); loop = g_main_loop_new (NULL, FALSE); /* create main loop */ g_main_loop_run (loop); /* run main loop */ if (nm_cli.complete) { /* Remove error statuses from command completion runs. */ if (nm_cli.return_value < NMC_RESULT_COMPLETE_FILE) nm_cli.return_value = NMC_RESULT_SUCCESS; } else if (nm_cli.return_value != NMC_RESULT_SUCCESS) { /* Print result descripting text */ g_printerr ("%s\n", nm_cli.return_text->str); } g_main_loop_unref (loop); nmc_cleanup (&nm_cli); return nm_cli.return_value; }