[network-manager-openvpn/dcbw/config-parsing: 1/2] core: implement testcases for openvpn config parsing



commit 49c735c1438fa7d9e8327550c4d9593218b874ce
Author: Dan Williams <dcbw redhat com>
Date:   Thu Dec 4 14:41:19 2014 -0600

    core: implement testcases for openvpn config parsing

 configure.ac                   |    1 +
 src/Makefile.am                |    4 +
 src/helper-config.c            |  671 ++++++++++++++++++++++++++++++++++++++++
 src/helper-config.h            |   40 +++
 src/tests/Makefile.am          |   22 ++
 src/tests/test-helper-config.c |  390 +++++++++++++++++++++++
 6 files changed, 1128 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 353b9b8..8616600 100644
--- a/configure.ac
+++ b/configure.ac
@@ -125,6 +125,7 @@ fi
 AC_CONFIG_FILES([
 Makefile
 src/Makefile
+src/tests/Makefile
 common/Makefile
 auth-dialog/Makefile
 properties/Makefile
diff --git a/src/Makefile.am b/src/Makefile.am
index 700f0d9..544974c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,3 +1,5 @@
+SUBDIRS= . tests
+
 AM_CPPFLAGS = \
        $(GLIB_CFLAGS) \
        $(DBUS_CFLAGS) \
@@ -26,6 +28,8 @@ nm_openvpn_service_LDADD = \
         $(top_builddir)/common/libnm-openvpn-common.la
 
 nm_openvpn_service_openvpn_helper_SOURCES = \
+       helper-config.c \
+       helper-config.h \
        nm-openvpn-service-openvpn-helper.c
 
 nm_openvpn_service_openvpn_helper_LDADD = \
diff --git a/src/helper-config.c b/src/helper-config.c
new file mode 100644
index 0000000..2d30b49
--- /dev/null
+++ b/src/helper-config.c
@@ -0,0 +1,671 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-openvpn-service-openvpn-helper - helper called after OpenVPN established
+ * a connection, uses DBUS to send information back to nm-openvpn-service
+ *
+ * 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 2005 - 2014 Red Hat, Inc.
+ */
+
+#include "config.h"
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "nm-utils.h"
+
+#include "helper-config.h"
+#include "nm-openvpn-service.h"
+
+static GValue *
+str_to_gvalue (const char *str, gboolean try_convert, GError **error)
+{
+       GValue *val;
+       char *converted = NULL;
+
+       /* Empty */
+       if (!str || !str[0]) {
+               g_set_error_literal (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
+                                    "expected non-empty string");
+               return NULL;
+       }
+
+       if (!g_utf8_validate (str, -1, NULL)) {
+               if (try_convert) {
+                       converted = g_convert (str, -1, "ISO-8859-1", "UTF-8", NULL, NULL, NULL);
+                       if (!converted)
+                               converted = g_convert (str, -1, "C", "UTF-8", NULL, NULL, NULL);
+                       if (!converted) {
+                               g_set_error_literal (error, NM_VPN_PLUGIN_ERROR, 
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
+                                                    "failed to convert non-UTF-8 string");
+                               return NULL;
+                       }
+                       str = converted;
+               } else {
+                       g_set_error_literal (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
+                                            "string not UTF-8");
+                       return NULL;
+               }
+       }
+
+       val = g_slice_new0 (GValue);
+       g_value_init (val, G_TYPE_STRING);
+       g_value_set_string (val, str);
+       g_free (converted);
+
+       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 *
+bool_to_gvalue (gboolean b)
+{
+       GValue *val;
+
+       val = g_slice_new0 (GValue);
+       g_value_init (val, G_TYPE_BOOLEAN);
+       g_value_set_boolean (val, b);
+       return val;
+}
+
+static GValue *
+addr4_to_gvalue (const char *str, GError **error)
+{
+       struct in_addr  temp_addr;
+       GValue *val;
+
+       /* Empty */
+       if (!str || !str[0]) {
+               g_set_error_literal (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
+                                    "expected IPv4 address, not empty string");
+               return NULL;
+       }
+
+       if (inet_pton (AF_INET, str, &temp_addr) <= 0) {
+               g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
+                            "failed to convert IPv4 address '%s'", str);
+               return NULL;
+       }
+
+       val = g_slice_new0 (GValue);
+       g_value_init (val, G_TYPE_UINT);
+       g_value_set_uint (val, temp_addr.s_addr);
+
+       return val;
+}
+
+static GValue *
+parse_addr4_list (GValue *value_array, const char *str)
+{
+       char **split;
+       int i;
+       struct in_addr temp_addr;
+       GArray *array;
+
+       /* Empty */
+       if (!str || !str[0])
+               return value_array;
+
+       if (value_array)
+               array = (GArray *) g_value_get_boxed (value_array);
+       else
+               array = g_array_new (FALSE, FALSE, sizeof (guint));
+
+       split = g_strsplit (str, " ", -1);
+       for (i = 0; split[i]; i++) {
+               if (inet_pton (AF_INET, split[i], &temp_addr) > 0)
+                       g_array_append_val (array, temp_addr.s_addr);
+       }
+
+       g_strfreev (split);
+
+       if (!value_array && array->len > 0) {
+               value_array = g_slice_new0 (GValue);
+               g_value_init (value_array, DBUS_TYPE_G_UINT_ARRAY);
+               g_value_set_boxed (value_array, array);
+       }
+       if (!value_array)
+               g_array_free (array, TRUE);
+
+       return value_array;
+}
+
+static GValue *
+addr6_to_gvalue (const char *str, GError **error)
+{
+       struct in6_addr temp_addr;
+       GValue *val;
+       GByteArray *ba;
+
+       /* Empty */
+       if (!str || !str[0]) {
+               g_set_error_literal (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
+                                    "expected IPv6 address, not empty string");
+               return NULL;
+       }
+
+       if (inet_pton (AF_INET6, str, &temp_addr) <= 0) {
+               g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
+                            "failed to convert IPv6 address '%s'", str);
+               return NULL;
+       }
+
+       val = g_slice_new0 (GValue);
+       g_value_init (val, DBUS_TYPE_G_UCHAR_ARRAY);
+       ba = g_byte_array_new ();
+       g_byte_array_append (ba, (guint8 *) &temp_addr, sizeof (temp_addr));
+       g_value_take_boxed (val, ba);
+       return val;
+}
+
+static inline gboolean
+is_domain_valid (const char *str)
+{
+       return (str && (strlen(str) >= 1) && (strlen(str) <= 255));
+}
+
+#define BUFLEN 256
+
+static GValue *
+get_ip4_routes (void)
+{
+       GValue *value = NULL;
+       GPtrArray *routes;
+       char *tmp;
+       int i;
+
+       routes = g_ptr_array_new ();
+
+       for (i = 1; i < 256; i++) {
+               GArray *array;
+               char buf[BUFLEN];
+               struct in_addr network;
+               struct in_addr netmask;
+               struct in_addr gateway = { 0, };
+               guint32 prefix, metric = 0;
+
+               snprintf (buf, BUFLEN, "route_network_%d", i);
+               tmp = getenv (buf);
+               if (!tmp || !tmp[0])
+                       break;
+
+               if (inet_pton (AF_INET, tmp, &network) <= 0) {
+                       g_warning ("Ignoring invalid static route address '%s'", tmp ? tmp : "NULL");
+                       continue;
+               }
+
+               snprintf (buf, BUFLEN, "route_netmask_%d", i);
+               tmp = getenv (buf);
+               if (!tmp || inet_pton (AF_INET, tmp, &netmask) <= 0) {
+                       g_warning ("Ignoring invalid static route netmask '%s'", tmp ? tmp : "NULL");
+                       continue;
+               }
+
+               snprintf (buf, BUFLEN, "route_gateway_%d", i);
+               tmp = getenv (buf);
+               /* gateway can be missing */
+               if (tmp && (inet_pton (AF_INET, tmp, &gateway) <= 0)) {
+                       g_warning ("Ignoring invalid static route gateway '%s'", tmp ? tmp : "NULL");
+                       continue;
+               }
+
+               snprintf (buf, BUFLEN, "route_metric_%d", i);
+               tmp = getenv (buf);
+               /* metric can be missing */
+               if (tmp && tmp[0]) {
+                       long int tmp_metric;
+
+                       errno = 0;
+                       tmp_metric = strtol (tmp, NULL, 10);
+                       if (errno || tmp_metric < 0 || tmp_metric > G_MAXUINT32) {
+                               g_warning ("Ignoring invalid static route metric '%s'", tmp);
+                               continue;
+                       }
+                       metric = (guint32) tmp_metric;
+               }
+
+               array = g_array_sized_new (FALSE, TRUE, sizeof (guint32), 4);
+               g_array_append_val (array, network.s_addr);
+               prefix = nm_utils_ip4_netmask_to_prefix (netmask.s_addr);
+               g_array_append_val (array, prefix);
+               g_array_append_val (array, gateway.s_addr);
+               g_array_append_val (array, metric);
+               g_ptr_array_add (routes, array);
+       }
+
+       if (routes->len > 0) {
+               value = g_new0 (GValue, 1);
+               g_value_init (value, DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT);
+               g_value_take_boxed (value, routes);
+       } else
+               g_ptr_array_free (routes, TRUE);
+
+       return value;
+}
+
+static GValue *
+get_ip6_routes (void)
+{
+       GValue *value = NULL;
+       GSList *routes;
+       char *tmp;
+       int i;
+
+       routes = NULL;
+
+       for (i = 1; i < 256; i++) {
+               NMIP6Route *route;
+               char buf[BUFLEN];
+               struct in6_addr network, gateway;
+               guint32 prefix;
+               gchar **dest_prefix;
+
+               snprintf (buf, BUFLEN, "route_ipv6_network_%d", i);
+               tmp = getenv (buf);
+               if (!tmp || !tmp[0])
+                       break;
+
+               /* Split network string in "dest/prefix" format */
+               dest_prefix = g_strsplit (tmp, "/", 2);
+
+               tmp = dest_prefix[0];
+               if (inet_pton (AF_INET6, tmp, &network) <= 0) {
+                       g_warning ("Ignoring invalid static route address '%s'", tmp ? tmp : "NULL");
+                       g_strfreev (dest_prefix);
+                       continue;
+               }
+
+               tmp = dest_prefix[1];
+               if (tmp) {
+                       long int tmp_prefix;
+
+                       errno = 0;
+                       tmp_prefix = strtol (tmp, NULL, 10);
+                       if (errno || tmp_prefix <= 0 || tmp_prefix > 128) {
+                               g_warning ("Ignoring invalid static route prefix '%s'", tmp ? tmp : "NULL");
+                               g_strfreev (dest_prefix);
+                               continue;
+                       }
+                       prefix = (guint32) tmp_prefix;
+               } else {
+                       g_warning ("Ignoring static route %d with no prefix length", i);
+                       g_strfreev (dest_prefix);
+                       continue;
+               }
+               g_strfreev (dest_prefix);
+
+               snprintf (buf, BUFLEN, "route_ipv6_gateway_%d", i);
+               tmp = getenv (buf);
+               /* gateway can be missing */
+               if (tmp && (inet_pton (AF_INET6, tmp, &gateway) <= 0)) {
+                       g_warning ("Ignoring invalid static route gateway '%s'", tmp ? tmp : "NULL");
+                       continue;
+               }
+
+               route = nm_ip6_route_new ();
+               nm_ip6_route_set_dest (route, &network);
+               nm_ip6_route_set_prefix (route, prefix);
+               nm_ip6_route_set_next_hop (route, &gateway);
+
+               routes = g_slist_append (routes, route);
+       }
+
+       if (routes) {
+               GSList *iter;
+
+               value = g_slice_new0 (GValue);
+               g_value_init (value, DBUS_TYPE_G_ARRAY_OF_IP6_ROUTE);
+               nm_utils_ip6_routes_to_gvalue (routes, value);
+
+               for (iter = routes; iter; iter = iter->next)
+                       nm_ip6_route_unref (iter->data);
+               g_slist_free (routes);
+       }
+
+       return value;
+}
+
+static GValue *
+trusted_remote_to_gvalue (GError **error)
+{
+       const char *tmp;
+       GValue *val = NULL;
+       const char *p;
+       gboolean is_name = FALSE;
+
+       tmp = getenv ("trusted_ip6");
+       if (tmp) {
+               val = addr6_to_gvalue (tmp, error);
+               if (!val)
+                       g_prefix_error (error, "%s: failed to convert VPN gateway address (trusted_ip6): ", 
__func__);
+               return val;
+       }
+
+       tmp = getenv ("trusted_ip");
+       if (!tmp)
+               tmp = getenv ("remote_1");
+       if (!tmp) {
+               g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
+                            "%s: did not receive remote gateway address (trusted_ip or remote_1)", __func__);
+               return NULL;
+       }
+
+       /* Check if it seems to be a hostname */
+       p = tmp;
+       while (*p) {
+               if (*p != '.' && !isdigit (*p)) {
+                       is_name = TRUE;
+                       break;
+               }
+               p++;
+       }
+
+       /* Resolve a hostname if required. Only look for IPv4 addresses */
+       if (is_name) {
+               struct in_addr addr;
+               struct addrinfo hints;
+               struct addrinfo *result = NULL, *rp;
+               int err;
+
+               addr.s_addr = 0;
+               memset (&hints, 0, sizeof (hints));
+
+               hints.ai_family = AF_INET;
+               hints.ai_flags = AI_ADDRCONFIG;
+               err = getaddrinfo (tmp, NULL, &hints, &result);
+               if (err != 0) {
+                       g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
+                                    "%s: failed to look up VPN gateway address '%s' (%d)",
+                                    __func__, tmp, err);
+                       return NULL;
+               }
+
+               /* FIXME: so what if the name resolves to multiple IP addresses?  We
+                * don't know which one pptp decided to use so we could end up using a
+                * different one here, and the VPN just won't work.
+                */
+               for (rp = result; rp; rp = rp->ai_next) {
+                       if (   (rp->ai_family == AF_INET)
+                           && (rp->ai_addrlen == sizeof (struct sockaddr_in))) {
+                               struct sockaddr_in *inptr = (struct sockaddr_in *) rp->ai_addr;
+
+                               memcpy (&addr, &(inptr->sin_addr), sizeof (struct in_addr));
+                               break;
+                       }
+               }
+
+               freeaddrinfo (result);
+               if (addr.s_addr != 0)
+                       return uint_to_gvalue (addr.s_addr);
+               else {
+                       g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
+                                    "%s: failed to convert or look up VPN gateway address '%s'",
+                                    __func__, tmp);
+                       return NULL;
+               }
+       } else {
+               val = addr4_to_gvalue (tmp, error);
+               if (val == NULL) {
+                       g_prefix_error (error, "%s: failed to convert VPN gateway address: ", __func__);
+                       return NULL;
+               }
+       }
+
+       return val;
+}
+
+gboolean
+helper_generate_config (const char **args,
+                        gboolean is_tapdev,
+                        gboolean is_restart,
+                        GHashTable **out_config,
+                        GHashTable **out_ip4_config,
+                        GHashTable **out_ip6_config,
+                        GError **error)
+{
+       GHashTable *config = NULL, *ip4config = NULL, *ip6config = NULL;
+       const char *tmp;
+       GValue *val;
+       int i;
+       GValue *dns_list = NULL;
+       GValue *nbns_list = NULL;
+       GPtrArray *dns_domains = NULL;
+       struct in_addr temp_addr;
+
+       config = g_hash_table_new (g_str_hash, g_str_equal);
+       ip4config = g_hash_table_new (g_str_hash, g_str_equal);
+       ip6config = g_hash_table_new (g_str_hash, g_str_equal);
+
+       /* External world-visible VPN gateway */
+       val = trusted_remote_to_gvalue (error);
+       if (!val)
+               return FALSE;
+       g_hash_table_insert (config, NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY, val);
+
+       /* Internal VPN subnet gateway */
+       tmp = getenv ("route_vpn_gateway");
+       val = addr4_to_gvalue (tmp, NULL);
+       if (val)
+               g_hash_table_insert (ip4config, NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, val);
+       else {
+               val = addr6_to_gvalue (tmp, NULL);
+               if (val)
+                       g_hash_table_insert (ip6config, NM_VPN_PLUGIN_IP6_CONFIG_INT_GATEWAY, val);
+       }
+
+       /* VPN device */
+       tmp = getenv ("dev");
+       val = str_to_gvalue (tmp, FALSE, error);
+       if (!val) {
+               g_prefix_error (error, "%s: failed to parse 'dev': ", __func__);
+               return FALSE;
+       }
+       g_hash_table_insert (config, NM_VPN_PLUGIN_CONFIG_TUNDEV, val);
+
+       /* IPv4 address */
+       tmp = getenv ("ifconfig_local");
+       if (!tmp && is_restart)
+               tmp = args[4];
+       if (tmp && tmp[0]) {
+               val = addr4_to_gvalue (tmp, error);
+               if (!val) {
+                       g_prefix_error (error, "%s: failed to parse 'ifconfig_local': ", __func__);
+                       return FALSE;
+               }
+               g_hash_table_insert (ip4config, NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, val);
+       }
+
+       /* PTP address; for vpnc PTP address == internal IP4 address */
+       tmp = getenv ("ifconfig_remote");
+       if (!tmp && is_restart)
+               tmp = args[5];
+       val = addr4_to_gvalue (tmp, NULL);
+       if (val) {
+               /* Sigh.  Openvpn added 'topology' stuff in 2.1 that changes the meaning
+                * of the ifconfig bits without actually telling you what they are
+                * supposed to mean; basically relying on specific 'ifconfig' behavior.
+                */
+               if (tmp && !strncmp (tmp, "255.", 4)) {
+                       guint32 addr;
+
+                       /* probably a netmask, not a PTP address; topology == subnet */
+                       addr = g_value_get_uint (val);
+                       g_value_set_uint (val, nm_utils_ip4_netmask_to_prefix (addr));
+                       g_hash_table_insert (ip4config, NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, val);
+               } else
+                       g_hash_table_insert (ip4config, NM_VPN_PLUGIN_IP4_CONFIG_PTP, val);
+       }
+
+       /* Netmask
+        *
+        * Either TAP or TUN modes can have an arbitrary netmask in newer versions
+        * of openvpn, while in older versions only TAP mode would.  So accept a
+        * netmask if passed, otherwise default to /32 for TUN devices since they
+        * are usually point-to-point.
+        */
+       tmp = getenv ("ifconfig_netmask");
+       if (tmp && inet_pton (AF_INET, tmp, &temp_addr) > 0) {
+               val = g_slice_new0 (GValue);
+               g_value_init (val, G_TYPE_UINT);
+               g_value_set_uint (val, nm_utils_ip4_netmask_to_prefix (temp_addr.s_addr));
+               g_hash_table_insert (ip4config, NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, val);
+       } else if (!is_tapdev) {
+               if (!g_hash_table_lookup (ip4config, NM_VPN_PLUGIN_IP4_CONFIG_PREFIX)) {
+                       val = g_slice_new0 (GValue);
+                       g_value_init (val, G_TYPE_UINT);
+                       g_value_set_uint (val, 32);
+                       g_hash_table_insert (ip4config, NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, val);
+               }
+       } else {
+               g_set_error_literal (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
+                                    "missing or invalid netmask/prefix");
+               return FALSE;
+       }
+
+       val = get_ip4_routes ();
+       if (val)
+               g_hash_table_insert (ip4config, NM_VPN_PLUGIN_IP4_CONFIG_ROUTES, val);
+
+       /* IPv6 address */
+       tmp = getenv ("ifconfig_ipv6_local");
+       if (tmp && tmp[0]) {
+               val = addr6_to_gvalue (tmp, error);
+               if (!val) {
+                       g_prefix_error (error, "%s: failed to parse 'ifconfig_ipv6_local': ", __func__);
+                       return FALSE;
+               }
+               g_hash_table_insert (ip6config, NM_VPN_PLUGIN_IP6_CONFIG_ADDRESS, val);
+       }
+
+       /* IPv6 remote address */
+       tmp = getenv ("ifconfig_ipv6_remote");
+       if (tmp && tmp[0]) {
+               val = addr6_to_gvalue (tmp, error);
+               if (!val) {
+                       g_prefix_error (error, "%s: failed to parse 'ifconfig_ipv6_remote': ", __func__);
+                       return FALSE;
+               }
+               g_hash_table_insert (ip6config, NM_VPN_PLUGIN_IP6_CONFIG_PTP, val);
+       }
+
+       /* IPv6 netbits */
+       tmp = getenv ("ifconfig_ipv6_netbits");
+       if (tmp && tmp[0]) {
+               long int netbits;
+
+               errno = 0;
+               netbits = strtol (tmp, NULL, 10);
+               if (errno || netbits < 0 || netbits > 128) {
+                       g_warning ("Ignoring invalid prefix '%s'", tmp);
+               } else {
+                       val = uint_to_gvalue ((guint32) netbits);
+                       g_hash_table_insert (ip6config, NM_VPN_PLUGIN_IP6_CONFIG_PREFIX, val);
+               }
+       }
+
+       val = get_ip6_routes ();
+       if (val)
+               g_hash_table_insert (ip6config, NM_VPN_PLUGIN_IP6_CONFIG_ROUTES, val);
+
+       /* DNS and WINS servers */
+       dns_domains = g_ptr_array_sized_new (3);
+       for (i = 1; i < 256; i++) {
+               char buf[50];
+
+               snprintf (buf, sizeof (buf), "foreign_option_%d", i);
+               tmp = getenv (buf);
+
+               if (!tmp || !tmp[0])
+                       break;
+
+               if (!g_str_has_prefix (tmp, "dhcp-option "))
+                       continue;
+
+               tmp += 12; /* strlen ("dhcp-option ") */
+
+               if (g_str_has_prefix (tmp, "DNS "))
+                       dns_list = parse_addr4_list (dns_list, tmp + 4);
+               else if (g_str_has_prefix (tmp, "WINS "))
+                       nbns_list = parse_addr4_list (nbns_list, tmp + 5);
+               else if (g_str_has_prefix (tmp, "DOMAIN ") && is_domain_valid (tmp + 7))
+                       g_ptr_array_add (dns_domains, (gpointer) tmp + 7);
+       }
+
+       if (dns_list)
+               g_hash_table_insert (ip4config, NM_VPN_PLUGIN_IP4_CONFIG_DNS, dns_list);
+       if (nbns_list)
+               g_hash_table_insert (ip4config, NM_VPN_PLUGIN_IP4_CONFIG_NBNS, nbns_list);
+       if (dns_domains->len) {
+               val = g_slice_new0 (GValue);
+               g_value_init (val, DBUS_TYPE_G_PTR_ARRAY_OF_STRING);
+               g_value_take_boxed (val, dns_domains);
+               g_hash_table_insert (ip4config, NM_VPN_PLUGIN_IP4_CONFIG_DOMAINS, val);
+       } else
+               g_ptr_array_free (dns_domains, TRUE);
+
+       /* Tunnel MTU */
+       tmp = getenv ("tun_mtu");
+       if (tmp && tmp[0]) {
+               long int mtu;
+
+               errno = 0;
+               mtu = strtol (tmp, NULL, 10);
+               if (errno || mtu < 0 || mtu > 20000) {
+                       g_warning ("Ignoring invalid tunnel MTU '%s'", tmp);
+               } else {
+                       val = uint_to_gvalue ((guint32) mtu);
+                       g_hash_table_insert (config, NM_VPN_PLUGIN_CONFIG_MTU, val);
+               }
+       }
+
+       if (g_hash_table_size (ip4config)) {
+               g_hash_table_insert (config, NM_VPN_PLUGIN_CONFIG_HAS_IP4, bool_to_gvalue (TRUE));
+               if (out_ip4_config)
+                       *out_ip4_config = g_hash_table_ref (ip4config);
+       }
+       g_hash_table_unref (ip4config);
+
+       if (g_hash_table_size (ip6config)) {
+               g_hash_table_insert (config, NM_VPN_PLUGIN_CONFIG_HAS_IP6, bool_to_gvalue (TRUE));
+               if (out_ip6_config)
+                       *out_ip6_config = g_hash_table_ref (ip6config);
+       }
+       g_hash_table_unref (ip6config);
+
+       if (out_config)
+               *out_config = g_hash_table_ref (config);
+       g_hash_table_unref (config);
+       return TRUE;
+}
+
diff --git a/src/helper-config.h b/src/helper-config.h
new file mode 100644
index 0000000..59c0229
--- /dev/null
+++ b/src/helper-config.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-openvpn-service - openvpn integration with NetworkManager
+ *
+ * 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.
+ *
+ */
+
+#ifndef HELPER_CONFIG_H
+#define HELPER_CONFIG_H
+
+#include <glib.h>
+#include <dbus/dbus-glib.h>
+
+#define DBUS_TYPE_G_ARRAY_OF_UINT          (dbus_g_type_get_collection ("GArray", G_TYPE_UINT))
+#define DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT (dbus_g_type_get_collection ("GPtrArray", 
DBUS_TYPE_G_ARRAY_OF_UINT))
+#define DBUS_TYPE_G_PTR_ARRAY_OF_STRING    (dbus_g_type_get_collection ("GPtrArray", G_TYPE_STRING))
+#define DBUS_TYPE_G_IP6_ROUTE              (dbus_g_type_get_struct ("GValueArray", DBUS_TYPE_G_UCHAR_ARRAY, 
G_TYPE_UINT, DBUS_TYPE_G_UCHAR_ARRAY, G_TYPE_UINT, G_TYPE_INVALID))
+#define DBUS_TYPE_G_ARRAY_OF_IP6_ROUTE     (dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_IP6_ROUTE))
+
+gboolean helper_generate_config (const char **args,
+                                 gboolean is_tapdev,
+                                 gboolean is_restart,
+                                 GHashTable **out_config,
+                                 GHashTable **out_ip4_config,
+                                 GHashTable **out_ip6_config,
+                                 GError **error);
+
+#endif /* HELPER_CONFIG_H */
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
new file mode 100644
index 0000000..8904611
--- /dev/null
+++ b/src/tests/Makefile.am
@@ -0,0 +1,22 @@
+AM_CPPFLAGS = \
+       -I$(top_srcdir) \
+       $(GLIB_CFLAGS) \
+       $(NM_CFLAGS) \
+       -DTESTDIR="\"$(abs_srcdir)\""
+
+noinst_PROGRAMS = test-helper-config
+
+test_helper_config_SOURCES = \
+       test-helper-config.c \
+       ../helper-config.c \
+       ../helper-config.h
+
+test_helper_config_LDADD = \
+       $(GLIB_LIBS) \
+       $(NM_LIBS)
+
+TESTS = test-helper-config
+
+EXTRA_DIST = \
+       test-basic-init.conf \
+       test-basic-restart.conf
diff --git a/src/tests/test-helper-config.c b/src/tests/test-helper-config.c
new file mode 100644
index 0000000..73258a8
--- /dev/null
+++ b/src/tests/test-helper-config.c
@@ -0,0 +1,390 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*
+ * 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.
+ *
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "src/nm-openvpn-service.h"
+#include "src/helper-config.h"
+
+/******************************************************************/
+/* libnm-util still uses GValueArray... remove when porting to libnm */
+
+#ifdef __clang__
+
+#undef G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+#undef G_GNUC_END_IGNORE_DEPRECATIONS
+
+#define G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
+    _Pragma("clang diagnostic push") \
+    _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")
+
+#define G_GNUC_END_IGNORE_DEPRECATIONS \
+    _Pragma("clang diagnostic pop")
+
+#endif
+
+#define g_value_array_get_type() \
+  G_GNUC_EXTENSION ({ \
+    G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
+    g_value_array_get_type (); \
+    G_GNUC_END_IGNORE_DEPRECATIONS \
+  })
+
+#define g_value_array_get_nth(value_array, index_) \
+  G_GNUC_EXTENSION ({ \
+    G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
+    g_value_array_get_nth (value_array, index_); \
+    G_GNUC_END_IGNORE_DEPRECATIONS \
+  })
+
+/******************************************************************/
+
+/* returns argv */
+static char **
+load_config (const char *file)
+{
+       char *contents = NULL;
+       gboolean success;
+       GError *error = NULL;
+       char **lines, **iter;
+       char **argv = NULL;
+
+       g_assert (file);
+       success = g_file_get_contents (file, &contents, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+       g_assert (contents && contents[0]);
+
+       lines = g_strsplit_set (contents, "\r\n", -1);
+       g_assert (g_strv_length (lines) > 5);
+
+       clearenv ();
+       for (iter = lines; iter && *iter; iter++) {
+               if (!argv) {
+                       argv = g_strsplit_set (*iter, " \t", -1);
+                       g_assert (argv && g_strv_length (argv));
+               } else {
+                       g_assert (!*iter[0] || strchr (*iter, '='));
+                       putenv (*iter);
+               }
+       }
+
+       return argv;
+}
+
+/********************/
+/* Remove these when porting to libnm */
+
+#define NM_UTILS_INET_ADDRSTRLEN     INET6_ADDRSTRLEN
+
+static char _nm_utils_inet_ntop_buffer[NM_UTILS_INET_ADDRSTRLEN];
+
+/**
+ * nm_utils_inet4_ntop: (skip)
+ * @inaddr: the address that should be converted to string.
+ * @dst: the destination buffer, it must contain at least %INET_ADDRSTRLEN
+ *  or %NM_UTILS_INET_ADDRSTRLEN characters. If set to %NULL, it will return
+ *  a pointer to an internal, static buffer (shared with nm_utils_inet6_ntop()).
+ *  Beware, that the internal buffer will be overwritten with ever new call
+ *  of nm_utils_inet4_ntop() or nm_utils_inet6_ntop() that does not provied it's
+ *  own @dst buffer. Also, using the internal buffer is not thread safe. When
+ *  in doubt, pass your own @dst buffer to avoid these issues.
+ *
+ * Wrapper for inet_ntop.
+ *
+ * Returns: the input buffer @dst, or a pointer to an
+ *  internal, static buffer. This function cannot fail.
+ **/
+static const char *
+nm_utils_inet4_ntop (in_addr_t inaddr, char *dst)
+{
+       return inet_ntop (AF_INET, &inaddr, dst ? dst : _nm_utils_inet_ntop_buffer,
+                         INET_ADDRSTRLEN);
+}
+
+/**
+ * nm_utils_inet6_ntop: (skip)
+ * @in6addr: the address that should be converted to string.
+ * @dst: the destination buffer, it must contain at least %INET6_ADDRSTRLEN
+ *  or %NM_UTILS_INET_ADDRSTRLEN characters. If set to %NULL, it will return
+ *  a pointer to an internal, static buffer (shared with nm_utils_inet4_ntop()).
+ *  Beware, that the internal buffer will be overwritten with ever new call
+ *  of nm_utils_inet4_ntop() or nm_utils_inet6_ntop() that does not provied it's
+ *  own @dst buffer. Also, using the internal buffer is not thread safe. When
+ *  in doubt, pass your own @dst buffer to avoid these issues.
+ *
+ * Wrapper for inet_ntop.
+ *
+ * Returns: the input buffer @dst, or a pointer to an
+ *  internal, static buffer. %NULL is not allowed as @in6addr,
+ *  otherwise, this function cannot fail.
+ **/
+static const char *
+nm_utils_inet6_ntop (const struct in6_addr *in6addr, char *dst)
+{
+       g_return_val_if_fail (in6addr, NULL);
+       return inet_ntop (AF_INET6, in6addr, dst ? dst : _nm_utils_inet_ntop_buffer,
+                         INET6_ADDRSTRLEN);
+}
+
+/**********************/
+
+typedef enum {
+       OPT_TYPE_STRING,
+       OPT_TYPE_UINT,
+       OPT_TYPE_BOOLEAN,
+       OPT_TYPE_IP4_ADDR,
+       OPT_TYPE_IP4_ARRAY,
+       OPT_TYPE_ROUTES4_ARRAY,
+       OPT_TYPE_IP6_ADDR,
+       OPT_TYPE_ROUTES6_ARRAY,
+} OptType;
+
+static GType
+opt_type_to_gtype (OptType t)
+{
+       switch (t) {
+       case OPT_TYPE_STRING:
+               return G_TYPE_STRING;
+       case OPT_TYPE_UINT:
+               return G_TYPE_UINT;
+       case OPT_TYPE_BOOLEAN:
+               return G_TYPE_BOOLEAN;
+       case OPT_TYPE_IP4_ADDR:
+               return G_TYPE_UINT;
+       case OPT_TYPE_IP6_ADDR:
+               return DBUS_TYPE_G_UCHAR_ARRAY;
+       case OPT_TYPE_IP4_ARRAY:
+               return DBUS_TYPE_G_ARRAY_OF_UINT;
+       case OPT_TYPE_ROUTES4_ARRAY:
+               return DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT;
+       case OPT_TYPE_ROUTES6_ARRAY:
+               return DBUS_TYPE_G_ARRAY_OF_IP6_ROUTE;
+       }
+       g_assert_not_reached ();
+}
+
+typedef struct {
+       const char *name;
+       OptType type;
+       union {
+               const char *s;        /* string, ipv4, ipv6 address */
+               guint u;              /* uint and boolean */
+               const char *sarray[6];
+       } u;
+} Option;
+
+static void
+config_equal (const Option *options, GHashTable *config)
+{
+       guint i = 0;
+       const Option *expected;
+
+       for (expected = options; expected->name; expected++, i++) {
+               GValue *val = g_hash_table_lookup (config, expected->name);
+
+               g_assert (val);
+               g_assert (G_VALUE_HOLDS (val, opt_type_to_gtype (expected->type)));
+
+               switch (expected->type) {
+               case OPT_TYPE_STRING:
+                       g_assert_cmpstr (expected->u.s, ==, g_value_get_string (val));
+                       break;
+               case OPT_TYPE_UINT:
+                       g_assert_cmpint (expected->u.u, ==, g_value_get_uint (val));
+                       break;
+               case OPT_TYPE_BOOLEAN:
+                       g_assert_cmpint (!!expected->u.u, ==, g_value_get_boolean (val));
+                       break;
+               case OPT_TYPE_IP4_ADDR:
+                       g_assert_cmpstr (expected->u.s, ==, nm_utils_inet4_ntop (g_value_get_uint (val), 
NULL));
+                       break;
+               case OPT_TYPE_IP6_ADDR: {
+                       GByteArray *ba = g_value_get_boxed (val);
+
+                       g_assert_cmpint (ba->len, ==, sizeof (struct in6_addr));
+                       g_assert_cmpstr (expected->u.s, ==, nm_utils_inet6_ntop ((struct in6_addr *) 
ba->data, NULL));
+                       break;
+               }
+               case OPT_TYPE_IP4_ARRAY: {
+                       GArray *a = g_value_get_boxed (val);
+                       guint n;
+
+                       g_assert_cmpint (g_strv_length ((char **) expected->u.sarray), ==, a->len);
+                       for (n = 0; n < a->len; n++)
+                               g_assert_cmpstr (expected->u.sarray[n], ==, nm_utils_inet4_ntop 
(g_array_index (a, guint, n), NULL));
+                       break;
+               }
+               case OPT_TYPE_ROUTES4_ARRAY: {
+                       GPtrArray *a = g_value_get_boxed (val);
+                       GString *s = g_string_sized_new (30);
+                       guint n;
+
+                       g_assert_cmpint (g_strv_length ((char **) expected->u.sarray), ==, a->len);
+                       for (n = 0; n < a->len; n++) {
+                               GArray *r = g_ptr_array_index (a, n);
+
+                               g_string_set_size (s, 0);
+                               g_string_append_printf (s, "%s/%u",
+                                                       nm_utils_inet4_ntop (g_array_index (r, guint, 0), 
NULL),
+                                                       g_array_index (r, guint, 1));
+                               /* Split due to static buffer in nm_utils_inet4_ntop() */
+                               g_string_append_printf (s, ",%s,%u",
+                                                       nm_utils_inet4_ntop (g_array_index (r, guint, 2), 
NULL),
+                                                       g_array_index (r, guint, 3));
+
+                               g_assert_cmpstr (expected->u.sarray[n], ==, s->str);
+                       }
+                       g_string_free (s, TRUE);
+                       break;
+               }
+               case OPT_TYPE_ROUTES6_ARRAY: {
+                       GPtrArray *a = g_value_get_boxed (val);
+                       GString *s = g_string_sized_new (30);
+                       guint n;
+
+                       g_assert_cmpint (g_strv_length ((char **) expected->u.sarray), ==, a->len);
+                       for (n = 0; n < a->len; n++) {
+                               GValueArray *r = g_ptr_array_index (a, n);
+                               GByteArray *b;
+
+                               g_string_set_size (s, 0);
+
+                               b = g_value_get_boxed (g_value_array_get_nth (r, 0));
+                               g_assert_cmpint (b->len, ==, 16);
+                               g_string_append_printf (s, "%s/%u",
+                                                       nm_utils_inet6_ntop ((struct in6_addr *) b->data, 
NULL),
+                                                       g_value_get_uint (g_value_array_get_nth (r, 1)));
+
+                               /* Split due to static buffer in nm_utils_inet6_ntop() */
+                               b = g_value_get_boxed (g_value_array_get_nth (r, 2));
+                               g_assert_cmpint (b->len, ==, 16);
+                               g_string_append_printf (s, ",%s,%u",
+                                                       nm_utils_inet6_ntop ((struct in6_addr *) b->data, 
NULL),
+                                                       g_value_get_uint (g_value_array_get_nth (r, 3)));
+
+                               g_assert_cmpstr (expected->u.sarray[n], ==, s->str);
+                       }
+                       g_string_free (s, TRUE);
+                       break;
+               }
+               default:
+                       g_assert_not_reached ();
+               }
+       }
+
+       g_assert_cmpint (i, ==, g_hash_table_size (config));
+}
+
+static void
+test_init (void)
+{
+       gboolean success;
+       char **argv;
+       GError *error = NULL;
+       GHashTable *config = NULL;
+       GHashTable *ip4_config = NULL;
+       GHashTable *ip6_config = NULL;
+       const Option options[] = {
+               { NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY, OPT_TYPE_IP4_ADDR, { .s = "87.238.35.145" } },
+               { NM_VPN_PLUGIN_CONFIG_TUNDEV,      OPT_TYPE_STRING,   { .s = "tun0"} },
+               { NM_VPN_PLUGIN_CONFIG_MTU,         OPT_TYPE_UINT,     { .u = 1500 } },
+               { NM_VPN_PLUGIN_CONFIG_HAS_IP4,     OPT_TYPE_BOOLEAN,  { .u = TRUE } },
+               { NM_VPN_PLUGIN_CONFIG_HAS_IP6,     OPT_TYPE_BOOLEAN,  { .u = TRUE } },
+               { NULL }
+       };
+       const Option ip4_options[] = {
+               { NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, OPT_TYPE_IP4_ADDR,      { .s = "100.64.48.5" } },
+               { NM_VPN_PLUGIN_IP4_CONFIG_PTP,         OPT_TYPE_IP4_ADDR,      { .s = "100.64.48.5" } },
+               { NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS,     OPT_TYPE_IP4_ADDR,      { .s = "100.64.48.6" } },
+               { NM_VPN_PLUGIN_IP4_CONFIG_PREFIX,      OPT_TYPE_UINT,          { .u = 32 } },
+               { NM_VPN_PLUGIN_IP4_CONFIG_DNS,         OPT_TYPE_IP4_ARRAY,     { .sarray = { "8.8.8.8", NULL 
} } },
+               { NM_VPN_PLUGIN_IP4_CONFIG_ROUTES,      OPT_TYPE_ROUTES4_ARRAY, { .sarray = { 
"10.0.0.0/24,100.64.48.5,0", "100.64.48.1/32,100.64.48.5,0", NULL } } },
+               { NULL }
+       };
+       const Option ip6_options[] = {
+               { NM_VPN_PLUGIN_IP6_CONFIG_ADDRESS,     OPT_TYPE_IP6_ADDR,      { .s = "2001:db8::1000" } },
+               { NM_VPN_PLUGIN_IP6_CONFIG_PTP,         OPT_TYPE_IP6_ADDR,      { .s = "2001:db8::1" } },
+               { NM_VPN_PLUGIN_IP6_CONFIG_PREFIX,      OPT_TYPE_UINT,          { .u = 64 } },
+               { NM_VPN_PLUGIN_IP6_CONFIG_ROUTES,      OPT_TYPE_ROUTES6_ARRAY, { .sarray = { 
"fd00::/64,2001:db8::1,0", NULL } } },
+               { NULL }
+       };
+
+       argv = load_config (TESTDIR "/test-basic-init.conf");
+       g_assert (argv);
+       success = helper_generate_config ((const char **) argv, FALSE, FALSE, &config, &ip4_config, 
&ip6_config, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+
+       g_assert (config);
+       config_equal (options, config);
+
+       g_assert (ip4_config);
+       config_equal (ip4_options, ip4_config);
+
+       g_assert (ip6_config);
+       config_equal (ip6_options, ip6_config);
+
+       g_hash_table_unref (config);
+       g_hash_table_unref (ip4_config);
+       g_hash_table_unref (ip6_config);
+       g_strfreev (argv);
+}
+
+#if 0
+static void
+test_restart (void)
+{
+       gboolean success;
+       char **argv;
+       GError *error = NULL;
+       GHashTable *config = NULL;
+       GHashTable *ip4_config = NULL;
+       GHashTable *ip6_config = NULL;
+
+       argv = load_config (TESTDIR "/test-basic-restart.conf");
+       g_assert (argv);
+       success = helper_generate_config ((const char **) argv, FALSE, TRUE, &config, &ip4_config, 
&ip6_config, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+
+       g_hash_table_unref (config);
+       g_hash_table_unref (ip4_config);
+       g_hash_table_unref (ip6_config);
+       g_strfreev (argv);
+}
+#endif
+
+int
+main (int argc, char **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+#if !GLIB_CHECK_VERSION (2, 35, 0)
+       g_type_init ();
+#endif
+
+       g_test_add_func ("/helper-config/init", test_init);
+#if 0
+       g_test_add_func ("/helper-config/restart", test_restart);
+#endif
+
+       return g_test_run ();
+}
+


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]