updown: Use process abstraction to invoke updown script
authorMartin Willi <martin@revosec.ch>
Thu, 2 Oct 2014 09:48:50 +0000 (11:48 +0200)
committerMartin Willi <martin@revosec.ch>
Mon, 6 Oct 2014 16:24:39 +0000 (18:24 +0200)
src/libcharon/plugins/updown/updown_listener.c

index 200f298..1d15cc5 100644 (file)
 
 #define _GNU_SOURCE
 #include <stdio.h>
+#include <unistd.h>
 
 #include "updown_listener.h"
 
+#include <utils/process.h>
 #include <hydra.h>
 #include <daemon.h>
 #include <config/child_cfg.h>
@@ -97,53 +99,84 @@ static char* uncache_iface(private_updown_listener_t *this, u_int32_t reqid)
 }
 
 /**
- * Create variables for handled DNS attributes
+ * Allocate and push a format string to the environment
  */
-static char *make_dns_vars(private_updown_listener_t *this, ike_sa_t *ike_sa)
+static bool push_env(char *envp[], u_int count, char *fmt, ...)
 {
-       enumerator_t *enumerator;
-       host_t *host;
-       int v4 = 0, v6 = 0;
-       char total[512] = "", current[64];
+       int i = 0;
+       char *str;
+       va_list args;
 
-       if (!this->handler)
+       while (envp[i])
        {
-               return strdup("");
+               if (++i + 1 >= count)
+               {
+                       return FALSE;
+               }
        }
+       va_start(args, fmt);
+       if (vasprintf(&str, fmt, args) >= 0)
+       {
+               envp[i] = str;
+       }
+       va_end(args);
+       return envp[i] != NULL;
+}
 
-       enumerator = this->handler->create_dns_enumerator(this->handler,
-                                                                                               ike_sa->get_unique_id(ike_sa));
-       while (enumerator->enumerate(enumerator, &host))
+/**
+ * Free all allocated environment strings
+ */
+static void free_env(char *envp[])
+{
+       int i;
+
+       for (i = 0; envp[i]; i++)
        {
-               switch (host->get_family(host))
+               free(envp[i]);
+       }
+}
+
+/**
+ * Push variables for handled DNS attributes
+ */
+static void push_dns_env(private_updown_listener_t *this, ike_sa_t *ike_sa,
+                                                char *envp[], u_int count)
+{
+       enumerator_t *enumerator;
+       host_t *host;
+       int v4 = 0, v6 = 0;
+
+       if (this->handler)
+       {
+               enumerator = this->handler->create_dns_enumerator(this->handler,
+                                                                                       ike_sa->get_unique_id(ike_sa));
+               while (enumerator->enumerate(enumerator, &host))
                {
-                       case AF_INET:
-                               snprintf(current, sizeof(current),
-                                                "PLUTO_DNS4_%d='%H' ", ++v4, host);
-                               break;
-                       case AF_INET6:
-                               snprintf(current, sizeof(current),
-                                                "PLUTO_DNS6_%d='%H' ", ++v6, host);
-                               break;
-                       default:
-                               continue;
+                       switch (host->get_family(host))
+                       {
+                               case AF_INET:
+                                       push_env(envp, count, "PLUTO_DNS4_%d=%H", ++v4, host);
+                                       break;
+                               case AF_INET6:
+                                       push_env(envp, count, "PLUTO_DNS6_%d=%H", ++v6, host);
+                                       break;
+                               default:
+                                       continue;
+                       }
                }
-               strncat(total, current, sizeof(total) - strlen(total) - 1);
+               enumerator->destroy(enumerator);
        }
-       enumerator->destroy(enumerator);
-
-       return strdup(total);
 }
 
 /**
- * Create variables for local virtual IPs
+ * Push variables for local virtual IPs
  */
-static char *make_vip_vars(private_updown_listener_t *this, ike_sa_t *ike_sa)
+static void push_vip_env(private_updown_listener_t *this, ike_sa_t *ike_sa,
+                                                char *envp[], u_int count)
 {
        enumerator_t *enumerator;
        host_t *host;
        int v4 = 0, v6 = 0;
-       char total[512] = "", current[64];
        bool first = TRUE;
 
        enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, TRUE);
@@ -151,28 +184,22 @@ static char *make_vip_vars(private_updown_listener_t *this, ike_sa_t *ike_sa)
        {
                if (first)
                {       /* legacy variable for first VIP */
-                       snprintf(current, sizeof(current),
-                                                "PLUTO_MY_SOURCEIP='%H' ", host);
-                       strncat(total, current, sizeof(total) - strlen(total) - 1);
+                       first = FALSE;
+                       push_env(envp, count, "PLUTO_MY_SOURCEIP=%H", host);
                }
                switch (host->get_family(host))
                {
                        case AF_INET:
-                               snprintf(current, sizeof(current),
-                                                "PLUTO_MY_SOURCEIP4_%d='%H' ", ++v4, host);
+                               push_env(envp, count, "PLUTO_MY_SOURCEIP4_%d=%H", ++v4, host);
                                break;
                        case AF_INET6:
-                               snprintf(current, sizeof(current),
-                                                "PLUTO_MY_SOURCEIP6_%d='%H' ", ++v6, host);
+                               push_env(envp, count, "PLUTO_MY_SOURCEIP6_%d=%H", ++v6, host);
                                break;
                        default:
                                continue;
                }
-               strncat(total, current, sizeof(total) - strlen(total) - 1);
        }
        enumerator->destroy(enumerator);
-
-       return strdup(total);
 }
 
 /**
@@ -196,240 +223,182 @@ static u_int16_t get_port(traffic_selector_t *me,
        return local ? me->get_from_port(me) : other->get_from_port(other);
 }
 
-METHOD(listener_t, child_updown, bool,
-       private_updown_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
-       bool up)
+/**
+ * Invoke the updown script once for given traffic selectors
+ */
+static void invoke_once(private_updown_listener_t *this, ike_sa_t *ike_sa,
+                                               child_sa_t *child_sa, child_cfg_t *config, bool up,
+                                               traffic_selector_t *my_ts, traffic_selector_t *other_ts)
 {
-       traffic_selector_t *my_ts, *other_ts;
-       enumerator_t *enumerator;
-       child_cfg_t *config;
-       host_t *me, *other;
-       char *script;
+       host_t *me, *other, *host;
+       char *iface;
+       u_int8_t mask;
+       mark_t mark;
+       bool is_host, is_ipv6;
+       int out;
+       FILE *shell;
+       process_t *process;
+       char *envp[128] = {};
 
-       config = child_sa->get_config(child_sa);
-       script = config->get_updown(config);
        me = ike_sa->get_my_host(ike_sa);
        other = ike_sa->get_other_host(ike_sa);
 
-       if (script == NULL)
+       push_env(envp, countof(envp), "PLUTO_VERSION=1.1");
+       is_host = my_ts->is_host(my_ts, me);
+       if (is_host)
        {
-               return TRUE;
+               is_ipv6 = me->get_family(me) == AF_INET6;
        }
-
-       enumerator = child_sa->create_policy_enumerator(child_sa);
-       while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
+       else
        {
-               char command[2048];
-               host_t *my_client, *other_client;
-               u_int8_t my_client_mask, other_client_mask;
-               char *virtual_ip, *iface, *mark_in, *mark_out, *udp_enc, *dns, *xauth;
-               mark_t mark;
-               bool is_host, is_ipv6, use_ipcomp;
-               FILE *shell;
-
-               my_ts->to_subnet(my_ts, &my_client, &my_client_mask);
-               other_ts->to_subnet(other_ts, &other_client, &other_client_mask);
-
-               virtual_ip = make_vip_vars(this, ike_sa);
-
-               /* check for the presence of an inbound mark */
-               mark = config->get_mark(config, TRUE);
-               if (mark.value)
-               {
-                       if (asprintf(&mark_in, "PLUTO_MARK_IN='%u/0x%08x' ",
-                                                mark.value, mark.mask ) < 0)
-                       {
-                               mark_in = NULL;
-                       }
-               }
-               else
-               {
-                       if (asprintf(&mark_in, "") < 0)
-                       {
-                               mark_in = NULL;
-                       }
-               }
-
-               /* check for the presence of an outbound mark */
-               mark = config->get_mark(config, FALSE);
-               if (mark.value)
-               {
-                       if (asprintf(&mark_out, "PLUTO_MARK_OUT='%u/0x%08x' ",
-                                                mark.value, mark.mask ) < 0)
-                       {
-                               mark_out = NULL;
-                       }
-               }
-               else
-               {
-                       if (asprintf(&mark_out, "") < 0)
-                       {
-                               mark_out = NULL;
-                       }
-               }
-
-               /* check for a NAT condition causing ESP_IN_UDP encapsulation */
-               if (ike_sa->has_condition(ike_sa, COND_NAT_ANY))
+               is_ipv6 = my_ts->get_type(my_ts) == TS_IPV6_ADDR_RANGE;
+       }
+       push_env(envp, countof(envp), "PLUTO_VERB=%s%s%s",
+                        up ? "up" : "down",
+                        is_host ? "-host" : "-client",
+                        is_ipv6 ? "-v6" : "");
+       push_env(envp, countof(envp), "PLUTO_CONNECTION=%s",
+                        config->get_name(config));
+       if (up)
+       {
+               if (hydra->kernel_interface->get_interface(hydra->kernel_interface,
+                                                                                                  me, &iface))
                {
-                       if (asprintf(&udp_enc, "PLUTO_UDP_ENC='%u' ",
-                                                other->get_port(other)) < 0)
-                       {
-                               udp_enc = NULL;
-                       }
-
+                       cache_iface(this, child_sa->get_reqid(child_sa), iface);
                }
                else
                {
-                       if (asprintf(&udp_enc, "") < 0)
-                       {
-                               udp_enc = NULL;
-                       }
-
+                       iface = NULL;
                }
+       }
+       else
+       {
+               iface = uncache_iface(this, child_sa->get_reqid(child_sa));
+       }
+       push_env(envp, countof(envp), "PLUTO_INTERFACE=%s",
+                        iface ? iface : "unknown");
+       push_env(envp, countof(envp), "PLUTO_REQID=%u",
+                        child_sa->get_reqid(child_sa));
+       push_env(envp, countof(envp), "PLUTO_PROTO=%s",
+                        child_sa->get_protocol(child_sa) == PROTO_ESP ? "esp" : "ah");
+       push_env(envp, countof(envp), "PLUTO_UNIQUEID=%u",
+                        ike_sa->get_unique_id(ike_sa));
+       push_env(envp, countof(envp), "PLUTO_ME=%H", me);
+       push_env(envp, countof(envp), "PLUTO_MY_ID=%Y", ike_sa->get_my_id(ike_sa));
+       if (my_ts->to_subnet(my_ts, &host, &mask))
+       {
+               push_env(envp, countof(envp), "PLUTO_MY_CLIENT=%+H/%u", host, mask);
+               host->destroy(host);
+       }
+       push_env(envp, countof(envp), "PLUTO_MY_PORT=%u",
+                        get_port(my_ts, other_ts, TRUE));
+       push_env(envp, countof(envp), "PLUTO_MY_PROTOCOL=%u",
+                        my_ts->get_protocol(my_ts));
+       push_env(envp, countof(envp), "PLUTO_PEER=%H", other);
+       push_env(envp, countof(envp), "PLUTO_PEER_ID=%Y",
+                        ike_sa->get_other_id(ike_sa));
+       if (other_ts->to_subnet(other_ts, &host, &mask))
+       {
+               push_env(envp, countof(envp), "PLUTO_PEER_CLIENT=%+H/%u", host, mask);
+               host->destroy(host);
+       }
+       push_env(envp, countof(envp), "PLUTO_PEER_PORT=%u",
+                        get_port(my_ts, other_ts, FALSE));
+       push_env(envp, countof(envp), "PLUTO_PEER_PROTOCOL=%u",
+                        other_ts->get_protocol(other_ts));
+       if (ike_sa->has_condition(ike_sa, COND_EAP_AUTHENTICATED) ||
+               ike_sa->has_condition(ike_sa, COND_XAUTH_AUTHENTICATED))
+       {
+               push_env(envp, countof(envp), "PLUTO_XAUTH_ID=%Y",
+                                ike_sa->get_other_eap_id(ike_sa));
+       }
+       push_vip_env(this, ike_sa, envp, countof(envp));
+       mark = config->get_mark(config, TRUE);
+       if (mark.value)
+       {
+               push_env(envp, countof(envp), "PLUTO_MARK_IN=%u/0x%08x",
+                                mark.value, mark.mask);
+       }
+       mark = config->get_mark(config, FALSE);
+       if (mark.value)
+       {
+               push_env(envp, countof(envp), "PLUTO_MARK_OUT=%u/0x%08x",
+                                mark.value, mark.mask);
+       }
+       if (ike_sa->has_condition(ike_sa, COND_NAT_ANY))
+       {
+               push_env(envp, countof(envp), "PLUTO_UDP_ENC=%u",
+                                other->get_port(other));
+       }
+       if (child_sa->get_ipcomp(child_sa) != IPCOMP_NONE)
+       {
+               push_env(envp, countof(envp), "PLUTO_IPCOMP=1");
+       }
+       push_dns_env(this, ike_sa, envp, countof(envp));
+       if (config->get_hostaccess(config))
+       {
+               push_env(envp, countof(envp), "PLUTO_HOST_ACCESS=1");
+       }
 
-               if (ike_sa->has_condition(ike_sa, COND_EAP_AUTHENTICATED) ||
-                       ike_sa->has_condition(ike_sa, COND_XAUTH_AUTHENTICATED))
-               {
-                       if (asprintf(&xauth, "PLUTO_XAUTH_ID='%Y' ",
-                                                ike_sa->get_other_eap_id(ike_sa)) < 0)
-                       {
-                               xauth = NULL;
-                       }
-               }
-               else
+       process = process_start_shell(envp, NULL, &out, NULL, "2>&1 %s",
+                                                                 config->get_updown(config));
+       if (process)
+       {
+               shell = fdopen(out, "r");
+               if (shell)
                {
-                       if (asprintf(&xauth, "") < 0)
+                       while (TRUE)
                        {
-                               xauth = NULL;
-                       }
-               }
+                               char resp[128];
 
-               if (up)
-               {
-                       if (hydra->kernel_interface->get_interface(hydra->kernel_interface,
-                                                                                                          me, &iface))
-                       {
-                               cache_iface(this, child_sa->get_reqid(child_sa), iface);
-                       }
-                       else
-                       {
-                               iface = NULL;
+                               if (fgets(resp, sizeof(resp), shell) == NULL)
+                               {
+                                       if (ferror(shell))
+                                       {
+                                               DBG1(DBG_CHD, "error reading from updown script");
+                                       }
+                                       break;
+                               }
+                               else
+                               {
+                                       char *e = resp + strlen(resp);
+                                       if (e > resp && e[-1] == '\n')
+                                       {
+                                               e[-1] = '\0';
+                                       }
+                                       DBG1(DBG_CHD, "updown: %s", resp);
+                               }
                        }
+                       fclose(shell);
                }
                else
                {
-                       iface = uncache_iface(this, child_sa->get_reqid(child_sa));
+                       close(out);
                }
+               process->wait(process, NULL);
+       }
+       free(iface);
+       free_env(envp);
+}
 
-               dns = make_dns_vars(this, ike_sa);
-
-               /* check for IPComp */
-               use_ipcomp = child_sa->get_ipcomp(child_sa) != IPCOMP_NONE;
-
-               /* determine IPv4/IPv6 and client/host situation */
-               is_host = my_ts->is_host(my_ts, me);
-               is_ipv6 = is_host ? (me->get_family(me) == AF_INET6) :
-                                                       (my_ts->get_type(my_ts) == TS_IPV6_ADDR_RANGE);
-
-               /* build the command with all env variables.
-                */
-               snprintf(command, sizeof(command),
-                                "2>&1 "
-                               "PLUTO_VERSION='1.1' "
-                               "PLUTO_VERB='%s%s%s' "
-                               "PLUTO_CONNECTION='%s' "
-                               "PLUTO_INTERFACE='%s' "
-                               "PLUTO_REQID='%u' "
-                               "PLUTO_PROTO='%s' "
-                               "PLUTO_UNIQUEID='%u' "
-                               "PLUTO_ME='%H' "
-                               "PLUTO_MY_ID='%Y' "
-                               "PLUTO_MY_CLIENT='%+H/%u' "
-                               "PLUTO_MY_PORT='%u' "
-                               "PLUTO_MY_PROTOCOL='%u' "
-                               "PLUTO_PEER='%H' "
-                               "PLUTO_PEER_ID='%Y' "
-                               "PLUTO_PEER_CLIENT='%+H/%u' "
-                               "PLUTO_PEER_PORT='%u' "
-                               "PLUTO_PEER_PROTOCOL='%u' "
-                               "%s"
-                               "%s"
-                               "%s"
-                               "%s"
-                               "%s"
-                               "%s"
-                               "%s"
-                               "%s"
-                               "%s",
-                                up ? "up" : "down",
-                                is_host ? "-host" : "-client",
-                                is_ipv6 ? "-v6" : "",
-                                config->get_name(config),
-                                iface ? iface : "unknown",
-                                child_sa->get_reqid(child_sa),
-                                child_sa->get_protocol(child_sa) == PROTO_ESP ? "esp" : "ah",
-                                ike_sa->get_unique_id(ike_sa),
-                                me, ike_sa->get_my_id(ike_sa),
-                                my_client, my_client_mask,
-                                get_port(my_ts, other_ts, TRUE),
-                                my_ts->get_protocol(my_ts),
-                                other, ike_sa->get_other_id(ike_sa),
-                                other_client, other_client_mask,
-                                get_port(my_ts, other_ts, FALSE),
-                                other_ts->get_protocol(other_ts),
-                                xauth,
-                                virtual_ip,
-                                mark_in,
-                                mark_out,
-                                udp_enc,
-                                use_ipcomp ? "PLUTO_IPCOMP='1' " : "",
-                                config->get_hostaccess(config) ? "PLUTO_HOST_ACCESS='1' " : "",
-                                dns,
-                                script);
-               my_client->destroy(my_client);
-               other_client->destroy(other_client);
-               free(virtual_ip);
-               free(mark_in);
-               free(mark_out);
-               free(udp_enc);
-               free(dns);
-               free(iface);
-               free(xauth);
-
-               DBG3(DBG_CHD, "running updown script: %s", command);
-               shell = popen(command, "r");
-
-               if (shell == NULL)
-               {
-                       DBG1(DBG_CHD, "could not execute updown script '%s'", script);
-                       return TRUE;
-               }
+METHOD(listener_t, child_updown, bool,
+       private_updown_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
+       bool up)
+{
+       traffic_selector_t *my_ts, *other_ts;
+       enumerator_t *enumerator;
+       child_cfg_t *config;
 
-               while (TRUE)
+       config = child_sa->get_config(child_sa);
+       if (config->get_updown(config))
+       {
+               enumerator = child_sa->create_policy_enumerator(child_sa);
+               while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
                {
-                       char resp[128];
-
-                       if (fgets(resp, sizeof(resp), shell) == NULL)
-                       {
-                               if (ferror(shell))
-                               {
-                                       DBG1(DBG_CHD, "error reading output from updown script");
-                               }
-                               break;
-                       }
-                       else
-                       {
-                               char *e = resp + strlen(resp);
-                               if (e > resp && e[-1] == '\n')
-                               {       /* trim trailing '\n' */
-                                       e[-1] = '\0';
-                               }
-                               DBG1(DBG_CHD, "updown: %s", resp);
-                       }
+                       invoke_once(this, ike_sa, child_sa, config, up, my_ts, other_ts);
                }
-               pclose(shell);
+               enumerator->destroy(enumerator);
        }
-       enumerator->destroy(enumerator);
        return TRUE;
 }