From 74e56e23e8d4fe93445b9d2caa7ba29bd1e6931b Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 27 Apr 2008 14:30:06 +0000 Subject: [PATCH] 2008-04-27 Dan Williams * callouts/Makefile.am callouts/nm-dispatcher-action.c callouts/nm-dispatcher-action.h callouts/nm-dispatcher.conf callouts/nm-dispatcher.xml callouts/org.freedesktop.nm_dispatcher.service - Re-implement the dispatcher as a system-bus activated service that NM calls on-demand, rather than an always running daemon * src/Makefile.am - Add callouts dir to includes to pick up dispatcher defines * src/nm-device.c - (nm_device_state_changed): call dispatcher on device activated/ deactivated * src/vpn-manager/nm-vpn-connection.c - (nm_vpn_connection_set_vpn_state): call dispatcher when VPN connections go up and down * src/NetworkManagerUtils.c src/NetworkManagerUtils.h - (nm_utils_call_dispatcher): helper to call dispatcher git-svn-id: http://svn-archive.gnome.org/svn/NetworkManager/trunk@3607 4912f4e0-d625-0410-9fb7-b9a5a253dbdc --- ChangeLog | 26 + callouts/Makefile.am | 51 +- callouts/nm-dispatcher-action.c | 576 ++++++++++++++++++ callouts/nm-dispatcher-action.h | 35 ++ callouts/nm-dispatcher.conf | 14 + callouts/nm-dispatcher.xml | 37 ++ .../org.freedesktop.nm_dispatcher.service | 5 + src/Makefile.am | 3 +- src/NetworkManagerUtils.c | 139 +++++ src/NetworkManagerUtils.h | 7 +- src/nm-device.c | 34 +- src/vpn-manager/nm-vpn-connection.c | 31 + 12 files changed, 940 insertions(+), 18 deletions(-) create mode 100644 callouts/nm-dispatcher-action.c create mode 100644 callouts/nm-dispatcher-action.h create mode 100644 callouts/nm-dispatcher.conf create mode 100644 callouts/nm-dispatcher.xml create mode 100644 callouts/org.freedesktop.nm_dispatcher.service diff --git a/ChangeLog b/ChangeLog index 437b5b637..232733851 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +2008-04-27 Dan Williams + + * callouts/Makefile.am + callouts/nm-dispatcher-action.c + callouts/nm-dispatcher-action.h + callouts/nm-dispatcher.conf + callouts/nm-dispatcher.xml + callouts/org.freedesktop.nm_dispatcher.service + - Re-implement the dispatcher as a system-bus activated service that + NM calls on-demand, rather than an always running daemon + + * src/Makefile.am + - Add callouts dir to includes to pick up dispatcher defines + + * src/nm-device.c + - (nm_device_state_changed): call dispatcher on device activated/ + deactivated + + * src/vpn-manager/nm-vpn-connection.c + - (nm_vpn_connection_set_vpn_state): call dispatcher when VPN connections + go up and down + + * src/NetworkManagerUtils.c + src/NetworkManagerUtils.h + - (nm_utils_call_dispatcher): helper to call dispatcher + 2008-04-27 Dan Williams * src/NetworkManagerUtils.c diff --git a/callouts/Makefile.am b/callouts/Makefile.am index a26d5505c..7a5f70d4e 100644 --- a/callouts/Makefile.am +++ b/callouts/Makefile.am @@ -1,7 +1,11 @@ dbusservicedir = $(DBUS_SYS_DIR) -dbusservice_DATA = nm-dhcp-client.conf +dbusservice_DATA = \ + nm-dhcp-client.conf \ + nm-dispatcher.conf -libexec_PROGRAMS = nm-dhcp-client.action +libexec_PROGRAMS = \ + nm-dhcp-client.action \ + nm-dispatcher.action nm_dhcp_client_action_SOURCES = \ nm-dhcp-client-action.c @@ -14,8 +18,43 @@ nm_dhcp_client_action_CPPFLAGS = \ -DSYSCONFDIR=\"$(sysconfdir)\" \ -DLIBEXECDIR=\"$(libexecdir)\" -nm_dhcp_client_action_LDADD = \ - $(DBUS_LIBS) \ - $(GTHREAD_LIBS) +nm_dhcp_client_action_LDADD = \ + $(DBUS_LIBS) \ + $(GTHREAD_LIBS) + + +nm_dispatcher_action_SOURCES = \ + nm-dispatcher-action.c \ + nm-dispatcher-action.h + +nm_dispatcher_action_CPPFLAGS = \ + -I${top_srcdir} \ + -I${top_srcdir}/include \ + -I${top_srcdir}/libnm-util \ + $(DBUS_CFLAGS) \ + $(GTHREAD_CFLAGS) \ + -DDBUS_API_SUBJECT_TO_CHANGE \ + -DG_DISABLE_DEPRECATED \ + -DSYSCONFDIR=\"$(sysconfdir)\" \ + -DLIBEXECDIR=\"$(libexecdir)\" + +nm_dispatcher_action_LDADD = \ + $(DBUS_LIBS) \ + $(GTHREAD_LIBS) \ + $(top_builddir)/libnm-util/libnm-util.la + +nm-dispatcher-glue.h: nm-dispatcher.xml + dbus-binding-tool --prefix=nm_dispatcher --mode=glib-server --output=nm-dispatcher-glue.h $(top_srcdir)/callouts/nm-dispatcher.xml + +dbusactivationdir = $(prefix)/share/dbus-1/system-services +dbusactivation_DATA = org.freedesktop.nm_dispatcher.service + +BUILT_SOURCES = nm-dispatcher-glue.h + +CLEANFILES = $(BUILT_SOURCES) + +EXTRA_DIST = \ + $(dbusservice_DATA) \ + $(dbusactivation_DATA) \ + nm-dispatcher.xml -EXTRA_DIST = $(dbusservice_DATA) diff --git a/callouts/nm-dispatcher-action.c b/callouts/nm-dispatcher-action.c new file mode 100644 index 000000000..b760812a4 --- /dev/null +++ b/callouts/nm-dispatcher-action.c @@ -0,0 +1,576 @@ +/* NetworkManager -- Network link manager + * + * Dan Williams + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * (C) Copyright 2008 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "nm-dispatcher-action.h" + +#define NMD_SCRIPT_DIR SYSCONFDIR "/NetworkManager/dispatcher.d" + +static GMainLoop *loop = NULL; + +static gboolean quit_timeout_cb (gpointer user_data); + +typedef struct Handler Handler; +typedef struct HandlerClass HandlerClass; + +GType handler_get_type (void); + +struct Handler { + GObject parent; +}; + +struct HandlerClass { + GObjectClass parent; +}; + +#define HANDLER_TYPE (handler_get_type ()) +#define HANDLER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), HANDLER_TYPE, Handler)) +#define HANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), HANDLER_TYPE, HandlerClass)) +#define IS_HANDLER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), HANDLER_TYPE)) +#define IS_HANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), HANDLER_TYPE)) +#define HANDLER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), HANDLER_TYPE, HandlerClass)) + +G_DEFINE_TYPE(Handler, handler, G_TYPE_OBJECT) + +typedef struct { + DBusGConnection *g_connection; + DBusGProxy *bus_proxy; + guint quit_timeout; + gboolean persist; + + Handler *handler; +} Dispatcher; + +static gboolean +nm_dispatcher_action (Handler *obj, + const char *action, + GHashTable *connection, + GHashTable *connection_props, + GHashTable *device_props, + GError **error); + +#include "nm-dispatcher-glue.h" + + +static void +handler_init (Handler *h) +{ +} + +static void +handler_finalize (GObject *object) +{ + G_OBJECT_CLASS (handler_parent_class)->finalize (object); +} + +static void +handler_class_init (HandlerClass *h_class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (h_class); + + gobject_class->finalize = handler_finalize; +} + +/* + * nmd_permission_check + * + * Verify that the given script has the permissions we want. Specifically, + * ensure that the file is + * - A regular file. + * - Owned by root. + * - Not writable by the group or by other. + * - Not setuid. + * - Executable by the owner. + * + */ +static inline gboolean +nmd_permission_check (struct stat *s, GError **error) +{ + g_return_val_if_fail (s != NULL, FALSE); + g_return_val_if_fail (error != NULL, FALSE); + g_return_val_if_fail (*error == NULL, FALSE); + + /* Only accept regular files */ + if (!S_ISREG (s->st_mode)) { + g_set_error (error, 0, 0, "not a regular file."); + return FALSE; + } + + /* Only accept files owned by root */ + if (s->st_uid != 0) { + g_set_error (error, 0, 0, "not owned by root."); + return FALSE; + } + + /* Only accept files not writable by group or other, and not SUID */ + if (s->st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) { + g_set_error (error, 0, 0, "writable by group or other, or set-UID."); + return FALSE; + } + + /* Only accept files executable by the owner */ + if (!(s->st_mode & S_IXUSR)) { + g_set_error (error, 0, 0, "not executable by owner."); + return FALSE; + } + + return TRUE; +} + + +/* + * nmd_is_valid_filename + * + * Verify that the given script is a valid file name. Specifically, + * ensure that the file: + * - is not a editor backup file + * - is not a package management file + * - does not start with '.' + */ +static inline gboolean +nmd_is_valid_filename (const char *file_name) +{ + char *bad_suffixes[] = { "~", ".rpmsave", ".rpmorig", ".rpmnew", NULL }; + char *tmp; + int i; + + if (file_name[0] == '.') + return FALSE; + for (i = 0; bad_suffixes[i]; i++) { + if (g_str_has_suffix(file_name, bad_suffixes[i])) + return FALSE; + } + tmp = g_strrstr(file_name, ".dpkg-"); + if (tmp && (tmp == strrchr(file_name,'.'))) + return FALSE; + return TRUE; +} + +static gint +sort_files (gconstpointer a, gconstpointer b) +{ + char *a_base = NULL, *b_base = NULL; + int ret = 0; + + if (a && !b) + return 1; + if (!a && !b) + return 0; + if (!a && b) + return -1; + + a_base = g_path_get_basename (a); + b_base = g_path_get_basename (b); + + ret = strcmp (a_base, b_base); + + g_free (a_base); + g_free (b_base); + return ret; +} + +static void +child_setup (gpointer user_data G_GNUC_UNUSED) +{ + /* We are in the child process at this point */ + /* Give child a different process group to ensure signal separation. */ + pid_t pid = getpid (); + setpgid (pid, pid); +} + +static void +dispatch_scripts (const char *action, + const char *iface, + const char *parent_iface, + NMDeviceType type) +{ + GDir *dir; + const char *filename; + GSList *scripts = NULL, *iter; + GError *error = NULL; + + if (!(dir = g_dir_open (NMD_SCRIPT_DIR, 0, &error))) { + g_warning ("g_dir_open() could not open '" NMD_SCRIPT_DIR "'. '%s'", + error->message); + g_error_free (error); + return; + } + + while ((filename = g_dir_read_name (dir))) { + char *file_path; + struct stat s; + GError *pc_error = NULL; + int err; + + if (!nmd_is_valid_filename (filename)) + continue; + + file_path = g_build_filename (NMD_SCRIPT_DIR, filename, NULL); + + err = stat (file_path, &s); + if (err) { + g_warning ("Script '%s' could not be stated: %d", file_path, err); + g_free (file_path); + continue; + } + + if (!nmd_permission_check (&s, &pc_error)) { + g_warning ("Script '%s' could not be executed: %s", file_path, pc_error->message); + g_error_free (pc_error); + g_free (file_path); + } else { + /* success */ + scripts = g_slist_insert_sorted (scripts, file_path, sort_files); + } + } + g_dir_close (dir); + + for (iter = scripts; iter; iter = g_slist_next (iter)) { + gchar *argv[4]; + gchar *envp[1] = { NULL }; + gint status = -1; + + argv[0] = (char *) iter->data; + argv[1] = (char *) iface; + argv[2] = (char *) action; + argv[3] = NULL; + + error = NULL; + if (g_spawn_sync ("/", argv, envp, 0, child_setup, NULL, NULL, NULL, &status, &error)) { + if (WIFEXITED (status)) { + if (WEXITSTATUS (status) != 0) + g_warning ("Script '%s' exited with error status %d.", + (char *) iter->data, WEXITSTATUS (status)); + } else + g_warning ("Script '%s' exited abnormally.", (char *) iter->data); + } else { + g_warning ("Could not run script '%s': (%d) %s", + (char *) iter->data, error->code, error->message); + g_error_free (error); + } + } + + g_slist_foreach (scripts, (GFunc) g_free, NULL); + g_slist_free (scripts); +} + +static gboolean +nm_dispatcher_action (Handler *h, + const char *action, + GHashTable *connection_hash, + GHashTable *connection_props, + GHashTable *device_props, + GError **error) +{ + Dispatcher *d = g_object_get_data (G_OBJECT (h), "dispatcher"); + NMConnection *connection; + char *iface = NULL; + char *parent_iface = NULL; + NMDeviceType type = DEVICE_TYPE_UNKNOWN; + GValue *value; + + /* Back off the quit timeout */ + if (d->quit_timeout) + g_source_remove (d->quit_timeout); + if (!d->persist) + d->quit_timeout = g_timeout_add (10000, quit_timeout_cb, NULL); + + connection = nm_connection_new_from_hash (connection_hash); + if (connection) { + if (!nm_connection_verify (connection)) + g_warning ("Connection was invalid!"); + } + + value = g_hash_table_lookup (device_props, NMD_DEVICE_PROPS_INTERFACE); + if (!value || !G_VALUE_HOLDS_STRING (value)) { + g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_INTERFACE "!"); + goto out; + } + iface = (char *) g_value_get_string (value); + + value = g_hash_table_lookup (device_props, NMD_DEVICE_PROPS_IP_INTERFACE); + if (value) { + if (!G_VALUE_HOLDS_STRING (value)) { + g_warning ("Invalid required value " NMD_DEVICE_PROPS_IP_INTERFACE "!"); + goto out; + } + parent_iface = iface; + iface = (char *) g_value_get_string (value); + } + + value = g_hash_table_lookup (device_props, NMD_DEVICE_PROPS_TYPE); + if (!value || !G_VALUE_HOLDS_UINT (value)) { + g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_TYPE "!"); + goto out; + } + type = g_value_get_uint (value); + + dispatch_scripts (action, iface, parent_iface, type); + +out: + return TRUE; +} + +static gboolean +start_dbus_service (Dispatcher *d) +{ + int request_name_result; + GError *err = NULL; + gboolean success = FALSE; + + if (!dbus_g_proxy_call (d->bus_proxy, "RequestName", &err, + G_TYPE_STRING, NM_DISPATCHER_DBUS_SERVICE, + G_TYPE_UINT, DBUS_NAME_FLAG_DO_NOT_QUEUE, + G_TYPE_INVALID, + G_TYPE_UINT, &request_name_result, + G_TYPE_INVALID)) { + g_warning ("Could not acquire the " NM_DISPATCHER_DBUS_SERVICE " service.\n" + " Message: '%s'", err->message); + g_error_free (err); + goto out; + } + + if (request_name_result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + g_warning ("Could not acquire the " NM_DISPATCHER_DBUS_SERVICE " service " + "as it is already taken. Return: %d", + request_name_result); + goto out; + } + success = TRUE; + +out: + return success; +} + +static void +destroy_cb (DBusGProxy *proxy, gpointer user_data) +{ + g_warning ("Disconnected from the system bus, exiting."); + g_main_loop_quit (loop); +} + +static gboolean +dbus_init (Dispatcher *d) +{ + GError *err = NULL; + DBusConnection *connection; + + dbus_connection_set_change_sigpipe (TRUE); + + d->g_connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &err); + if (!d->g_connection) { + g_warning ("Could not get the system bus. Make sure " + "the message bus daemon is running! Message: %s", + err->message); + g_error_free (err); + return FALSE; + } + + /* Clean up nicely if we get kicked off the bus */ + connection = dbus_g_connection_get_connection (d->g_connection); + dbus_connection_set_exit_on_disconnect (connection, FALSE); + + d->bus_proxy = dbus_g_proxy_new_for_name (d->g_connection, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus"); + if (!d->bus_proxy) { + g_warning ("Could not get the DBus object!"); + goto error; + } + + g_signal_connect (d->bus_proxy, "destroy", G_CALLBACK (destroy_cb), NULL); + + return TRUE; + +error: + return FALSE; +} + +static void +log_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer ignored) +{ + int syslog_priority; + + switch (log_level) { + case G_LOG_LEVEL_ERROR: + syslog_priority = LOG_CRIT; + break; + + case G_LOG_LEVEL_CRITICAL: + syslog_priority = LOG_ERR; + break; + + case G_LOG_LEVEL_WARNING: + syslog_priority = LOG_WARNING; + break; + + case G_LOG_LEVEL_MESSAGE: + syslog_priority = LOG_NOTICE; + break; + + case G_LOG_LEVEL_DEBUG: + syslog_priority = LOG_DEBUG; + break; + + case G_LOG_LEVEL_INFO: + default: + syslog_priority = LOG_INFO; + break; + } + + syslog (syslog_priority, "%s", message); +} + + +static void +logging_setup (void) +{ + openlog (G_LOG_DOMAIN, LOG_CONS, LOG_DAEMON); + g_log_set_handler (G_LOG_DOMAIN, + G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, + log_handler, + NULL); +} + +static void +logging_shutdown (void) +{ + closelog (); +} + +static void +signal_handler (int signo) +{ + if (signo == SIGINT || signo == SIGTERM) { + g_message ("Caught signal %d, shutting down...", signo); + g_main_loop_quit (loop); + } +} + +static void +setup_signals (void) +{ + struct sigaction action; + sigset_t mask; + + sigemptyset (&mask); + action.sa_handler = signal_handler; + action.sa_mask = mask; + action.sa_flags = 0; + sigaction (SIGTERM, &action, NULL); + sigaction (SIGINT, &action, NULL); +} + +static gboolean +quit_timeout_cb (gpointer user_data) +{ + g_main_loop_quit (loop); + return FALSE; +} + +int +main (int argc, char **argv) +{ + Dispatcher *d = g_malloc0 (sizeof (Dispatcher)); + GOptionContext *opt_ctx; + GError *error = NULL; + gboolean debug = FALSE; + gboolean persist = FALSE; + + GOptionEntry entries[] = { + { "debug", 0, 0, G_OPTION_ARG_NONE, &debug, "Output to console rather than syslog", NULL }, + { "persist", 0, 0, G_OPTION_ARG_NONE, &persist, "Don't quit after a short timeout", NULL }, + { NULL } + }; + + opt_ctx = g_option_context_new (NULL); + g_option_context_set_summary (opt_ctx, "Executes scripts upon actions by NetworkManager."); + g_option_context_add_main_entries (opt_ctx, entries, NULL); + + if (!g_option_context_parse (opt_ctx, &argc, &argv, &error)) { + g_warning ("%s\n", error->message); + g_error_free (error); + return 1; + } + + g_option_context_free (opt_ctx); + + g_type_init (); + setup_signals (); + + if (!debug) + logging_setup (); + + loop = g_main_loop_new (NULL, FALSE); + + if (!dbus_init (d)) + return -1; + if (!start_dbus_service (d)) + return -1; + + d->persist = persist; + d->handler = g_object_new (HANDLER_TYPE, NULL); + if (!d->handler) + return -1; + g_object_set_data (G_OBJECT (d->handler), "dispatcher", d); + + dbus_g_object_type_install_info (HANDLER_TYPE, &dbus_glib_nm_dispatcher_object_info); + dbus_g_connection_register_g_object (d->g_connection, + NM_DISPATCHER_DBUS_PATH, + G_OBJECT (d->handler)); + + if (!persist) + d->quit_timeout = g_timeout_add (10000, quit_timeout_cb, NULL); + + g_main_loop_run (loop); + + g_object_unref (d->handler); + dbus_g_connection_unref (d->g_connection); + g_free (d); + + if (!debug) + logging_shutdown (); + + return 0; +} + diff --git a/callouts/nm-dispatcher-action.h b/callouts/nm-dispatcher-action.h new file mode 100644 index 000000000..35a0832b7 --- /dev/null +++ b/callouts/nm-dispatcher-action.h @@ -0,0 +1,35 @@ +/* NetworkManager -- Network link manager + * + * Dan Williams + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * (C) Copyright 2008 Red Hat, Inc. + */ + + +#define NM_DISPATCHER_DBUS_SERVICE "org.freedesktop.nm_dispatcher" +#define NM_DISPATCHER_DBUS_IFACE "org.freedesktop.nm_dispatcher" +#define NM_DISPATCHER_DBUS_PATH "/org/freedesktop/nm_dispatcher" + +#define NMD_CONNECTION_PROPS_SERVICE_NAME "service-name" +#define NMD_CONNECTION_PROPS_PATH "path" + +#define NMD_DEVICE_PROPS_INTERFACE "interface" +#define NMD_DEVICE_PROPS_IP_INTERFACE "ip-interface" +#define NMD_DEVICE_PROPS_TYPE "type" +#define NMD_DEVICE_PROPS_STATE "state" +#define NMD_DEVICE_PROPS_PATH "path" + diff --git a/callouts/nm-dispatcher.conf b/callouts/nm-dispatcher.conf new file mode 100644 index 000000000..32833a73c --- /dev/null +++ b/callouts/nm-dispatcher.conf @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/callouts/nm-dispatcher.xml b/callouts/nm-dispatcher.xml new file mode 100644 index 000000000..532ae9f31 --- /dev/null +++ b/callouts/nm-dispatcher.xml @@ -0,0 +1,37 @@ + + + + + + + + INTERNAL; not public API. Perform an action. + + + + + The action being performed. + + + + + + The connection for which this action was triggered. + + + + + + Properties of the connection, including service and path. + + + + + + Properties of the device, including type, path, interface, and state. + + + + + + diff --git a/callouts/org.freedesktop.nm_dispatcher.service b/callouts/org.freedesktop.nm_dispatcher.service new file mode 100644 index 000000000..57c57e2ad --- /dev/null +++ b/callouts/org.freedesktop.nm_dispatcher.service @@ -0,0 +1,5 @@ +[D-BUS Service] +Name=org.freedesktop.nm_dispatcher +Exec=/usr/libexec/nm-dispatcher.action +User=root + diff --git a/src/Makefile.am b/src/Makefile.am index 3638fd774..5eea6e159 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,7 +13,8 @@ INCLUDES = -I${top_srcdir} \ -I${top_srcdir}/src/vpn-manager \ -I${top_srcdir}/src/dhcp-manager \ -I${top_srcdir}/src/supplicant-manager \ - -I${top_srcdir}/libnm-util + -I${top_srcdir}/libnm-util \ + -I${top_srcdir}/callouts sbin_PROGRAMS = NetworkManager diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c index 419fcca76..c1e24e171 100644 --- a/src/NetworkManagerUtils.c +++ b/src/NetworkManagerUtils.c @@ -28,6 +28,9 @@ #include "nm-device.h" #include "nm-device-802-11-wireless.h" #include "nm-device-802-3-ethernet.h" +#include "nm-dbus-manager.h" +#include "nm-dispatcher-action.h" +#include "nm-dbus-glib-types.h" #include #include @@ -297,4 +300,140 @@ nm_utils_merge_ip4_config (NMIP4Config *ip4_config, NMSettingIP4Config *setting) } } +static void +nm_gvalue_destroy (gpointer data) +{ + GValue *value = (GValue *) data; + + g_value_unset (value); + g_slice_free (GValue, value); +} + +static GValue * +str_to_gvalue (const char *str) +{ + GValue *value; + + value = g_slice_new0 (GValue); + g_value_init (value, G_TYPE_STRING); + g_value_set_string (value, str); + return value; +} + +static GValue * +op_to_gvalue (const char *op) +{ + GValue *value; + + value = g_slice_new0 (GValue); + g_value_init (value, DBUS_TYPE_G_OBJECT_PATH); + g_value_set_boxed (value, op); + return value; +} + +static GValue * +uint_to_gvalue (guint32 val) +{ + GValue *value; + + value = g_slice_new0 (GValue); + g_value_init (value, G_TYPE_UINT); + g_value_set_uint (value, val); + return value; +} + +void +nm_utils_call_dispatcher (const char *action, + NMConnection *connection, + NMDevice *device, + const char *vpn_iface) +{ + NMDBusManager *dbus_mgr; + DBusGProxy *proxy; + DBusGConnection *g_connection; + GHashTable *connection_hash; + GHashTable *connection_props; + GHashTable *device_props; + + g_return_if_fail (action != NULL); + g_return_if_fail (NM_IS_DEVICE (device)); + + dbus_mgr = nm_dbus_manager_get (); + g_connection = nm_dbus_manager_get_connection (dbus_mgr); + proxy = dbus_g_proxy_new_for_name (g_connection, + NM_DISPATCHER_DBUS_SERVICE, + NM_DISPATCHER_DBUS_PATH, + NM_DISPATCHER_DBUS_IFACE); + if (!proxy) { + nm_warning ("Error: could not get dispatcher proxy!"); + g_object_unref (dbus_mgr); + return; + } + + if (connection) { + connection_hash = nm_connection_to_hash (connection); + + connection_props = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, nm_gvalue_destroy); + + /* Service name */ + if (nm_connection_get_scope (connection) == NM_CONNECTION_SCOPE_USER) { + g_hash_table_insert (connection_props, + NMD_CONNECTION_PROPS_SERVICE_NAME, + str_to_gvalue (NM_DBUS_SERVICE_USER_SETTINGS)); + } else if (nm_connection_get_scope (connection) == NM_CONNECTION_SCOPE_USER) { + g_hash_table_insert (connection_props, + NMD_CONNECTION_PROPS_SERVICE_NAME, + str_to_gvalue (NM_DBUS_SERVICE_SYSTEM_SETTINGS)); + } + + /* path */ + g_hash_table_insert (connection_props, + NMD_CONNECTION_PROPS_PATH, + op_to_gvalue (nm_connection_get_path (connection))); + } else { + connection_hash = g_hash_table_new (g_direct_hash, g_direct_equal); + connection_props = g_hash_table_new (g_direct_hash, g_direct_equal); + } + + device_props = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, nm_gvalue_destroy); + + /* interface */ + g_hash_table_insert (device_props, NMD_DEVICE_PROPS_INTERFACE, + str_to_gvalue (nm_device_get_iface (device))); + + /* IP interface */ + if (vpn_iface) { + g_hash_table_insert (device_props, NMD_DEVICE_PROPS_IP_INTERFACE, + str_to_gvalue (vpn_iface)); + } else if (nm_device_get_ip_iface (device)) { + g_hash_table_insert (device_props, NMD_DEVICE_PROPS_IP_INTERFACE, + str_to_gvalue (nm_device_get_ip_iface (device))); + } + + /* type */ + g_hash_table_insert (device_props, NMD_DEVICE_PROPS_TYPE, + uint_to_gvalue (nm_device_get_device_type (device))); + + /* state */ + g_hash_table_insert (device_props, NMD_DEVICE_PROPS_STATE, + uint_to_gvalue (nm_device_get_state (device))); + + g_hash_table_insert (device_props, NMD_DEVICE_PROPS_PATH, + op_to_gvalue (nm_device_get_udi (device))); + + dbus_g_proxy_call_no_reply (proxy, "Action", + G_TYPE_STRING, action, + DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, connection_hash, + DBUS_TYPE_G_MAP_OF_VARIANT, connection_props, + DBUS_TYPE_G_MAP_OF_VARIANT, device_props, + G_TYPE_INVALID); + + g_hash_table_destroy (connection_hash); + g_hash_table_destroy (connection_props); + g_hash_table_destroy (device_props); + g_object_unref (proxy); + g_object_unref (dbus_mgr); +} diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h index 3f2d6b698..2aa25daaa 100644 --- a/src/NetworkManagerUtils.h +++ b/src/NetworkManagerUtils.h @@ -25,11 +25,11 @@ #include #include #include -#include #include "nm-device.h" #include "nm-ip4-config.h" #include "nm-setting-ip4-config.h" +#include "nm-connection.h" gboolean nm_ethernet_address_is_valid (const struct ether_addr *test_addr); @@ -47,5 +47,10 @@ char *nm_ether_ntop (const struct ether_addr *mac); void nm_utils_merge_ip4_config (NMIP4Config *ip4_config, NMSettingIP4Config *setting); +void nm_utils_call_dispatcher (const char *action, + NMConnection *connection, + NMDevice *device, + const char *vpn_iface); + #endif diff --git a/src/nm-device.c b/src/nm-device.c index 4146a0685..13f94e4d2 100644 --- a/src/nm-device.c +++ b/src/nm-device.c @@ -1693,23 +1693,27 @@ failed_to_disconnected (gpointer user_data) void nm_device_state_changed (NMDevice *device, NMDeviceState state) { - const char *iface; + NMDevicePrivate *priv; NMDeviceState old_state; + NMActRequest *req; g_return_if_fail (NM_IS_DEVICE (device)); + priv = device->priv; - if (device->priv->state == state) + if (priv->state == state) return; - iface = nm_device_get_iface (device); - old_state = device->priv->state; - device->priv->state = state; + old_state = priv->state; + priv->state = state; - if (device->priv->failed_to_disconnected_id) { - g_source_remove (device->priv->failed_to_disconnected_id); - device->priv->failed_to_disconnected_id = 0; + if (priv->failed_to_disconnected_id) { + g_source_remove (priv->failed_to_disconnected_id); + priv->failed_to_disconnected_id = 0; } + /* Cache the activation request for the dispatcher */ + req = priv->act_request ? g_object_ref (priv->act_request) : NULL; + /* Handle the new state here; but anything that could trigger * another state change should be done below. */ @@ -1730,17 +1734,27 @@ nm_device_state_changed (NMDevice *device, NMDeviceState state) g_object_notify (G_OBJECT (device), NM_DEVICE_INTERFACE_STATE); g_signal_emit_by_name (device, "state-changed", state); + /* Post-process the event after internal notification */ + switch (state) { case NM_DEVICE_STATE_ACTIVATED: - nm_info ("Activation (%s) successful, device activated.", iface); + nm_info ("Activation (%s) successful, device activated.", nm_device_get_iface (device)); + nm_utils_call_dispatcher ("up", nm_act_request_get_connection (req), device, NULL); break; case NM_DEVICE_STATE_FAILED: nm_info ("Activation (%s) failed.", nm_device_get_iface (device)); - device->priv->failed_to_disconnected_id = g_idle_add (failed_to_disconnected, device); + priv->failed_to_disconnected_id = g_idle_add (failed_to_disconnected, device); break; default: break; } + + if (old_state == NM_DEVICE_STATE_ACTIVATED) + nm_utils_call_dispatcher ("down", nm_act_request_get_connection (req), device, NULL); + + /* Dispose of the cached activation request */ + if (req) + g_object_unref (req); } NMDeviceState diff --git a/src/vpn-manager/nm-vpn-connection.c b/src/vpn-manager/nm-vpn-connection.c index e972d4622..7c28f20ff 100644 --- a/src/vpn-manager/nm-vpn-connection.c +++ b/src/vpn-manager/nm-vpn-connection.c @@ -69,6 +69,7 @@ typedef struct { guint ipconfig_timeout; NMIP4Config *ip4_config; char *tundev; + char *tapdev; char *banner; } NMVPNConnectionPrivate; @@ -107,6 +108,7 @@ nm_vpn_connection_set_vpn_state (NMVPNConnection *connection, { NMVPNConnectionPrivate *priv; NMActiveConnectionState new_ac_state; + NMVPNConnectionState old_vpn_state; g_return_if_fail (NM_IS_VPN_CONNECTION (connection)); @@ -115,6 +117,7 @@ nm_vpn_connection_set_vpn_state (NMVPNConnection *connection, if (vpn_state == priv->vpn_state) return; + old_vpn_state = priv->vpn_state; priv->vpn_state = vpn_state; /* Set the NMActiveConnection state based on VPN state */ @@ -138,8 +141,36 @@ nm_vpn_connection_set_vpn_state (NMVPNConnection *connection, g_object_notify (G_OBJECT (connection), NM_ACTIVE_CONNECTION_STATE); } + /* The connection gets destroyed by the VPN manager when it enters the + * disconnected/failed state, but we need to keep it around for a bit + * to send out signals and handle the dispatcher. So ref it. + */ g_object_ref (connection); + g_signal_emit (connection, signals[VPN_STATE_CHANGED], 0, vpn_state, reason); + g_object_notify (G_OBJECT (connection), NM_VPN_CONNECTION_VPN_STATE); + + /* Call dispatcher after the event gets processed internally */ + switch (vpn_state) { + case NM_VPN_CONNECTION_STATE_ACTIVATED: + nm_utils_call_dispatcher ("vpn-up", + priv->connection, + priv->parent_dev, + priv->tapdev ? priv->tapdev : priv->tundev); + break; + case NM_VPN_CONNECTION_STATE_FAILED: + case NM_VPN_CONNECTION_STATE_DISCONNECTED: + if (old_vpn_state == NM_VPN_CONNECTION_STATE_ACTIVATED) { + nm_utils_call_dispatcher ("vpn-down", + priv->connection, + priv->parent_dev, + priv->tapdev ? priv->tapdev : priv->tundev); + } + break; + default: + break; + } + g_object_unref (connection); }