diff options
Diffstat (limited to 'src/nm-iodine-service.c')
-rw-r--r-- | src/nm-iodine-service.c | 672 |
1 files changed, 672 insertions, 0 deletions
diff --git a/src/nm-iodine-service.c b/src/nm-iodine-service.c new file mode 100644 index 0000000..4c97581 --- /dev/null +++ b/src/nm-iodine-service.c @@ -0,0 +1,672 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * NetworkManager iodine VPN connections + * + * 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 © 2012 Guido Günther <agx@sigxcpu.org> + * + * Based on network-manager-{openconnect,pptp} + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <linux/if_tun.h> +#include <net/if.h> +#include <pwd.h> +#include <grp.h> +#include <glib/gi18n.h> + +#include <nm-setting-vpn.h> +#include "nm-iodine-service.h" +#include "nm-utils.h" + +G_DEFINE_TYPE (NMIODINEPlugin, nm_iodine_plugin, NM_TYPE_VPN_PLUGIN) + +typedef struct { + GPid pid; + GHashTable *ip4config; +} NMIODINEPluginPrivate; + +#define NM_IODINE_PLUGIN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_IODINE_PLUGIN, NMIODINEPluginPrivate)) + +static const char *iodine_binary_paths[] = +{ + "/usr/bin/iodine", + "/usr/sbin/iodine", + "/usr/local/bin/iodine", + "/usr/local/sbin/iodine", + "/opt/bin/iodine", + "/opt/sbin/iodine", + NULL +}; + +#define NM_IODINE_HELPER_PATH LIBEXECDIR"/nm-iodine-service-iodine-helper" + +typedef struct { + const char *name; + GType type; + gint int_min; + gint int_max; +} ValidProperty; + +static ValidProperty valid_properties[] = { + { NM_IODINE_KEY_TOPDOMAIN, G_TYPE_STRING, 0, 0 }, + { NM_IODINE_KEY_NAMESERVER, G_TYPE_STRING, 0, 0 }, + { NM_IODINE_KEY_FRAGSIZE, G_TYPE_STRING, 0, 0 }, + { NULL, G_TYPE_NONE, 0, 0 } +}; + +static ValidProperty valid_secrets[] = { + { NM_IODINE_KEY_PASSWORD, G_TYPE_STRING, 0, 0 }, + { NULL, G_TYPE_NONE, 0, 0 } +}; + +typedef struct ValidateInfo { + ValidProperty *table; + GError **error; + gboolean have_items; +} ValidateInfo; + +static void +validate_one_property (const char *key, const char *value, gpointer user_data) +{ + ValidateInfo *info = (ValidateInfo *) user_data; + int i; + + if (*(info->error)) + return; + + info->have_items = TRUE; + + /* 'name' is the setting name; always allowed but unused */ + if (!strcmp (key, NM_SETTING_NAME)) + return; + + for (i = 0; info->table[i].name; i++) { + ValidProperty prop = info->table[i]; + long int tmp; + + if (strcmp (prop.name, key)) + continue; + + switch (prop.type) { + case G_TYPE_STRING: + return; /* valid */ + case G_TYPE_INT: + errno = 0; + tmp = strtol (value, NULL, 10); + if (errno == 0 && tmp >= prop.int_min && tmp <= prop.int_max) + return; /* valid */ + + g_set_error (info->error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + _("invalid integer property '%s' or out of range " + "[%d -> %d]"), + key, prop.int_min, prop.int_max); + break; + case G_TYPE_BOOLEAN: + if (!strcmp (value, "yes") || !strcmp (value, "no")) + return; /* valid */ + + g_set_error (info->error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + _("invalid boolean property '%s' (not yes or no)"), + key); + break; + default: + g_set_error (info->error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + _("unhandled property '%s' type %s"), + key, g_type_name (prop.type)); + break; + } + } + + /* Did not find the property from valid_properties or the type did not + match */ + if (!info->table[i].name && strncmp(key, "form:", 5)) { + g_warning ("property '%s' unknown", key); + if (0) + g_set_error (info->error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + _("property '%s' invalid or not supported"), + key); + } +} + +static gboolean +nm_iodine_properties_validate (NMSettingVPN *s_vpn, GError **error) +{ + ValidateInfo info = { &valid_properties[0], error, FALSE }; + + nm_setting_vpn_foreach_data_item (s_vpn, validate_one_property, &info); + if (!info.have_items) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + "%s", + _("No VPN configuration options.")); + return FALSE; + } + + return *error ? FALSE : TRUE; +} + + +static gboolean +nm_iodine_secrets_validate (NMSettingVPN *s_vpn, GError **error) +{ + ValidateInfo info = { &valid_secrets[0], error, FALSE }; + + nm_setting_vpn_foreach_secret (s_vpn, validate_one_property, &info); + if (!info.have_items) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + "%s", + _("No VPN secrets!")); + return FALSE; + } + + return *error ? FALSE : TRUE; +} + +static GValue * +str_to_gvalue (const char *str, gboolean try_convert) +{ + GValue *val; + + /* Empty */ + if (!str || strlen (str) < 1) + return NULL; + + if (!g_utf8_validate (str, -1, NULL)) { + if (try_convert && !(str = g_convert (str, + -1, + "ISO-8859-1", + "UTF-8", + NULL, + NULL, + NULL))) + str = g_convert (str, -1, "C", "UTF-8", NULL, NULL, NULL); + if (!str) + /* Invalid */ + return NULL; + } + + val = g_slice_new0 (GValue); + g_value_init (val, G_TYPE_STRING); + g_value_set_string (val, str); + return val; +} + +static GValue * +uint_to_gvalue (guint32 num) +{ + GValue *val; + + if (num == 0) + return NULL; + + val = g_slice_new0 (GValue); + g_value_init (val, G_TYPE_UINT); + g_value_set_uint (val, num); + + return val; +} + +static GValue * +addr_to_gvalue (const char *str) +{ + struct in_addr temp_addr; + + /* Empty */ + if (!str || strlen (str) < 1) + return NULL; + + if (inet_pton (AF_INET, str, &temp_addr) <= 0) + return NULL; + + return uint_to_gvalue (temp_addr.s_addr); +} + +static void +value_destroy (gpointer data) +{ + GValue *val = (GValue *) data; + + g_value_unset (val); + g_slice_free (GValue, val); +} + +static gboolean +iodine_parse_stderr_line (const char* line, GHashTable *ip4config) +{ + gchar **split; + GValue *val; + gint len; + gint ret = TRUE; + + split = g_strsplit (line, " ", 0); + len = g_strv_length (split); + if (len < 2) + goto out; + + if (g_str_has_prefix(line, "Server tunnel IP is ")) { + g_debug("PTP address: %s", split[len-1]); + val = addr_to_gvalue (split[len-1]); + if (val) + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_PTP, + val); + val = addr_to_gvalue (split[len-1]); + if (val) + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, + val); + } else if (g_str_has_prefix(line, "Sending DNS queries for ")) { + g_debug("External gw: %s", split[len-1]); + val = addr_to_gvalue (split[len-1]); + if (val) + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_EXT_GATEWAY, + val); + } else if (g_str_has_prefix(line, "Sending raw traffic directly to ")) { + /* If the DNS server is directly reachable we need to set it + as external gateway overwriting the above valus */ + g_debug("Overwrite ext. gw. address: %s", split[len-1]); + val = addr_to_gvalue (split[len-1]); + if (val) + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_EXT_GATEWAY, + val); + } else if (g_str_has_prefix(line, "Setting IP of dns")) { + g_debug("Address: %s", split[len-1]); + val = addr_to_gvalue (split[len-1]); + if (val) + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, + val); + } else if (g_str_has_prefix(line, "Setting MTU of ")) { + g_debug("MTU: %s", split[len-1]); + val = addr_to_gvalue (split[len-1]); + if (val) + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_MTU, + val); + } else if (g_str_has_prefix(line, "Opened dns")) { + g_debug("Interface: %s", split[len-1]); + val = str_to_gvalue (split[len-1], FALSE); + if (val) + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_TUNDEV, + val); + } else if (g_str_has_prefix(line, + "Connection setup complete, " + "transmitting data.")) { + val = uint_to_gvalue(27); + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, + val); + ret = FALSE; + } else + g_debug("%s", line); + +out: + g_strfreev(split); + return ret; +} + + +static gboolean +iodine_stderr_cb (GIOChannel *source, GIOCondition condition, gpointer plugin) +{ + GIOStatus status; + GError *err = NULL; + gchar *line; + gint ret, l; + NMIODINEPluginPrivate *priv = NM_IODINE_PLUGIN_GET_PRIVATE (plugin); + + status = g_io_channel_read_line (source, &line, NULL, NULL, &err); + if (status != G_IO_STATUS_NORMAL) { + g_warning ("Fetching data failed: %s", err->message); + return FALSE; + } + + l = strlen(line); + if (l) + line[l-1] = '\0'; + + ret = iodine_parse_stderr_line(line, priv->ip4config); + if (!ret) { + g_debug("Parsing done, sending IP4 config"); + nm_vpn_plugin_set_ip4_config(plugin, priv->ip4config); + + g_hash_table_destroy (priv->ip4config); + priv->ip4config = NULL; + } + g_free (line); + return TRUE; +} + + +static void +iodine_watch_cb (GPid pid, gint status, gpointer user_data) +{ + NMIODINEPlugin *plugin = NM_IODINE_PLUGIN (user_data); + NMIODINEPluginPrivate *priv = NM_IODINE_PLUGIN_GET_PRIVATE (plugin); + guint error = 0; + + if (WIFEXITED (status)) { + error = WEXITSTATUS (status); + if (error != 0) + g_warning ("iodine exited with error code %d", error); + } + else if (WIFSTOPPED (status)) + g_warning ("iodine stopped unexpectedly with signal %d", + WSTOPSIG (status)); + else if (WIFSIGNALED (status)) + g_warning ("iodine died with signal %d", WTERMSIG (status)); + else + g_warning ("iodine died from an unknown cause"); + + /* Reap child if needed. */ + waitpid (priv->pid, NULL, WNOHANG); + priv->pid = 0; + + /* Must be after data->state is set since signals use data->state */ + if (error) { + nm_vpn_plugin_failure (NM_VPN_PLUGIN (plugin), + NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED); + } + + nm_vpn_plugin_set_state (NM_VPN_PLUGIN (plugin), + NM_VPN_SERVICE_STATE_STOPPED); +} + +static gboolean +has_user(const char* user) +{ + return (getpwnam(user) == NULL) ? FALSE : TRUE; +} + +static gint +nm_iodine_start_iodine_binary(NMIODINEPlugin *plugin, + NMSettingVPN *s_vpn, + GError **error) +{ + GPid pid; + const char **iodine_binary = NULL; + GPtrArray *iodine_argv; + GSource *iodine_watch; + GIOChannel *stderr_channel; + gint stdin_fd, stderr_fd; + const char *props_topdomain, *props_fragsize, *props_nameserver, *passwd; + + /* Find iodine */ + iodine_binary = iodine_binary_paths; + while (*iodine_binary != NULL) { + if (g_file_test (*iodine_binary, G_FILE_TEST_EXISTS)) + break; + iodine_binary++; + } + + if (!*iodine_binary) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_LAUNCH_FAILED, + "%s", + _("Could not find iodine binary.")); + return -1; + } + + props_fragsize = nm_setting_vpn_get_data_item (s_vpn, + NM_IODINE_KEY_FRAGSIZE); + props_nameserver = nm_setting_vpn_get_data_item (s_vpn, + NM_IODINE_KEY_NAMESERVER); + props_topdomain = nm_setting_vpn_get_data_item (s_vpn, + NM_IODINE_KEY_TOPDOMAIN); + + passwd = nm_setting_vpn_get_secret (s_vpn, NM_IODINE_KEY_PASSWORD); + + iodine_argv = g_ptr_array_new (); + g_ptr_array_add (iodine_argv, (gpointer) (*iodine_binary)); + /* Run in foreground */ + g_ptr_array_add (iodine_argv, (gpointer) "-f"); + + if (props_fragsize && strlen(props_fragsize)) { + g_ptr_array_add (iodine_argv, (gpointer) "-m"); + g_ptr_array_add (iodine_argv, (gpointer) props_fragsize); + } + + if (passwd && strlen(passwd)) { + g_ptr_array_add (iodine_argv, (gpointer) "-P"); + g_ptr_array_add (iodine_argv, (gpointer) passwd); + } + + if (has_user(NM_IODINE_USER)) { + g_ptr_array_add (iodine_argv, (gpointer) "-u"); + g_ptr_array_add (iodine_argv, (gpointer) NM_IODINE_USER); + } else + g_warning("Running as root user"); + + if (props_nameserver && strlen(props_nameserver)) + g_ptr_array_add (iodine_argv, (gpointer) props_nameserver); + + if (props_topdomain && strlen(props_topdomain)) + g_ptr_array_add (iodine_argv, (gpointer) props_topdomain); + + g_ptr_array_add (iodine_argv, NULL); + + if (!g_spawn_async_with_pipes (NULL, (char **) iodine_argv->pdata, NULL, + G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, + &pid, &stdin_fd, NULL, &stderr_fd, error)) { + g_ptr_array_free (iodine_argv, TRUE); + g_warning ("iodine failed to start. error: '%s'", (*error)->message); + return -1; + } + g_ptr_array_free (iodine_argv, TRUE); + + g_message ("iodine started with pid %d", pid); + close(stdin_fd); + + stderr_channel = g_io_channel_unix_new (stderr_fd); + g_io_add_watch(stderr_channel, + G_IO_IN, + iodine_stderr_cb, + plugin); + + NM_IODINE_PLUGIN_GET_PRIVATE (plugin)->pid = pid; + iodine_watch = g_child_watch_source_new (pid); + g_source_set_callback (iodine_watch, + (GSourceFunc) iodine_watch_cb, + plugin, + NULL); + g_source_attach (iodine_watch, NULL); + g_source_unref (iodine_watch); + + return 0; +} + +static gboolean +real_connect (NMVPNPlugin *plugin, + NMConnection *connection, + GError **error) +{ + NMSettingVPN *s_vpn; + gint iodine_fd = -1; + + s_vpn = NM_SETTING_VPN (nm_connection_get_setting (connection, + NM_TYPE_SETTING_VPN)); + g_assert (s_vpn); + if (!nm_iodine_properties_validate (s_vpn, error)) + goto out; + + if (!nm_iodine_secrets_validate (s_vpn, error)) + goto out; + + iodine_fd = nm_iodine_start_iodine_binary (NM_IODINE_PLUGIN (plugin), + s_vpn, error); + if (!iodine_fd) + return TRUE; + + out: + return FALSE; +} + +static gboolean +real_need_secrets (NMVPNPlugin *plugin, + + NMConnection *connection, + char **setting_name, + GError **error) +{ + NMSettingVPN *s_vpn; + + g_return_val_if_fail (NM_IS_VPN_PLUGIN (plugin), FALSE); + g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + + s_vpn = NM_SETTING_VPN (nm_connection_get_setting (connection, + NM_TYPE_SETTING_VPN)); + if (!s_vpn) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_CONNECTION_INVALID, + "%s", + "Could not process the request because the VPN" + "connection settings were invalid."); + return FALSE; + } + + if (!nm_setting_vpn_get_secret (s_vpn, NM_IODINE_KEY_PASSWORD)) { + *setting_name = NM_SETTING_VPN_SETTING_NAME; + return TRUE; + } + + return FALSE; +} + +static gboolean +ensure_killed (gpointer data) +{ + int pid = GPOINTER_TO_INT (data); + + if (kill (pid, 0) == 0) + kill (pid, SIGKILL); + + return FALSE; +} + +static gboolean +real_disconnect (NMVPNPlugin *plugin, + GError **err) +{ + NMIODINEPluginPrivate *priv = NM_IODINE_PLUGIN_GET_PRIVATE (plugin); + + if (priv->pid) { + if (kill (priv->pid, SIGTERM) == 0) + g_timeout_add (2000, ensure_killed, GINT_TO_POINTER (priv->pid)); + else + kill (priv->pid, SIGKILL); + + g_message ("Terminated iodine daemon with PID %d.", priv->pid); + priv->pid = 0; + } + + return TRUE; +} + +static void +nm_iodine_plugin_init (NMIODINEPlugin *plugin) +{ + NMIODINEPluginPrivate *priv = NM_IODINE_PLUGIN_GET_PRIVATE (plugin); + + priv->ip4config = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + value_destroy); +} + +static void +nm_iodine_plugin_class_init (NMIODINEPluginClass *iodine_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (iodine_class); + NMVPNPluginClass *parent_class = NM_VPN_PLUGIN_CLASS (iodine_class); + + g_type_class_add_private (object_class, sizeof (NMIODINEPluginPrivate)); + + /* virtual methods */ + parent_class->connect = real_connect; + parent_class->need_secrets = real_need_secrets; + parent_class->disconnect = real_disconnect; +} + +NMIODINEPlugin * +nm_iodine_plugin_new (void) +{ + return (NMIODINEPlugin *) g_object_new (NM_TYPE_IODINE_PLUGIN, + NM_VPN_PLUGIN_DBUS_SERVICE_NAME, + NM_DBUS_SERVICE_IODINE, + NULL); +} + +static void +quit_mainloop (NMIODINEPlugin *plugin, gpointer user_data) +{ + g_main_loop_quit ((GMainLoop *) user_data); +} + +int main (int argc, char *argv[]) +{ + NMIODINEPlugin *plugin; + GMainLoop *main_loop; + + g_type_init (); + + plugin = nm_iodine_plugin_new (); + if (!plugin) + exit (EXIT_FAILURE); + + main_loop = g_main_loop_new (NULL, FALSE); + + g_signal_connect (plugin, "quit", + G_CALLBACK (quit_mainloop), + main_loop); + + g_main_loop_run (main_loop); + + g_main_loop_unref (main_loop); + g_object_unref (plugin); + + exit (EXIT_SUCCESS); +} |