With this option, the server can remove individual options from the
set pushed to a client (call from --client-config-dir file, or from
--client-connect script or plugin).  Options are removed at parse
time, so it is possible to do stuff like:

  push-remove route-ipv6
  push "route-ipv6 fd00::/8"

to first remove all IPv6 route options set so far, then add something
specific (what "push-reset" does to all the options).

Arguments to push-remove are strncmp()'ed to option string, so partial
matches like

  push-remove "route-ipv6 2001:"

are possible ("remove all IPv6 routes starting with 2001:").

Implementation of remove_iroutes_from_push_route_list() had to be changed
slightly to stop it from re-enabling all disabled options again.

v2: documentation (Changes.rst, doc/openvpn.8)
    remove surplus gc_arena
    implement filtering of "ifconfig-ipv6"

v3: correct quoting in commit message
    only handle a single argument per push-remove statement - if multiple
    options are to be removed, just use multiple push-remove statements

Trac #29, #614

Signed-off-by: Gert Doering <g...@greenie.muc.de>
---
 Changes.rst           |  4 ++++
 doc/openvpn.8         | 35 ++++++++++++++++++++++++++++++++++-
 src/openvpn/options.c |  6 ++++++
 src/openvpn/options.h |  1 +
 src/openvpn/push.c    | 47 ++++++++++++++++++++++++++++++++++++++++-------
 src/openvpn/push.h    |  1 +
 6 files changed, 86 insertions(+), 8 deletions(-)

diff --git a/Changes.rst b/Changes.rst
index dc9131b..a6bb2a5 100644
--- a/Changes.rst
+++ b/Changes.rst
@@ -5,6 +5,10 @@ Version 2.4.0
 New features
 ------------

+push-remove
+    new option to remove options on a per-client basis from the "push" list
+    (more fine-grained than "push-reset")
+
 keying-material-exporter
     Keying Material Exporter [RFC-5705] allow additional keying material to be
     derived from existing TLS channel.
diff --git a/doc/openvpn.8 b/doc/openvpn.8
index 7d5dc5b..8d1b062 100644
--- a/doc/openvpn.8
+++ b/doc/openvpn.8
@@ -2965,6 +2965,39 @@ as with a
 configuration file.  This option will ignore
 .B \-\-push
 options at the global config file level.
+.\"*********************************************************
+.TP
+.B \-\-push\-remove opt
+selectively remove all
+.B \-\-push
+options matching "opt" from the option list for a client.  "opt" is matched
+as a substring against the whole option string to-be-pushed to the client, so
+.B \-\-push\-remove route
+would remove all
+.B \-\-push route ...
+and
+.B \-\-push route-ipv6 ...
+statements, while
+.B \-\-push\-remove 'route-ipv6 2001:'
+would only remove IPv6 routes for 2001:... networks.
+
+.B \-\-push\-remove
+can only be used in a client-specific context, like in a
+.B \-\-client\-config\-dir
+file, or
+.B \-\-client\-connect
+script or plugin -- similar to
+.B \-\-push\-reset,
+just more selective.
+
+NOTE: to
+.I change
+an option,
+.B \-\-push\-remove
+can be used to first remove the old value, and then add a new
+.B \-\-push
+option with the new value.
+.\"*********************************************************
 .TP
 .B \-\-push\-peer\-info
 Push additional information about the client to server.  The additional 
information
@@ -3289,7 +3322,7 @@ without needing to restart the server.

 The following
 options are legal in a client-specific context:
-.B \-\-push, \-\-push\-reset, \-\-iroute, \-\-ifconfig\-push,
+.B \-\-push, \-\-push\-reset, \-\-push\-remove, \-\-iroute, \-\-ifconfig\-push,
 and
 .B \-\-config.
 .\"*********************************************************
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 764ca74..55630c7 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -5582,6 +5582,11 @@ add_option (struct options *options,
       VERIFY_PERMISSION (OPT_P_INSTANCE);
       push_reset (options);
     }
+  else if (streq (p[0], "push-remove") && p[1] && !p[2])
+    {
+      VERIFY_PERMISSION (OPT_P_INSTANCE);
+      push_remove_option (options,p[1]);
+    }
   else if (streq (p[0], "ifconfig-pool") && p[1] && p[2] && !p[4])
     {
       const int lev = M_WARN;
@@ -5930,6 +5935,7 @@ add_option (struct options *options,
       options->push_ifconfig_ipv6_local = local;
       options->push_ifconfig_ipv6_netbits = netbits;
       options->push_ifconfig_ipv6_remote = remote;
+      options->push_ifconfig_ipv6_blocked = false;
     }
   else if (streq (p[0], "disable") && !p[1])
     {
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index 18d8376..2fa375f 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -432,6 +432,7 @@ struct options
   struct in6_addr push_ifconfig_ipv6_local;            /* IPv6 */
   int            push_ifconfig_ipv6_netbits;           /* IPv6 */
   struct in6_addr push_ifconfig_ipv6_remote;           /* IPv6 */
+  bool            push_ifconfig_ipv6_blocked;          /* IPv6 */
   bool enable_c2c;
   bool duplicate_cn;
   int cf_max;
diff --git a/src/openvpn/push.c b/src/openvpn/push.c
index be4daa1..38ac59e 100644
--- a/src/openvpn/push.c
+++ b/src/openvpn/push.c
@@ -322,7 +322,8 @@ send_push_reply (struct context *c)

   buf_printf (&buf, "%s", cmd);

-  if ( c->c2.push_ifconfig_ipv6_defined )
+  if ( c->c2.push_ifconfig_ipv6_defined &&
+          !c->options.push_ifconfig_ipv6_blocked )
     {
       /* IPv6 is put into buffer first, could be lengthy */
       buf_printf( &buf, ",ifconfig-ipv6 %s/%d %s",
@@ -483,6 +484,37 @@ push_reset (struct options *o)
 {
   CLEAR (o->push_list);
 }
+
+void
+push_remove_option (struct options *o, const char *p)
+{
+  msg( D_PUSH, "PUSH_REMOVE '%s'", p );
+
+  /* ifconfig-ipv6 is special, as not part of the push list */
+  if ( streq( p, "ifconfig-ipv6" ))
+    {
+      o->push_ifconfig_ipv6_blocked = true;
+      return;
+    }
+
+  if (o && o->push_list.head )
+    {
+      struct push_entry *e = o->push_list.head;
+
+      /* cycle through the push list */
+      while (e)
+       {
+         if ( e->enable &&
+               strncmp( e->option, p, strlen(p) ) == 0 )
+           {
+             msg (D_PUSH, "PUSH_REMOVE removing: '%s'", e->option);
+             e->enable = false;
+           }
+
+         e = e->next;
+       }
+    }
+}
 #endif

 #if P2MP_SERVER
@@ -613,7 +645,8 @@ remove_iroutes_from_push_route_list (struct options *o)

          /* parse the push item */
          CLEAR (p);
-         if (parse_line (e->option, p, SIZE (p), "[PUSH_ROUTE_REMOVE]", 1, 
D_ROUTE_DEBUG, &gc))
+         if ( e->enable &&
+               parse_line (e->option, p, SIZE (p), "[PUSH_ROUTE_REMOVE]", 1, 
D_ROUTE_DEBUG, &gc))
            {
              /* is the push item a route directive? */
              if (p[0] && !strcmp (p[0], "route") && !p[3])
@@ -639,12 +672,12 @@ remove_iroutes_from_push_route_list (struct options *o)
                        }
                    }
                }
-           }

-         /* should we copy the push item? */
-         e->enable = enable;
-         if (!enable)
-           msg (D_PUSH, "REMOVE PUSH ROUTE: '%s'", e->option);
+             /* should we copy the push item? */
+             e->enable = enable;
+             if (!enable)
+               msg (D_PUSH, "REMOVE PUSH ROUTE: '%s'", e->option);
+           }

          e = e->next;
        }
diff --git a/src/openvpn/push.h b/src/openvpn/push.h
index 19bbf5e..d6cb4b1 100644
--- a/src/openvpn/push.h
+++ b/src/openvpn/push.h
@@ -61,6 +61,7 @@ void push_option (struct options *o, const char *opt, int 
msglevel);
 void push_options (struct options *o, char **p, int msglevel, struct gc_arena 
*gc);

 void push_reset (struct options *o);
+void push_remove_option (struct options *o, const char *p);

 void remove_iroutes_from_push_route_list (struct options *o);

-- 
2.7.3


Reply via email to