[network-manager-libreswan/lr/multiple-vpn: 5/8] service: process the configuration in the service, not the helper



commit acb9eb9de50bcf6b9a6e645053017331c2df828e
Author: Lubomir Rintel <lkundrak v3 sk>
Date:   Wed Oct 28 12:26:01 2015 +0100

    service: process the configuration in the service, not the helper
    
    For multiple instance support we need to pass arguments to the helper. We use
    the ifupdown option for that.
    
    As we call our ifupdown script we need to manage the routes ourselves. That's
    probably a good idea anyway. However, the helper never knows all the routes.
    Therefore we have to move the logic of creating the IP4Config in the service.
    
    The service adds a new bus object for the helper to talk to and the helper just
    pushes in all the PLUTO_* environment variables to the service.
    
    It's probably cleaner that way anyway + our DBus exposed VPN configuration now
    makes sense. Yay!

 .gitignore                          |    2 +
 configure.ac                        |    2 +-
 nm-libreswan-service.conf           |    1 +
 src/Makefile.am                     |   25 ++-
 src/nm-libreswan-helper-service.xml |    9 +
 src/nm-libreswan-service-helper.c   |  396 ++---------------------------
 src/nm-libreswan-service.c          |  468 ++++++++++++++++++++++++++++++++++-
 src/nm-libreswan-service.h          |    7 +-
 8 files changed, 525 insertions(+), 385 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index c07e7cf..94c0298 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,5 +36,7 @@ nm-libreswan.desktop
 src/nm-libreswan-service
 src/nm-libreswan-service-helper
 src/show-xfrm
+src/nm-libreswan-helper-service-dbus.c
+src/nm-libreswan-helper-service-dbus.h
 
 /NetworkManager-libreswan*.tar*
diff --git a/configure.ac b/configure.ac
index 50f37fd..7125637 100644
--- a/configure.ac
+++ b/configure.ac
@@ -62,7 +62,7 @@ AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE", [Gettext package])
 IT_PROG_INTLTOOL([0.35])
 AM_GLIB_GNU_GETTEXT
 
-PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.32)
+PKG_CHECK_MODULES(GLIB, gio-unix-2.0 >= 2.32)
 GLIB_CFLAGS="$GLIB_CFLAGS -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_32"
 
 PKG_CHECK_MODULES(LIBNL, libnl-3.0 >= 3.2.8)
diff --git a/nm-libreswan-service.conf b/nm-libreswan-service.conf
index b5cecc6..49e421e 100644
--- a/nm-libreswan-service.conf
+++ b/nm-libreswan-service.conf
@@ -5,6 +5,7 @@
        <policy user="root">
                <allow own="org.freedesktop.NetworkManager.libreswan"/>
                <allow send_destination="org.freedesktop.NetworkManager.libreswan"/>
+               <allow send_interface="org.freedesktop.NetworkManager.libreswan.helper"/>
        </policy>
        <policy context="default">
                <deny own="org.freedesktop.NetworkManager.libreswan"/>
diff --git a/src/Makefile.am b/src/Makefile.am
index 189e090..c326cbe 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -13,6 +13,25 @@ AM_CPPFLAGS = \
 
 libexec_PROGRAMS = nm-libreswan-service nm-libreswan-service-helper
 
+# D-Bus stuff
+noinst_LTLIBRARIES = libnm-libreswan-helper-service-dbus.la
+
+nodist_libnm_libreswan_helper_service_dbus_la_SOURCES =                \
+       nm-libreswan-helper-service-dbus.c                      \
+       nm-libreswan-helper-service-dbus.h
+
+libnm_libreswan_helper_service_dbus_la_CPPFLAGS = $(filter-out -DGLIB_VERSION_MAX_ALLOWED%,$(AM_CPPFLAGS))
+
+nm-libreswan-helper-service-dbus.h: $(top_srcdir)/src/nm-libreswan-helper-service.xml
+       $(AM_V_GEN) gdbus-codegen \
+               --generate-c-code $(basename $@) \
+               --c-namespace NMDBus \
+               --interface-prefix org.freedesktop.NetworkManager \
+               $<
+
+nm-libreswan-helper-service-dbus.c:
+       @true
+
 nm_libreswan_service_SOURCES = \
        nm-libreswan-service.c \
        nm-libreswan-service.h
@@ -20,6 +39,8 @@ nm_libreswan_service_SOURCES = \
 nm_libreswan_service_LDADD = \
        $(GLIB_LIBS) \
        $(LIBNM_LIBS) \
+       $(LIBNL_LIBS) \
+       libnm-libreswan-helper-service-dbus.la \
        -lutil
 
 nm_libreswan_service_helper_SOURCES = \
@@ -27,7 +48,7 @@ nm_libreswan_service_helper_SOURCES = \
 
 nm_libreswan_service_helper_LDADD = \
        $(LIBNM_LIBS) \
-       $(LIBNL_LIBS)
+       libnm-libreswan-helper-service-dbus.la
 
 install-exec-hook:
        $(LN_S) -f nm-libreswan-service-helper \
@@ -42,4 +63,6 @@ show_xfrm_SOURCES = show-xfrm.c
 
 show_xfrm_LDADD = $(LIBNL_LIBS) $(GLIB_LIBS)
 
+BUILT_SOURCES = nm-libreswan-helper-service-dbus.h nm-libreswan-helper-service-dbus.c
+
 CLEANFILES = *~
diff --git a/src/nm-libreswan-helper-service.xml b/src/nm-libreswan-helper-service.xml
new file mode 100644
index 0000000..f92b5d4
--- /dev/null
+++ b/src/nm-libreswan-helper-service.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/">
+  <interface name="org.freedesktop.NetworkManager.libreswan.helper">
+    <method name="Callback">
+      <arg name="environment" type="a{ss}" direction="in"/>
+    </method>
+  </interface>
+</node>
diff --git a/src/nm-libreswan-service-helper.c b/src/nm-libreswan-service-helper.c
index ccde230..531bdba 100644
--- a/src/nm-libreswan-service-helper.c
+++ b/src/nm-libreswan-service-helper.c
@@ -1,8 +1,7 @@
 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-libreswan-service-helper - libreswan integration with NetworkManager
  *
- * Dan Williams <dcbw redhat com>
- * Avesh Agarwal <avagarwa redhat com>
+ * Lubomir Rintel <lkundrak v3 sk>
  *
  * 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
@@ -18,400 +17,57 @@
  * 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.
+ * Copyright 2015 Red Hat, Inc.
  */
 
-#define _GNU_SOURCE 1
-
 #include <glib.h>
 #include <stdlib.h>
-#include <stdio.h>
 #include <string.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <errno.h>
-
-#include <netlink/netlink.h>
-#include <netlink/msg.h>
-
-#define _LINUX_IN6_H 1
-#include <linux/xfrm.h>
-
-#include <NetworkManager.h>
 
-#include <nm-vpn-service-plugin.h>
+#include "nm-libreswan-helper-service-dbus.h"
 #include "nm-libreswan-service.h"
-#include "nm-utils.h"
-
-static void
-helper_failed (GDBusProxy *proxy, const char *reason)
-{
-       GError *err = NULL;
-
-       g_warning ("This helper did not receive a valid %s from the IPsec daemon", reason);
-
-       if (!g_dbus_proxy_call_sync (proxy, "SetFailure",
-                                    g_variant_new ("(s)", reason),
-                                    G_DBUS_CALL_FLAGS_NONE, -1,
-                                    NULL,
-                                    &err)) {
-               g_warning ("Could not send failure information: %s", err->message);
-               g_error_free (err);
-       }
-
-       exit (1);
-}
-
-static void
-send_ip4_config (GDBusProxy *proxy, GVariant *config)
-{
-       GError *err = NULL;
-
-       if (!g_dbus_proxy_call_sync (proxy, "SetIp4Config",
-                                    g_variant_new ("(*)", config),
-                                    G_DBUS_CALL_FLAGS_NONE, -1,
-                                    NULL,
-                                    &err)) {
-               g_warning ("Could not send IPv4 configuration: %s", err->message);
-               g_error_free (err);
-       }
-}
-
-/********************************************************************/
-
-/* The various SWANs don't tell helper scripts whether upstream sent
- * specific subnets to be routed over the VPN (eg, CISCO_SPLIT_INC).
- * This is what we need to automatically determine 'never-default' behavior.
- * Instead, we have to inspect the kernel's SAD (Security Assocation Database)
- * for IPsec-secured routes pointing to the VPN gateway.
- */
-
-typedef struct {
-       struct in_addr gw4;
-       gboolean have_routes4;
-} RoutesInfo;
-
-static int
-verify_source (struct nl_msg *msg, gpointer user_data)
-{
-       struct ucred *creds = nlmsg_get_creds (msg);
-
-       if (!creds || creds->pid || creds->uid || creds->gid) {
-               if (creds) {
-                       g_warning ("netlink: received non-kernel message (pid %d uid %d gid %d)",
-                                  creds->pid, creds->uid, creds->gid);
-               } else
-                       g_warning ("netlink: received message without credentials");
-               return NL_STOP;
-       }
 
-       return NL_OK;
-}
-
-static struct nl_sock *
-setup_socket (void)
-{
-       struct nl_sock *sk;
-       int err;
-
-       sk = nl_socket_alloc ();
-       g_return_val_if_fail (sk, NULL);
-
-       /* Only ever accept messages from kernel */
-       err = nl_socket_modify_cb (sk, NL_CB_MSG_IN, NL_CB_CUSTOM, verify_source, NULL);
-       g_assert (!err);
-
-       err = nl_connect (sk, NETLINK_XFRM);
-       g_assert (!err);
-       err = nl_socket_set_passcred (sk, 1);
-       g_assert (!err);
-
-       return sk;
-}
-
-static int
-parse_reply (struct nl_msg *msg, void *arg)
-{
-       RoutesInfo *info = arg;
-       struct nlmsghdr *n = nlmsg_hdr (msg);
-       struct nlattr *tb[XFRMA_MAX + 1];
-       struct xfrm_userpolicy_info *xpinfo = NULL;
-
-       if (info->have_routes4) {
-               /* already found some routes */
-               return NL_SKIP;
-       }
-
-       if (n->nlmsg_type != XFRM_MSG_NEWPOLICY) {
-               g_warning ("msg type %d not NEWPOLICY", n->nlmsg_type);
-               return NL_SKIP;
-       }
-
-       /* Netlink message header is followed by 'struct xfrm_userpolicy_info' and
-        * then the attributes.
-        */
-
-       if (!nlmsg_valid_hdr (n, sizeof (struct xfrm_userpolicy_info))) {
-               g_warning ("msg too short");
-               return -NLE_MSG_TOOSHORT;
-       }
-
-       xpinfo = nlmsg_data (n);
-       if (nla_parse (tb, XFRMA_MAX,
-                      nlmsg_attrdata (n, sizeof (struct xfrm_userpolicy_info)),
-                      nlmsg_attrlen (n, sizeof (struct xfrm_userpolicy_info)),
-                      NULL) < 0) {
-               g_warning ("failed to parse attributes");
-               return NL_SKIP;
-       }
-
-       if (tb[XFRMA_TMPL]) {
-               int attrlen = nla_len (tb[XFRMA_TMPL]);
-               struct xfrm_user_tmpl *list = nla_data (tb[XFRMA_TMPL]);
-               int i;
-
-               /* We only look for subnet route associations, eg where
-                * (sel->prefixlen_d > 0), and for those associations, we match
-                * the xfrm_user_tmpl's destination address against the PLUTO_PEER.
-                */
-               if (xpinfo->sel.family == AF_INET && xpinfo->sel.prefixlen_d > 0) {
-                       for (i = 0; i < attrlen / sizeof (struct xfrm_user_tmpl); i++) {
-                               struct xfrm_user_tmpl *tmpl = &list[i];
-
-                               if (   tmpl->family == AF_INET
-                                   && memcmp (&tmpl->id.daddr, &info->gw4, sizeof (struct in_addr)) == 0) {
-                                       info->have_routes4 = TRUE;
-                                       break;
-                               }
-                       }
-               }
-       }
-
-       return NL_OK;
-}
-
-static gboolean
-have_sad_routes (const char *gw_addr4)
-{
-       RoutesInfo info = { { 0 }, FALSE };
-       struct nl_sock *sk;
-       int err;
-
-       if (inet_pton (AF_INET, gw_addr4, &info.gw4) != 1)
-               return FALSE;
-
-       sk = setup_socket ();
-       if (!sk)
-               return FALSE;
-
-       err = nl_send_simple (sk, XFRM_MSG_GETPOLICY, NLM_F_DUMP, NULL, 0);
-       if (err < 0) {
-               g_warning ("Error sending: %d %s", err, nl_geterror (err));
-               goto done;
-       }
-
-       nl_socket_modify_cb (sk, NL_CB_VALID, NL_CB_CUSTOM, parse_reply, &info);
-
-       err = nl_recvmsgs_default (sk);
-       if (err < 0) {
-               g_warning ("Error parsing: %d %s", err, nl_geterror (err));
-               goto done;
-       }
-
-done:
-       nl_socket_free (sk);
-       return info.have_routes4;
-}
-
-/********************************************************************/
-
-static GVariant *
-str_to_gvariant (const char *str, gboolean try_convert)
-{
-
-       /* 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;
-       }
-
-       return g_variant_new_string (str);
-}
-
-static GVariant *
-addr4_to_gvariant (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 g_variant_new_uint32 (temp_addr.s_addr);
-}
-
-static GVariant *
-addr4_list_to_gvariant (const char *str)
-{
-       GVariantBuilder builder;
-       char **split;
-       int i;
-
-       /* Empty */
-       if (!str || strlen (str) < 1)
-               return NULL;
-
-       split = g_strsplit (str, " ", -1);
-       if (g_strv_length (split) == 0)
-               return NULL;
-
-       g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
-
-       for (i = 0; split[i]; i++) {
-               struct in_addr addr;
-
-               if (inet_pton (AF_INET, split[i], &addr) > 0) {
-                       g_variant_builder_add_value (&builder, g_variant_new_uint32 (addr.s_addr));
-               } else {
-                       g_strfreev (split);
-                       g_variant_unref (g_variant_builder_end (&builder));
-                       return NULL;
-               }
-       }
-
-       g_strfreev (split);
-
-       return g_variant_builder_end (&builder);
-}
-
-/*
- * Environment variables passed to this helper:
- *
- * PLUTO_PEER                -- vpn gateway address
- * PLUTO_MY_SOURCEIP         -- address
- * PLUTO_CISCO_DNS_INFO/     -- list of dns servers
- *    PLUTO_PEER_DNS_INFO
- * PLUTO_CISCO_DOMAIN_INFO/  -- default domain name
- *    PLUTO_PEER_DOMAIN_INFO
- * PLUTO_PEER_BANNER         -- banner from server
- *
- * NOTE: this helper is currently called explicitly by the ipsec up/down
- * script /usr/libexec/ipsec/_updown.netkey when the configuration contains
- * "nm_configured=yes".  Eventually we want to somehow pass the helper
- * directly to pluto/whack with the --updown option.
- */
 int 
 main (int argc, char *argv[])
 {
-       GDBusProxy *proxy;
-       char *tmp=NULL;
-       GVariantBuilder config;
-       GVariant *val;
+       NMDBusLibreswanHelper *proxy;
+       GVariantBuilder environment;
        GError *err = NULL;
+       gchar **environ;
+       gchar **p;
 
 #if !GLIB_CHECK_VERSION (2, 35, 0)
        g_type_init ();
 #endif
 
-       /* The IPsec service gives us a "reason" code.  If we are given one,
-        * don't proceed unless its "connect".
-        */
-       tmp = getenv ("libreswan_reason");
-       if (!tmp)
-               tmp = getenv ("openswan_reason");
-       if (g_strcmp0 (tmp, "connect") != 0)
-               exit (0);
-
-       
-       proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
-                                              G_DBUS_PROXY_FLAGS_NONE,
-                                              NULL,
-                                              NM_DBUS_SERVICE_LIBRESWAN,
-                                              NM_VPN_DBUS_PLUGIN_PATH,
-                                              NM_VPN_DBUS_PLUGIN_INTERFACE,
-                                              NULL, &err);
+       proxy = nmdbus_libreswan_helper_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                                               G_DBUS_PROXY_FLAGS_NONE,
+                                                               NM_DBUS_SERVICE_LIBRESWAN,
+                                                               NM_DBUS_PATH_LIBRESWAN_HELPER,
+                                                               NULL, &err);
        if (!proxy) {
                g_warning ("Could not create a D-Bus proxy: %s", err->message);
                g_error_free (err);
                exit (1);
        }
 
-       g_variant_builder_init (&config, G_VARIANT_TYPE_VARDICT);
-
-
-       /* Right peer (or Gateway) */
-       val = addr4_to_gvariant (getenv ("PLUTO_PEER"));
-       if (val)
-               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_GATEWAY, val);
-       else
-               helper_failed (proxy, "IPsec/Pluto Right Peer (VPN Gateway)");
-
-
-       /*
-        * Tunnel device
-        * Indicate that this plugin doesn't use tun/tap device
-        */
-       val = g_variant_new_string (NM_VPN_PLUGIN_IP4_CONFIG_TUNDEV_NONE);
-       g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_TUNDEV, val);
-
-       /* IP address */
-       val = addr4_to_gvariant (getenv ("PLUTO_MY_SOURCEIP"));
-       if (val)
-               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, val);
-       else
-               helper_failed (proxy, "IP4 Address");
-
-       /* PTP address; PTP address == internal IP4 address */
-       val = addr4_to_gvariant (getenv ("PLUTO_MY_SOURCEIP"));
-       if (val)
-               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_PTP, val);
-       else
-               helper_failed (proxy, "IP4 PTP Address");
-
-       /* Netmask */
-       val = g_variant_new_uint32 (32);
-       g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, val);
-
-       /* DNS */
-       val = addr4_list_to_gvariant (getenv ("PLUTO_PEER_DNS_INFO"));
-       if (!val) {
-               /* openswan value */
-               val = addr4_list_to_gvariant (getenv ("PLUTO_CISCO_DNS_INFO"));
+       g_variant_builder_init (&environment, G_VARIANT_TYPE ("a{ss}"));
+       environ = g_listenv ();
+       for (p = environ; *p; p++) {
+               if (strncmp ("PLUTO_", *p, 6))
+                       continue;
+               g_variant_builder_add (&environment, "{ss}", *p, g_getenv (*p));
        }
-       if (val)
-               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_DNS, val);
+       g_strfreev (environ);
 
-
-       /* Default domain */
-       val = str_to_gvariant (getenv ("PLUTO_PEER_DOMAIN_INFO"), TRUE);
-       if (!val) {
-               /* openswan value */
-               val = str_to_gvariant (getenv ("PLUTO_CISCO_DOMAIN_INFO"), TRUE);
+       if (!nmdbus_libreswan_helper_call_callback_sync (proxy,
+                                                        g_variant_builder_end (&environment),
+                                                        NULL, &err)) {
+               g_warning ("Could not call the plugin: %s", err->message);
+               g_error_free (err);
+               g_object_unref (proxy);
+               exit (1);
        }
-       if (val)
-               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_DOMAIN, val);
-
-       /* Banner */
-       val = str_to_gvariant (getenv ("PLUTO_PEER_BANNER"), TRUE);
-       if (val)
-               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_BANNER, val);
-
-       if (have_sad_routes (getenv ("PLUTO_PEER")))
-               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT, 
g_variant_new_boolean (TRUE));
-
-       /* Send the config info to the VPN plugin */
-       send_ip4_config (proxy, g_variant_builder_end (&config));
 
        g_object_unref (proxy);
 
diff --git a/src/nm-libreswan-service.c b/src/nm-libreswan-service.c
index 8f24bab..28b1bba 100644
--- a/src/nm-libreswan-service.c
+++ b/src/nm-libreswan-service.c
@@ -1,6 +1,10 @@
 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager-libreswan -- Network Manager Libreswan plugin
  *
+ * Dan Williams <dcbw redhat com>
+ * Avesh Agarwal <avagarwa redhat com>
+ * Lubomir Rintel <lkundrak v3 sk>
+ *
  * 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
@@ -15,9 +19,20 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2010 - 2011 Red Hat, Inc.
+ * Copyright (C) 2010 - 2015 Red Hat, Inc.
  */
 
+#define _GNU_SOURCE 1
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <netlink/netlink.h>
+#include <netlink/msg.h>
+
+#define _LINUX_IN6_H 1
+#include <linux/xfrm.h>
+
 #include <config.h>
 #include <stdio.h>
 #include <string.h>
@@ -37,6 +52,8 @@
 
 #include <NetworkManager.h>
 #include <nm-vpn-service-plugin.h>
+
+#include "nm-libreswan-helper-service-dbus.h"
 #include "nm-libreswan-service.h"
 #include "nm-utils.h"
 
@@ -94,6 +111,8 @@ typedef struct {
        guint retries;
        ConnectStep connect_step;
        NMConnection *connection;
+       NMDBusLibreswanHelper *dbus_skeleton;
+       GSList *routes;
 
        GIOChannel *channel;
        guint io_id;
@@ -106,12 +125,7 @@ typedef struct {
 
 #define NM_LIBRESWAN_PLUGIN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_LIBRESWAN_PLUGIN, 
NMLibreswanPluginPrivate))
 
-/* NOTE: the helper is currently called explicitly by the ipsec up/down
- * script /usr/libexec/ipsec/_updown.netkey when the configuration contains
- * "nm_configured=yes".  Eventually we want to somehow pass the helper
- * directly to pluto/whack with the --updown option.
- */
-#define NM_LIBRESWAN_HELPER_PATH               LIBEXECDIR"/nm-libreswan-service-helper"
+#define NM_LIBRESWAN_HELPER_PATH       LIBEXECDIR"/nm-libreswan-service-helper"
 
 #define DEBUG(...) \
     G_STMT_START { \
@@ -641,6 +655,7 @@ nm_libreswan_config_write (NMLibreswanPlugin *self,
        write_config_option (fd, " leftid= %s\n", nm_setting_vpn_get_data_item (s_vpn, NM_LIBRESWAN_LEFTID));
        write_config_option (fd, " leftxauthclient=yes\n");
        write_config_option (fd, " leftmodecfgclient=yes\n");
+       write_config_option (fd, " leftupdown=\"" NM_LIBRESWAN_HELPER_PATH "\"\n");
 
        default_username = nm_setting_vpn_get_user_name (s_vpn);
        props_username = nm_setting_vpn_get_data_item (s_vpn, NM_LIBRESWAN_LEFTXAUTHUSER);
@@ -667,7 +682,6 @@ nm_libreswan_config_write (NMLibreswanPlugin *self,
        else
                write_config_option (fd, " esp=%s\n", phase2_alg_str);
 
-       write_config_option (fd, " nm_configured=yes\n");
        write_config_option (fd, " rekey=yes\n");
        write_config_option (fd, " salifetime=24h\n");
        write_config_option (fd, " ikelifetime=24h\n");
@@ -843,6 +857,400 @@ spawn_pty (int *out_stdout,
 
 /****************************************************************/
 
+/* The various SWANs don't tell helper scripts whether upstream sent
+ * specific subnets to be routed over the VPN (eg, CISCO_SPLIT_INC).
+ * This is what we need to automatically determine 'never-default' behavior.
+ * Instead, we have to inspect the kernel's SAD (Security Assocation Database)
+ * for IPSec-secured routes pointing to the VPN gateway.
+ */
+
+typedef struct {
+       struct in_addr gw4;
+       gboolean have_routes4;
+} RoutesInfo;
+
+static int
+verify_source (struct nl_msg *msg, gpointer user_data)
+{
+       struct ucred *creds = nlmsg_get_creds (msg);
+
+       if (!creds || creds->pid || creds->uid || creds->gid) {
+               if (creds) {
+                       g_warning ("netlink: received non-kernel message (pid %d uid %d gid %d)",
+                                  creds->pid, creds->uid, creds->gid);
+               } else
+                       g_warning ("netlink: received message without credentials");
+               return NL_STOP;
+       }
+
+       return NL_OK;
+}
+
+static struct nl_sock *
+setup_socket (void)
+{
+       struct nl_sock *sk;
+       int err;
+
+       sk = nl_socket_alloc ();
+       g_return_val_if_fail (sk, NULL);
+
+       /* Only ever accept messages from kernel */
+       err = nl_socket_modify_cb (sk, NL_CB_MSG_IN, NL_CB_CUSTOM, verify_source, NULL);
+       g_assert (!err);
+
+       err = nl_connect (sk, NETLINK_XFRM);
+       g_assert (!err);
+       err = nl_socket_set_passcred (sk, 1);
+       g_assert (!err);
+
+       return sk;
+}
+
+static int
+parse_reply (struct nl_msg *msg, void *arg)
+{
+       RoutesInfo *info = arg;
+       struct nlmsghdr *n = nlmsg_hdr (msg);
+       struct nlattr *tb[XFRMA_MAX + 1];
+       struct xfrm_userpolicy_info *xpinfo = NULL;
+
+       if (info->have_routes4) {
+               /* already found some routes */
+               return NL_SKIP;
+       }
+
+       if (n->nlmsg_type != XFRM_MSG_NEWPOLICY) {
+               g_warning ("msg type %d not NEWPOLICY", n->nlmsg_type);
+               return NL_SKIP;
+       }
+
+       /* Netlink message header is followed by 'struct xfrm_userpolicy_info' and
+        * then the attributes.
+        */
+
+       if (!nlmsg_valid_hdr (n, sizeof (struct xfrm_userpolicy_info))) {
+               g_warning ("msg too short");
+               return -NLE_MSG_TOOSHORT;
+       }
+
+       xpinfo = nlmsg_data (n);
+       if (nla_parse (tb, XFRMA_MAX,
+                      nlmsg_attrdata (n, sizeof (struct xfrm_userpolicy_info)),
+                      nlmsg_attrlen (n, sizeof (struct xfrm_userpolicy_info)),
+                      NULL) < 0) {
+               g_warning ("failed to parse attributes");
+               return NL_SKIP;
+       }
+
+       if (tb[XFRMA_TMPL]) {
+               int attrlen = nla_len (tb[XFRMA_TMPL]);
+               struct xfrm_user_tmpl *list = nla_data (tb[XFRMA_TMPL]);
+               int i;
+
+               /* We only look for subnet route associations, eg where
+                * (sel->prefixlen_d > 0), and for those associations, we match
+                * the xfrm_user_tmpl's destination address against the PLUTO_PEER.
+                */
+               if (xpinfo->sel.family == AF_INET && xpinfo->sel.prefixlen_d > 0) {
+                       for (i = 0; i < attrlen / sizeof (struct xfrm_user_tmpl); i++) {
+                               struct xfrm_user_tmpl *tmpl = &list[i];
+
+                               if (   tmpl->family == AF_INET
+                                   && memcmp (&tmpl->id.daddr, &info->gw4, sizeof (struct in_addr)) == 0) {
+                                       info->have_routes4 = TRUE;
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       return NL_OK;
+}
+
+static gboolean
+have_sad_routes (const char *gw_addr4)
+{
+       RoutesInfo info = { { 0 }, FALSE };
+       struct nl_sock *sk;
+       int err;
+
+       if (inet_pton (AF_INET, gw_addr4, &info.gw4) != 1)
+               return FALSE;
+
+       sk = setup_socket ();
+       if (!sk)
+               return FALSE;
+
+       err = nl_send_simple (sk, XFRM_MSG_GETPOLICY, NLM_F_DUMP, NULL, 0);
+       if (err < 0) {
+               g_warning ("Error sending: %d %s", err, nl_geterror (err));
+               goto done;
+       }
+
+       nl_socket_modify_cb (sk, NL_CB_VALID, NL_CB_CUSTOM, parse_reply, &info);
+
+       err = nl_recvmsgs_default (sk);
+       if (err < 0) {
+               g_warning ("Error parsing: %d %s", err, nl_geterror (err));
+               goto done;
+       }
+
+done:
+       nl_socket_free (sk);
+       return info.have_routes4;
+}
+
+/****************************************************************/
+
+static GVariant *
+str_to_gvariant (const char *str, gboolean try_convert)
+{
+
+       /* 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;
+       }
+
+       return g_variant_new_string (str);
+}
+
+static GVariant *
+addr4_to_gvariant (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 g_variant_new_uint32 (temp_addr.s_addr);
+}
+
+static GVariant *
+netmask4_to_gvariant (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 g_variant_new_uint32 (nm_utils_ip4_netmask_to_prefix (temp_addr.s_addr));
+}
+
+static GVariant *
+addr4_list_to_gvariant (const char *str)
+{
+       GVariantBuilder builder;
+       char **split;
+       int i;
+
+       /* Empty */
+       if (!str || strlen (str) < 1)
+               return NULL;
+
+       split = g_strsplit (str, " ", -1);
+       if (g_strv_length (split) == 0)
+               return NULL;
+
+       g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+
+       for (i = 0; split[i]; i++) {
+               struct in_addr addr;
+
+               if (inet_pton (AF_INET, split[i], &addr) > 0) {
+                       g_variant_builder_add_value (&builder, g_variant_new_uint32 (addr.s_addr));
+               } else {
+                       g_strfreev (split);
+                       g_variant_unref (g_variant_builder_end (&builder));
+                       return NULL;
+               }
+       }
+
+       g_strfreev (split);
+
+       return g_variant_builder_end (&builder);
+}
+
+static const gchar *
+lookup_string (GVariant *dict, const gchar *key)
+{
+       const gchar *value = NULL;
+
+       g_variant_lookup (dict, key, "&s", &value);
+       return value;
+}
+
+static GVariant *
+route_to_gvariant (GVariant *env)
+{
+       GVariantBuilder builder;
+
+       if (!lookup_string (env, "PLUTO_PEER_CLIENT"))
+               return NULL;
+
+       g_variant_builder_init (&builder, G_VARIANT_TYPE ("au"));
+
+#define _try_add(builder, variant) \
+       G_STMT_START { \
+               GVariant *_v = (variant); \
+               \
+               if (!_v) \
+                       goto fail; \
+               g_variant_builder_add_value ((builder), _v); \
+       } G_STMT_END
+       _try_add (&builder, addr4_to_gvariant (lookup_string (env, "PLUTO_PEER_CLIENT_NET")));
+       _try_add (&builder, netmask4_to_gvariant (lookup_string (env, "PLUTO_PEER_CLIENT_MASK")));
+       _try_add (&builder, addr4_to_gvariant (lookup_string (env, "PLUTO_NEXT_HOP")));
+       _try_add (&builder, g_variant_new_uint32 (0));
+       _try_add (&builder, addr4_to_gvariant (lookup_string (env, "PLUTO_MY_SOURCEIP")));
+#undef _try_add
+
+       return g_variant_builder_end (&builder);
+fail:
+       g_variant_builder_clear (&builder);
+       return NULL;
+}
+
+static gboolean
+handle_callback (NMDBusLibreswanHelper *object,
+                 GDBusMethodInvocation *invocation,
+                 GVariant *env,
+                 gpointer user_data)
+{
+       NMLibreswanPluginPrivate *priv = NM_LIBRESWAN_PLUGIN_GET_PRIVATE (user_data);
+       GVariantBuilder config;
+       GVariantBuilder builder;
+       GSList *iter;
+       GVariant *val;
+       gboolean success = FALSE;
+
+       g_message ("Configuration from the helper received.");
+
+       if (g_strcmp0 (lookup_string (env, "PLUTO_VERB"), "up-client") != 0) {
+               nmdbus_libreswan_helper_complete_callback (object, invocation);
+               return TRUE;
+       }
+
+       g_variant_builder_init (&config, G_VARIANT_TYPE_VARDICT);
+
+       /* Right peer (or Gateway) */
+       val = addr4_to_gvariant (lookup_string (env, "PLUTO_PEER"));
+       if (val)
+               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_GATEWAY, val);
+       else {
+               g_warning ("IPsec/Pluto Right Peer (VPN Gateway)");
+               goto out;
+       }
+
+       /*
+        * Tunnel device
+        * Indicate that this plugin doesn't use tun/tap device
+        */
+       val = g_variant_new_string (NM_VPN_PLUGIN_IP4_CONFIG_TUNDEV_NONE);
+       g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_TUNDEV, val);
+
+       /* IP address */
+       val = addr4_to_gvariant (lookup_string (env, "PLUTO_MY_SOURCEIP"));
+       if (val)
+               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, val);
+       else {
+               g_warning ("IP4 Address");
+               goto out;
+       }
+
+       /* PTP address; PTP address == internal IP4 address */
+       val = addr4_to_gvariant (lookup_string (env, "PLUTO_MY_SOURCEIP"));
+       if (val)
+               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_PTP, val);
+       else {
+               g_warning ("IP4 PTP Address");
+               goto out;
+       }
+
+       /* Netmask */
+       val = g_variant_new_uint32 (32);
+       g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, val);
+
+       /* DNS */
+       val = addr4_list_to_gvariant (lookup_string (env, "PLUTO_CISCO_DNS_INFO"));
+       if (!val) {
+               /* libreswan value */
+               val = addr4_list_to_gvariant (lookup_string (env, "PLUTO_PEER_DNS_INFO"));
+       }
+       if (val)
+               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_DNS, val);
+
+
+       /* Default domain */
+       val = str_to_gvariant (lookup_string (env, "PLUTO_CISCO_DOMAIN_INFO"), TRUE);
+       if (!val) {
+               /* libreswan value */
+               val = str_to_gvariant (lookup_string (env, "PLUTO_PEER_DOMAIN_INFO"), TRUE);
+       }
+       if (val)
+               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_DOMAIN, val);
+
+       /* Banner */
+       val = str_to_gvariant (lookup_string (env, "PLUTO_PEER_BANNER"), TRUE);
+       if (val)
+               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_BANNER, val);
+
+
+       val = addr4_to_gvariant (lookup_string (env, "PLUTO_NEXT_HOP"));
+       if (val)
+               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, val);
+
+       /* This route */
+       /* TODO: We just cumulate the routes on up-client. We probably should add and remove them
+        * on route-client and unroute-client verbs. */
+       val = route_to_gvariant (env);
+       if (val)
+               priv->routes = g_slist_append (priv->routes, g_variant_ref_sink (val));
+
+       /* Routes */
+       g_variant_builder_init (&builder, G_VARIANT_TYPE ("aau"));
+       for (iter = priv->routes; iter; iter = g_slist_next (iter))
+               g_variant_builder_add_value (&builder, (GVariant *) iter->data);
+       g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_ROUTES,
+                              g_variant_builder_end (&builder));
+
+       /* :( */
+       if (have_sad_routes (lookup_string (env, "PLUTO_PEER")))
+               g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT, 
g_variant_new_boolean (TRUE));
+
+       success = TRUE;
+
+out:
+       if (success) {
+               nm_vpn_service_plugin_set_ip4_config (NM_VPN_SERVICE_PLUGIN (user_data),
+                                                     g_variant_builder_end (&config));
+       } else {
+               connect_failed (NM_LIBRESWAN_PLUGIN (user_data), NULL,
+                               NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED);
+       }
+
+       nmdbus_libreswan_helper_complete_callback (object, invocation);
+       return success;
+}
+
+/****************************************************************/
+
 #define PASSPHRASE_REQUEST "Enter passphrase: "
 
 static gboolean
@@ -1296,6 +1704,9 @@ real_disconnect (NMVpnServicePlugin *plugin, GError **error)
        NMLibreswanPluginPrivate *priv = NM_LIBRESWAN_PLUGIN_GET_PRIVATE (plugin);
        gboolean ret;
 
+       g_slist_free_full (priv->routes, (GDestroyNotify) g_variant_unref);
+       priv->routes = NULL;
+
        if (!priv->connection)
                return TRUE;
 
@@ -1323,6 +1734,21 @@ real_disconnect (NMVpnServicePlugin *plugin, GError **error)
 }
 
 static void
+dispose (GObject *object)
+{
+       NMLibreswanPluginPrivate *priv = NM_LIBRESWAN_PLUGIN_GET_PRIVATE (object);
+       GDBusInterfaceSkeleton *skeleton = G_DBUS_INTERFACE_SKELETON (priv->dbus_skeleton);
+
+       if (skeleton) {
+               if (g_dbus_interface_skeleton_get_object_path (skeleton))
+                       g_dbus_interface_skeleton_unexport (skeleton);
+               g_signal_handlers_disconnect_by_func (skeleton, handle_callback, object);
+       }
+
+       G_OBJECT_CLASS (nm_libreswan_plugin_parent_class)->dispose (object);
+}
+
+static void
 nm_libreswan_plugin_init (NMLibreswanPlugin *plugin)
 {
 }
@@ -1348,6 +1774,7 @@ nm_libreswan_plugin_class_init (NMLibreswanPluginClass *libreswan_class)
        g_type_class_add_private (object_class, sizeof (NMLibreswanPluginPrivate));
 
        /* virtual methods */
+       object_class->dispose = dispose;
        object_class->finalize = finalize;
        parent_class->connect = real_connect;
        parent_class->connect_interactive = real_connect_interactive;
@@ -1387,8 +1814,10 @@ int
 main (int argc, char *argv[])
 {
        NMLibreswanPlugin *plugin;
+       NMLibreswanPluginPrivate *priv;
        gboolean persist = FALSE;
        GOptionContext *opt_ctx = NULL;
+       GDBusConnection *connection;
        GError *error = NULL;
 
        GOptionEntry options[] = {
@@ -1431,11 +1860,30 @@ main (int argc, char *argv[])
                                 NM_VPN_SERVICE_PLUGIN_DBUS_SERVICE_NAME, NM_DBUS_SERVICE_LIBRESWAN,
                                 NULL);
        if (!plugin) {
-                g_warning ("Failed to initialize a plugin instance: %s", error->message);
-                g_error_free (error);
+               g_warning ("Failed to initialize a plugin instance: %s", error->message);
+               g_error_free (error);
                exit (1);
        }
 
+       connection = nm_vpn_service_plugin_get_connection (NM_VPN_SERVICE_PLUGIN (plugin)),
+       priv = NM_LIBRESWAN_PLUGIN_GET_PRIVATE (plugin);
+       priv->dbus_skeleton = nmdbus_libreswan_helper_skeleton_new ();
+       if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (priv->dbus_skeleton),
+                                              connection,
+                                              NM_DBUS_PATH_LIBRESWAN_HELPER,
+                                              &error)) {
+               g_warning ("Failed to export helper interface: %s", error->message);
+               g_error_free (error);
+               g_clear_object (&plugin);
+               exit (1);
+       }
+
+       g_dbus_connection_register_object (connection, NM_VPN_DBUS_PLUGIN_PATH,
+                                          nmdbus_libreswan_helper_interface_info (),
+                                          NULL, NULL, NULL, NULL);
+
+       g_signal_connect (priv->dbus_skeleton, "handle-callback", G_CALLBACK (handle_callback), plugin);
+
        loop = g_main_loop_new (NULL, FALSE);
 
        if (!persist)
diff --git a/src/nm-libreswan-service.h b/src/nm-libreswan-service.h
index d5c50bc..236a5de 100644
--- a/src/nm-libreswan-service.h
+++ b/src/nm-libreswan-service.h
@@ -24,9 +24,10 @@
 #include <glib.h>
 #include <glib-object.h>
 
-#define NM_DBUS_SERVICE_LIBRESWAN    "org.freedesktop.NetworkManager.libreswan"
-#define NM_DBUS_INTERFACE_LIBRESWAN  "org.freedesktop.NetworkManager.libreswan"
-#define NM_DBUS_PATH_LIBRESWAN       "/org/freedesktop/NetworkManager/libreswan"
+#define NM_DBUS_SERVICE_LIBRESWAN     "org.freedesktop.NetworkManager.libreswan"
+#define NM_DBUS_INTERFACE_LIBRESWAN   "org.freedesktop.NetworkManager.libreswan"
+#define NM_DBUS_PATH_LIBRESWAN        "/org/freedesktop/NetworkManager/libreswan"
+#define NM_DBUS_PATH_LIBRESWAN_HELPER "/org/freedesktop/NetworkManager/libreswan/helper"
 
 #define NM_LIBRESWAN_RIGHT  "right"
 #define NM_LIBRESWAN_LEFTID "leftid"


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