Browse code

PUSH_UPDATE: Allow OpenVPN in client mode to receive and handle PUSH UPDATE control messages to allow options updating at runtime.

* Added IV_PROTO_PUSH_UPDATE flag bit to support push-updates.
* Added process_incoming_push_update(), in a separate file to create tests more easily.
* Modified incoming_push_message(), process_incoming_push_msg(), apply_push_options(),
apply_pull_filter() to process also push-update messages.
* Added the check_push_update_option_flags() function used in apply_pull_filter() to
check options formatting inside push-update messages, if the options are updatables
and to check for '?' and '-' flags that may be present in front of the options.
The '-' flag is used to indicate that the option in question should be removed,
while the '?' indicates that the option is optional and to do not generate
errors if the client cannot update that option.
For more info you can read the RFC at https://github.com/OpenVPN/openvpn-rfc .
* Created some unit tests for the push-update message handling in test_push_update_msg.c.

Change-Id: I6ecd4cb47571cc8c20e46de8595c742aeec6064a
Signed-off-by: Marco Baffo <marco@mandelbit.com>
Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <20250729104045.27582-1-gert@greenie.muc.de>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32406.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>

Marco Baffo authored on 2025/07/29 19:40:39
Showing 15 changed files
... ...
@@ -534,6 +534,7 @@ set(SOURCE_FILES
534 534
     src/openvpn/ps.c
535 535
     src/openvpn/ps.h
536 536
     src/openvpn/push.c
537
+    src/openvpn/push_util.c
537 538
     src/openvpn/push.h
538 539
     src/openvpn/pushlist.h
539 540
     src/openvpn/reflect_filter.c
... ...
@@ -652,6 +653,7 @@ if (BUILD_TESTING)
652 652
         "test_provider"
653 653
         "test_ssl"
654 654
         "test_user_pass"
655
+        "test_push_update_msg"
655 656
         )
656 657
 
657 658
     if (WIN32)
... ...
@@ -854,6 +856,14 @@ if (BUILD_TESTING)
854 854
         src/openvpn/run_command.c
855 855
         )
856 856
 
857
+    target_sources(test_push_update_msg PRIVATE
858
+        tests/unit_tests/openvpn/mock_msg.c
859
+        tests/unit_tests/openvpn/mock_get_random.c
860
+	    src/openvpn/push_util.c
861
+	    src/openvpn/options_util.c
862
+	    src/openvpn/otime.c
863
+        )
864
+
857 865
     if (TARGET test_argv)
858 866
         target_link_options(test_argv PRIVATE -Wl,--wrap=parse_line)
859 867
         target_sources(test_argv PRIVATE
... ...
@@ -117,7 +117,7 @@ openvpn_SOURCES = \
117 117
 	proto.c proto.h \
118 118
 	proxy.c proxy.h \
119 119
 	ps.c ps.h \
120
-	push.c push.h \
120
+	push.c push_util.c push.h \
121 121
 	pushlist.h \
122 122
 	reflect_filter.c reflect_filter.h \
123 123
 	reliable.c reliable.h \
... ...
@@ -2064,7 +2064,7 @@ pre_select(struct context *c)
2064 2064
     }
2065 2065
 
2066 2066
     /* check for incoming control messages on the control channel like
2067
-     * push request/reply, or authentication failure and 2FA messages */
2067
+     * push request/reply/update, or authentication failure and 2FA messages */
2068 2068
     if (tls_test_payload_len(c->c2.tls_multi) > 0)
2069 2069
     {
2070 2070
         check_incoming_control_channel(c);
... ...
@@ -2051,8 +2051,8 @@ do_open_tun(struct context *c, int *error_flags)
2051 2051
         /* possibly add routes */
2052 2052
         if ((route_order(c->c1.tuntap) == ROUTE_AFTER_TUN) && (!c->options.route_delay_defined))
2053 2053
         {
2054
-            int status = do_route(&c->options, c->c1.route_list, c->c1.route_ipv6_list,
2055
-                                  c->c1.tuntap, c->plugins, c->c2.es, &c->net_ctx);
2054
+            bool status = do_route(&c->options, c->c1.route_list, c->c1.route_ipv6_list,
2055
+                                   c->c1.tuntap, c->plugins, c->c2.es, &c->net_ctx);
2056 2056
             *error_flags |= (status ? 0 : ISC_ROUTE_ERRORS);
2057 2057
         }
2058 2058
 
... ...
@@ -66,6 +66,7 @@
66 66
 #include <ctype.h>
67 67
 
68 68
 #include "memdbg.h"
69
+#include "options_util.h"
69 70
 
70 71
 const char title_string[] =
71 72
     PACKAGE_STRING
... ...
@@ -948,24 +949,6 @@ uninit_options(struct options *o)
948 948
     }
949 949
 }
950 950
 
951
-struct pull_filter
952
-{
953
-#define PUF_TYPE_UNDEF  0    /**< undefined filter type */
954
-#define PUF_TYPE_ACCEPT 1    /**< filter type to accept a matching option */
955
-#define PUF_TYPE_IGNORE 2    /**< filter type to ignore a matching option */
956
-#define PUF_TYPE_REJECT 3    /**< filter type to reject and trigger SIGUSR1 */
957
-    int type;
958
-    int size;
959
-    char *pattern;
960
-    struct pull_filter *next;
961
-};
962
-
963
-struct pull_filter_list
964
-{
965
-    struct pull_filter *head;
966
-    struct pull_filter *tail;
967
-};
968
-
969 951
 #ifndef ENABLE_SMALL
970 952
 
971 953
 static const char *
... ...
@@ -5503,60 +5486,14 @@ parse_argv(struct options *options,
5503 5503
     }
5504 5504
 }
5505 5505
 
5506
-/**
5507
- * Filter an option line by all pull filters.
5508
- *
5509
- * If a match is found, the line is modified depending on
5510
- * the filter type, and returns true. If the filter type is
5511
- * reject, SIGUSR1 is triggered and the return value is false.
5512
- * In that case the caller must end the push processing.
5513
- */
5514
-static bool
5515
-apply_pull_filter(const struct options *o, char *line)
5516
-{
5517
-    struct pull_filter *f;
5518
-
5519
-    if (!o->pull_filter_list)
5520
-    {
5521
-        return true;
5522
-    }
5523
-
5524
-    /* skip leading spaces matching the behaviour of parse_line */
5525
-    while (isspace(*line))
5526
-    {
5527
-        line++;
5528
-    }
5529
-
5530
-    for (f = o->pull_filter_list->head; f; f = f->next)
5531
-    {
5532
-        if (f->type == PUF_TYPE_ACCEPT && strncmp(line, f->pattern, f->size) == 0)
5533
-        {
5534
-            msg(D_LOW, "Pushed option accepted by filter: '%s'", line);
5535
-            return true;
5536
-        }
5537
-        else if (f->type == PUF_TYPE_IGNORE && strncmp(line, f->pattern, f->size) == 0)
5538
-        {
5539
-            msg(D_PUSH, "Pushed option removed by filter: '%s'", line);
5540
-            *line = '\0';
5541
-            return true;
5542
-        }
5543
-        else if (f->type == PUF_TYPE_REJECT && strncmp(line, f->pattern, f->size) == 0)
5544
-        {
5545
-            msg(M_WARN, "Pushed option rejected by filter: '%s'. Restarting.", line);
5546
-            *line = '\0';
5547
-            throw_signal_soft(SIGUSR1, "Offending option received from server");
5548
-            return false;
5549
-        }
5550
-    }
5551
-    return true;
5552
-}
5553
-
5554 5506
 bool
5555
-apply_push_options(struct options *options,
5507
+apply_push_options(struct context *c,
5508
+                   struct options *options,
5556 5509
                    struct buffer *buf,
5557 5510
                    unsigned int permission_mask,
5558 5511
                    unsigned int *option_types_found,
5559
-                   struct env_set *es)
5512
+                   struct env_set *es,
5513
+                   bool is_update)
5560 5514
 {
5561 5515
     char line[OPTION_PARM_SIZE];
5562 5516
     int line_num = 0;
... ...
@@ -5568,14 +5505,40 @@ apply_push_options(struct options *options,
5568 5568
         char *p[MAX_PARMS+1];
5569 5569
         CLEAR(p);
5570 5570
         ++line_num;
5571
-        if (!apply_pull_filter(options, line))
5571
+        unsigned int push_update_option_flags = 0;
5572
+        int i = 0;
5573
+
5574
+        /* skip leading spaces matching the behaviour of parse_line */
5575
+        while (isspace(line[i]))
5572 5576
         {
5577
+            i++;
5578
+        }
5579
+
5580
+        /* If we are not in a 'PUSH_UPDATE' we just check `apply_pull_filter()`
5581
+         * otherwise we must call `check_push_update_option_flags()` first
5582
+         */
5583
+        if ((is_update && !check_push_update_option_flags(line, &i, &push_update_option_flags))
5584
+            || !apply_pull_filter(options, &line[i]))
5585
+        {
5586
+            /* In case we are in a `PUSH_UPDATE` and `check_push_update_option_flags()`
5587
+             * or `apply_pull_filter()` fail but the option is flagged by `PUSH_OPT_OPTIONAL`,
5588
+             * instead of restarting, we just ignore the option and we process the next one
5589
+             */
5590
+            if (push_update_option_flags & PUSH_OPT_OPTIONAL)
5591
+            {
5592
+                continue; /* Ignoring this option */
5593
+            }
5594
+            throw_signal_soft(SIGUSR1, "Offending option received from server");
5573 5595
             return false; /* Cause push/pull error and stop push processing */
5574 5596
         }
5575
-        if (parse_line(line, p, SIZE(p)-1, file, line_num, msglevel, &options->gc))
5597
+
5598
+        if (parse_line(&line[i], p, SIZE(p)-1, file, line_num, msglevel, &options->gc))
5576 5599
         {
5577
-            add_option(options, p, false, file, line_num, 0, msglevel,
5578
-                       permission_mask, option_types_found, es);
5600
+            if (!is_update)
5601
+            {
5602
+                add_option(options, p, false, file, line_num, 0, msglevel,
5603
+                           permission_mask, option_types_found, es);
5604
+            }
5579 5605
         }
5580 5606
     }
5581 5607
     return true;
... ...
@@ -794,6 +794,33 @@ struct options
794 794
 #define MAN_CLIENT_AUTH_ENABLED(opt) (false)
795 795
 #endif
796 796
 
797
+/*
798
+ * some PUSH_UPDATE options
799
+ */
800
+#define OPT_P_U_ROUTE           (1<<0)
801
+#define OPT_P_U_ROUTE6          (1<<1)
802
+#define OPT_P_U_DNS             (1<<2)
803
+#define OPT_P_U_DHCP            (1<<3)
804
+#define OPT_P_U_REDIR_GATEWAY   (1<<4)
805
+
806
+struct pull_filter
807
+{
808
+#define PUF_TYPE_UNDEF  0    /**< undefined filter type */
809
+#define PUF_TYPE_ACCEPT 1    /**< filter type to accept a matching option */
810
+#define PUF_TYPE_IGNORE 2    /**< filter type to ignore a matching option */
811
+#define PUF_TYPE_REJECT 3    /**< filter type to reject and trigger SIGUSR1 */
812
+    int type;
813
+    int size;
814
+    char *pattern;
815
+    struct pull_filter *next;
816
+};
817
+
818
+struct pull_filter_list
819
+{
820
+    struct pull_filter *head;
821
+    struct pull_filter *tail;
822
+};
823
+
797 824
 void parse_argv(struct options *options,
798 825
                 const int argc,
799 826
                 char *argv[],
... ...
@@ -862,11 +889,13 @@ bool options_postprocess_pull(struct options *o, struct env_set *es);
862 862
 
863 863
 void pre_connect_restore(struct options *o, struct gc_arena *gc);
864 864
 
865
-bool apply_push_options(struct options *options,
865
+bool apply_push_options(struct context *c,
866
+                        struct options *options,
866 867
                         struct buffer *buf,
867 868
                         unsigned int permission_mask,
868 869
                         unsigned int *option_types_found,
869
-                        struct env_set *es);
870
+                        struct env_set *es,
871
+                        bool is_update);
870 872
 
871 873
 void options_detach(struct options *o);
872 874
 
... ...
@@ -30,6 +30,8 @@
30 30
 
31 31
 #include "options_util.h"
32 32
 
33
+#include "push.h"
34
+
33 35
 const char *
34 36
 parse_auth_failed_temp(struct options *o, const char *reason)
35 37
 {
... ...
@@ -145,3 +147,116 @@ atoi_warn(const char *str, int msglevel)
145 145
 
146 146
     return (int) i;
147 147
 }
148
+
149
+static const char *updatable_options[] = {
150
+    "block-ipv6",
151
+    "block-outside-dns",
152
+    "dhcp-option",
153
+    "dns",
154
+    "ifconfig",
155
+    "ifconfig-ipv6",
156
+    "push-continuation",
157
+    "redirect-gateway",
158
+    "redirect-private",
159
+    "route",
160
+    "route-gateway",
161
+    "route-ipv6",
162
+    "route-metric",
163
+    "topology",
164
+    "tun-mtu",
165
+    "keepalive"
166
+};
167
+
168
+bool
169
+check_push_update_option_flags(char *line, int *i, unsigned int *flags)
170
+{
171
+    *flags = 0;
172
+    bool opt_is_updatable = false;
173
+    char c = line[*i];
174
+
175
+    /* We check for '?' and '-' and
176
+     * if they are present we skip them.
177
+     */
178
+    if (c == '-')
179
+    {
180
+        if (!(line)[*i + 1])
181
+        {
182
+            return false;
183
+        }
184
+        *flags |= PUSH_OPT_TO_REMOVE;
185
+        c = (line)[++(*i)];
186
+    }
187
+    if (c == '?')
188
+    {
189
+        if (!(line)[*i + 1] || (line)[*i + 1] == '-')
190
+        {
191
+            return false;
192
+        }
193
+        *flags |= PUSH_OPT_OPTIONAL;
194
+        c = (line)[++(*i)];
195
+    }
196
+
197
+    size_t len = strlen(&line[*i]);
198
+    int count = sizeof(updatable_options)/sizeof(char *);
199
+    for (int j = 0; j < count; ++j)
200
+    {
201
+        size_t opt_len = strlen(updatable_options[j]);
202
+        if (len < opt_len)
203
+        {
204
+            continue;
205
+        }
206
+        if (!strncmp(&line[*i], updatable_options[j], opt_len)
207
+            && (!line[*i + opt_len] || line[*i + opt_len] == ' '))
208
+        {
209
+            opt_is_updatable = true;
210
+            break;
211
+        }
212
+    }
213
+
214
+    if (!opt_is_updatable)
215
+    {
216
+        if (*flags & PUSH_OPT_OPTIONAL)
217
+        {
218
+            msg(D_PUSH, "Pushed option is not updatable: '%s'. Ignoring.", line);
219
+        }
220
+        else
221
+        {
222
+            msg(M_WARN, "Pushed option is not updatable: '%s'. Restarting.", line);
223
+            return false;
224
+        }
225
+    }
226
+
227
+    return true;
228
+}
229
+
230
+bool
231
+apply_pull_filter(const struct options *o, char *line)
232
+{
233
+    if (!o->pull_filter_list)
234
+    {
235
+        return true;
236
+    }
237
+
238
+    struct pull_filter *f;
239
+
240
+    for (f = o->pull_filter_list->head; f; f = f->next)
241
+    {
242
+        if (f->type == PUF_TYPE_ACCEPT && strncmp(line, f->pattern, f->size) == 0)
243
+        {
244
+            msg(D_LOW, "Pushed option accepted by filter: '%s'", line);
245
+            return true;
246
+        }
247
+        else if (f->type == PUF_TYPE_IGNORE && strncmp(line, f->pattern, f->size) == 0)
248
+        {
249
+            msg(D_PUSH, "Pushed option removed by filter: '%s'", line);
250
+            *line = '\0';
251
+            return true;
252
+        }
253
+        else if (f->type == PUF_TYPE_REJECT && strncmp(line, f->pattern, f->size) == 0)
254
+        {
255
+            msg(M_WARN, "Pushed option rejected by filter: '%s'.", line);
256
+            return false;
257
+        }
258
+    }
259
+    return true;
260
+}
... ...
@@ -50,4 +50,47 @@ positive_atoi(const char *str, int msglevel);
50 50
 int
51 51
 atoi_warn(const char *str, int msglevel);
52 52
 
53
+/**
54
+ * Filter an option line by all pull filters.
55
+ *
56
+ * If a match is found, the line is modified depending on
57
+ * the filter type, and returns true. If the filter type is
58
+ * reject, SIGUSR1 is triggered and the return value is false.
59
+ * In that case the caller must end the push processing.
60
+ */
61
+bool
62
+apply_pull_filter(const struct options *o,
63
+                  char *line);
64
+
65
+/**
66
+ * @brief Checks the formatting and validity of options inside push-update messages.
67
+ *
68
+ * This function is used to validate and process options
69
+ * in push-update messages. It performs the following checks:
70
+ * - Determines if the options are updatable.
71
+ * - Checks for the presence of the `-` flag, which indicates that the option
72
+ *   should be removed.
73
+ * - Checks for the `?` flag, which marks the option as optional and suppresses
74
+ *   errors if the client cannot update it.
75
+ * - Increase the value pointed by 'i' when we encounter the `'-'` and `'?'` flags
76
+ *   after validating them and updating the appropriate flags in the `flags` variable.
77
+ * - `-?option`, `-option`, `?option` are valid formats, `?-option` is not a valid format.
78
+ * - If the flags and the option are not consecutive, the option is invalid:
79
+ *   `- ?option`, `-? option`, `- option` are invalid formats.
80
+ *
81
+ * @param line A pointer to an option string. This string is the option being validated.
82
+ * @param i A pointer to an integer that represents the current index in the `line` string.
83
+ * @param flags A pointer where flags will be stored:
84
+ *              - `PUSH_OPT_TO_REMOVE`: Set if the `-` flag is present.
85
+ *              - `PUSH_OPT_OPTIONAL`: Set if the `?` flag is present.
86
+ *
87
+ * @return true if the flags and option combination are valid.
88
+ * @return false if:
89
+ *         - The `-` and `?` flags are not formatted correctly.
90
+ *         - The `line` parameter is empty or `NULL`.
91
+ *         - The `?` flag is absent and the option is not updatable.
92
+ */
93
+bool
94
+check_push_update_option_flags(char *line, int *i, unsigned int *flags);
95
+
53 96
 #endif /* ifndef OPTIONS_UTIL_H_ */
... ...
@@ -39,8 +39,6 @@
39 39
 #include "ssl_util.h"
40 40
 #include "options_util.h"
41 41
 
42
-static char push_reply_cmd[] = "PUSH_REPLY";
43
-
44 42
 /*
45 43
  * Auth username/password
46 44
  *
... ...
@@ -519,21 +517,31 @@ incoming_push_message(struct context *c, const struct buffer *buffer)
519 519
     {
520 520
         msg(D_PUSH_ERRORS, "WARNING: Received bad push/pull message: %s", sanitize_control_message(BSTR(buffer), &gc));
521 521
     }
522
-    else if (status == PUSH_MSG_REPLY || status == PUSH_MSG_CONTINUATION)
522
+    else if (status == PUSH_MSG_REPLY || status == PUSH_MSG_UPDATE || status == PUSH_MSG_CONTINUATION)
523 523
     {
524 524
         c->options.push_option_types_found |= option_types_found;
525 525
 
526 526
         /* delay bringing tun/tap up until --push parms received from remote */
527
-        if (status == PUSH_MSG_REPLY)
527
+        if (status == PUSH_MSG_REPLY || status == PUSH_MSG_UPDATE)
528 528
         {
529 529
             if (!options_postprocess_pull(&c->options, c->c2.es))
530 530
             {
531 531
                 goto error;
532 532
             }
533
-            if (!do_up(c, true, c->options.push_option_types_found))
533
+            if (status == PUSH_MSG_REPLY)
534 534
             {
535
-                msg(D_PUSH_ERRORS, "Failed to open tun/tap interface");
536
-                goto error;
535
+                if (!do_up(c, true, c->options.push_option_types_found))
536
+                {
537
+                    msg(D_PUSH_ERRORS, "Failed to open tun/tap interface");
538
+                    goto error;
539
+                }
540
+            }
541
+            else
542
+            {
543
+                if (!option_types_found)
544
+                {
545
+                    msg(M_WARN, "No updatable options found in incoming PUSH_UPDATE message");
546
+                }
537 547
             }
538 548
         }
539 549
         event_timeout_clear(&c->c2.push_request_interval);
... ...
@@ -1056,11 +1064,13 @@ process_incoming_push_reply(struct context *c,
1056 1056
             md_ctx_init(c->c2.pulled_options_state, "SHA256");
1057 1057
             c->c2.pulled_options_digest_init_done = true;
1058 1058
         }
1059
-        if (apply_push_options(&c->options,
1059
+        if (apply_push_options(c,
1060
+                               &c->options,
1060 1061
                                buf,
1061 1062
                                permission_mask,
1062 1063
                                option_types_found,
1063
-                               c->c2.es))
1064
+                               c->c2.es,
1065
+                               false))
1064 1066
         {
1065 1067
             push_update_digest(c->c2.pulled_options_state, &buf_orig,
1066 1068
                                &c->options);
... ...
@@ -1111,6 +1121,12 @@ process_incoming_push_msg(struct context *c,
1111 1111
         return process_incoming_push_reply(c, permission_mask,
1112 1112
                                            option_types_found, &buf);
1113 1113
     }
1114
+    else if (honor_received_options
1115
+             && buf_string_compare_advance(&buf, push_update_cmd))
1116
+    {
1117
+        return process_incoming_push_update(c, permission_mask,
1118
+                                            option_types_found, &buf);
1119
+    }
1114 1120
     else
1115 1121
     {
1116 1122
         return PUSH_MSG_ERROR;
... ...
@@ -33,9 +33,42 @@
33 33
 #define PUSH_MSG_AUTH_FAILURE     4
34 34
 #define PUSH_MSG_CONTINUATION     5
35 35
 #define PUSH_MSG_ALREADY_REPLIED  6
36
+#define PUSH_MSG_UPDATE           7
37
+
38
+#define push_reply_cmd "PUSH_REPLY"
39
+#define push_update_cmd "PUSH_UPDATE"
40
+
41
+/* Push-update options flags */
42
+#define PUSH_OPT_TO_REMOVE (1<<0)
43
+#define PUSH_OPT_OPTIONAL (1<<1)
36 44
 
37 45
 int process_incoming_push_request(struct context *c);
38 46
 
47
+/**
48
+ * @brief Handles the receiving of a push-update message and applies updates to the specified options.
49
+ *
50
+ * This function processes a push-update message, validating its content and applying updates
51
+ * to the options specified in the message. It also handles split messages if the complete
52
+ * message has not yet been received.
53
+ *
54
+ * @param c The context for the operation.
55
+ * @param permission_mask The permission mask specifying which options are allowed to be pulled.
56
+ * @param option_types_found A pointer to a variable that will be filled with the types of options
57
+ *                           found in the message.
58
+ * @param buf A buffer containing the received message.
59
+ *
60
+ * @return
61
+ * - `PUSH_MSG_UPDATE`: The message was processed successfully, and the updates were applied.
62
+ * - `PUSH_MSG_CONTINUATION`: The message is a fragment of a larger message, and the program is
63
+ *                            waiting for the final part.
64
+ * - `PUSH_MSG_ERROR`: An error occurred during message processing, or the message is invalid.
65
+ */
66
+
67
+int process_incoming_push_update(struct context *c,
68
+                                 unsigned int permission_mask,
69
+                                 unsigned int *option_types_found,
70
+                                 struct buffer *buf);
71
+
39 72
 int process_incoming_push_msg(struct context *c,
40 73
                               const struct buffer *buffer,
41 74
                               bool honor_received_options,
42 75
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+#ifdef HAVE_CONFIG_H
1
+#include "config.h"
2
+#endif
3
+
4
+#include "push.h"
5
+
6
+int
7
+process_incoming_push_update(struct context *c,
8
+                             unsigned int permission_mask,
9
+                             unsigned int *option_types_found,
10
+                             struct buffer *buf)
11
+{
12
+    int ret = PUSH_MSG_ERROR;
13
+    const uint8_t ch = buf_read_u8(buf);
14
+    if (ch == ',')
15
+    {
16
+        if (apply_push_options(c,
17
+                               &c->options,
18
+                               buf,
19
+                               permission_mask,
20
+                               option_types_found,
21
+                               c->c2.es,
22
+                               true))
23
+        {
24
+            switch (c->options.push_continuation)
25
+            {
26
+                case 0:
27
+                case 1:
28
+                    ret = PUSH_MSG_UPDATE;
29
+                    break;
30
+
31
+                case 2:
32
+                    ret = PUSH_MSG_CONTINUATION;
33
+                    break;
34
+            }
35
+        }
36
+    }
37
+    else if (ch == '\0')
38
+    {
39
+        ret = PUSH_MSG_UPDATE;
40
+    }
41
+
42
+    return ret;
43
+}
... ...
@@ -1993,6 +1993,9 @@ push_peer_info(struct buffer *buf, struct tls_session *session)
1993 1993
         /* support for exit notify via control channel */
1994 1994
         iv_proto |= IV_PROTO_CC_EXIT_NOTIFY;
1995 1995
 
1996
+        /* support push-updates */
1997
+        iv_proto |= IV_PROTO_PUSH_UPDATE;
1998
+
1996 1999
         if (session->opt->pull)
1997 2000
         {
1998 2001
             /* support for receiving push_reply before sending
... ...
@@ -114,6 +114,9 @@
114 114
 /** Supports the --dns option after all the incompatible changes */
115 115
 #define IV_PROTO_DNS_OPTION_V2   (1<<11)
116 116
 
117
+/** Supports push-update */
118
+#define IV_PROTO_PUSH_UPDATE     (1<<12)
119
+
117 120
 /* Default field in X509 to be username */
118 121
 #define X509_USERNAME_FIELD_DEFAULT "CN"
119 122
 
... ...
@@ -11,7 +11,7 @@ test_binaries += argv_testdriver buffer_testdriver
11 11
 endif
12 12
 
13 13
 test_binaries += crypto_testdriver packet_id_testdriver auth_token_testdriver ncp_testdriver misc_testdriver \
14
-	pkt_testdriver ssl_testdriver user_pass_testdriver
14
+	pkt_testdriver ssl_testdriver user_pass_testdriver push_update_msg_testdriver
15 15
 
16 16
 if HAVE_LD_WRAP_SUPPORT
17 17
 if !WIN32
... ...
@@ -326,3 +326,21 @@ misc_testdriver_SOURCES = test_misc.c \
326 326
 	$(top_srcdir)/src/openvpn/win32-util.c \
327 327
 	$(top_srcdir)/src/openvpn/platform.c \
328 328
 	$(top_srcdir)/src/openvpn/list.c
329
+
330
+push_update_msg_testdriver_CFLAGS = -I$(top_srcdir)/src/openvpn \
331
+	-I$(top_srcdir)/src/compat \
332
+	-I$(top_srcdir)/tests/unit_tests/openvpn \
333
+	@TEST_CFLAGS@
334
+
335
+push_update_msg_testdriver_LDFLAGS = \
336
+	@TEST_LDFLAGS@ \
337
+	-L$(top_srcdir)/src/openvpn
338
+
339
+push_update_msg_testdriver_SOURCES = test_push_update_msg.c \
340
+	mock_msg.c \
341
+	mock_get_random.c \
342
+	$(top_srcdir)/src/openvpn/buffer.c \
343
+	$(top_srcdir)/src/openvpn/platform.c \
344
+	$(top_srcdir)/src/openvpn/push_util.c \
345
+	$(top_srcdir)/src/openvpn/options_util.c \
346
+	$(top_srcdir)/src/openvpn/otime.c
329 347
\ No newline at end of file
330 348
new file mode 100644
... ...
@@ -0,0 +1,260 @@
0
+#ifdef HAVE_CONFIG_H
1
+#include "config.h"
2
+#endif
3
+
4
+#include <stdlib.h>
5
+#include <stdarg.h>
6
+#include <setjmp.h>
7
+#include <cmocka.h>
8
+#include "push.h"
9
+#include "options_util.h"
10
+
11
+/* mocks */
12
+
13
+unsigned int
14
+pull_permission_mask(const struct context *c)
15
+{
16
+    unsigned int flags =
17
+        OPT_P_UP
18
+        | OPT_P_ROUTE_EXTRAS
19
+        | OPT_P_SOCKBUF
20
+        | OPT_P_SOCKFLAGS
21
+        | OPT_P_SETENV
22
+        | OPT_P_SHAPER
23
+        | OPT_P_TIMER
24
+        | OPT_P_COMP
25
+        | OPT_P_PERSIST
26
+        | OPT_P_MESSAGES
27
+        | OPT_P_EXPLICIT_NOTIFY
28
+        | OPT_P_ECHO
29
+        | OPT_P_PULL_MODE
30
+        | OPT_P_PEER_ID
31
+        | OPT_P_NCP
32
+        | OPT_P_PUSH_MTU
33
+        | OPT_P_ROUTE
34
+        | OPT_P_DHCPDNS;
35
+    return flags;
36
+}
37
+
38
+bool
39
+apply_push_options(struct context *c,
40
+                   struct options *options,
41
+                   struct buffer *buf,
42
+                   unsigned int permission_mask,
43
+                   unsigned int *option_types_found,
44
+                   struct env_set *es,
45
+                   bool is_update)
46
+{
47
+    char line[OPTION_PARM_SIZE];
48
+
49
+    while (buf_parse(buf, ',', line, sizeof(line)))
50
+    {
51
+        unsigned int push_update_option_flags = 0;
52
+        int i = 0;
53
+
54
+        if (is_update || options->pull_filter_list)
55
+        {
56
+            /* skip leading spaces matching the behaviour of parse_line */
57
+            while (isspace(line[i]))
58
+            {
59
+                i++;
60
+            }
61
+
62
+            if ((is_update && !check_push_update_option_flags(line, &i, &push_update_option_flags))
63
+                || (options->pull_filter_list && !apply_pull_filter(options, &line[i])))
64
+            {
65
+                if (push_update_option_flags & PUSH_OPT_OPTIONAL)
66
+                {
67
+                    continue; /* Ignoring this option */
68
+                }
69
+                msg(M_WARN, "Offending option received from server");
70
+                return false; /* Cause push/pull error and stop push processing */
71
+            }
72
+        }
73
+        /*
74
+         * No need to test also the application part here
75
+         * (add_option/remove_option/update_option)
76
+         */
77
+    }
78
+    return true;
79
+}
80
+
81
+int
82
+process_incoming_push_msg(struct context *c,
83
+                          const struct buffer *buffer,
84
+                          bool honor_received_options,
85
+                          unsigned int permission_mask,
86
+                          unsigned int *option_types_found)
87
+{
88
+    struct buffer buf = *buffer;
89
+
90
+    if (buf_string_compare_advance(&buf, "PUSH_REQUEST"))
91
+    {
92
+        return PUSH_MSG_REQUEST;
93
+    }
94
+    else if (honor_received_options
95
+             && buf_string_compare_advance(&buf, push_reply_cmd))
96
+    {
97
+        return PUSH_MSG_REPLY;
98
+    }
99
+    else if (honor_received_options
100
+             && buf_string_compare_advance(&buf, push_update_cmd))
101
+    {
102
+        return process_incoming_push_update(c, permission_mask,
103
+                                            option_types_found, &buf);
104
+    }
105
+    else
106
+    {
107
+        return PUSH_MSG_ERROR;
108
+    }
109
+}
110
+
111
+/* tests */
112
+
113
+static void
114
+test_incoming_push_message_basic(void **state)
115
+{
116
+    struct context *c = *state;
117
+    struct buffer buf = alloc_buf(256);
118
+    const char *update_msg = "PUSH_UPDATE,dhcp-option DNS 8.8.8.8, route 0.0.0.0 0.0.0.0 10.10.10.1";
119
+    buf_write(&buf, update_msg, strlen(update_msg));
120
+    unsigned int option_types_found = 0;
121
+
122
+    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c), &option_types_found), PUSH_MSG_UPDATE);
123
+
124
+    free_buf(&buf);
125
+}
126
+
127
+static void
128
+test_incoming_push_message_error1(void **state)
129
+{
130
+    struct context *c = *state;
131
+    struct buffer buf = alloc_buf(256);
132
+    const char *update_msg = "PUSH_UPDATEerr,dhcp-option DNS 8.8.8.8";
133
+    buf_write(&buf, update_msg, strlen(update_msg));
134
+    unsigned int option_types_found = 0;
135
+
136
+    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c), &option_types_found), PUSH_MSG_ERROR);
137
+
138
+    free_buf(&buf);
139
+}
140
+
141
+
142
+static void
143
+test_incoming_push_message_error2(void **state)
144
+{
145
+    struct context *c = *state;
146
+    struct buffer buf = alloc_buf(256);
147
+    const char *update_msg = "PUSH_UPDATE ,dhcp-option DNS 8.8.8.8";
148
+    buf_write(&buf, update_msg, strlen(update_msg));
149
+    unsigned int option_types_found = 0;
150
+
151
+    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c), &option_types_found), PUSH_MSG_ERROR);
152
+
153
+    free_buf(&buf);
154
+}
155
+
156
+static void
157
+test_incoming_push_message_1(void **state)
158
+{
159
+    struct context *c = *state;
160
+    struct buffer buf = alloc_buf(256);
161
+    const char *update_msg = "PUSH_UPDATE, -?dns, route something, ?dhcp-option DNS 8.8.8.8";
162
+    buf_write(&buf, update_msg, strlen(update_msg));
163
+    unsigned int option_types_found = 0;
164
+
165
+    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c), &option_types_found), PUSH_MSG_UPDATE);
166
+
167
+    free_buf(&buf);
168
+}
169
+
170
+static void
171
+test_incoming_push_message_bad_format(void **state)
172
+{
173
+    struct context *c = *state;
174
+    struct buffer buf = alloc_buf(256);
175
+    const char *update_msg = "PUSH_UPDATE, -dhcp-option, ?-dns";
176
+    buf_write(&buf, update_msg, strlen(update_msg));
177
+    unsigned int option_types_found = 0;
178
+
179
+    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c), &option_types_found), PUSH_MSG_ERROR);
180
+
181
+    free_buf(&buf);
182
+}
183
+
184
+static void
185
+test_incoming_push_message_not_updatable_option(void **state)
186
+{
187
+    struct context *c = *state;
188
+    struct buffer buf = alloc_buf(256);
189
+    const char *update_msg = "PUSH_UPDATE, dev tun";
190
+    buf_write(&buf, update_msg, strlen(update_msg));
191
+    unsigned int option_types_found = 0;
192
+
193
+    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c), &option_types_found), PUSH_MSG_ERROR);
194
+
195
+    free_buf(&buf);
196
+}
197
+
198
+static void
199
+test_incoming_push_message_mix(void **state)
200
+{
201
+    struct context *c = *state;
202
+    struct buffer buf = alloc_buf(256);
203
+    const char *update_msg = "PUSH_UPDATE,-dhcp-option, route 10.10.10.0, dhcp-option DNS 1.1.1.1, route 10.11.12.0, dhcp-option DOMAIN corp.local, keepalive 10 60";
204
+    buf_write(&buf, update_msg, strlen(update_msg));
205
+    unsigned int option_types_found = 0;
206
+
207
+    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c), &option_types_found), PUSH_MSG_UPDATE);
208
+
209
+    free_buf(&buf);
210
+}
211
+
212
+static void
213
+test_incoming_push_message_mix2(void **state)
214
+{
215
+    struct context *c = *state;
216
+    struct buffer buf = alloc_buf(256);
217
+    const char *update_msg = "PUSH_UPDATE,-dhcp-option,dhcp-option DNS 8.8.8.8,redirect-gateway local,route 192.168.1.0 255.255.255.0";
218
+    buf_write(&buf, update_msg, strlen(update_msg));
219
+    unsigned int option_types_found = 0;
220
+
221
+    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c), &option_types_found), PUSH_MSG_UPDATE);
222
+
223
+    free_buf(&buf);
224
+}
225
+
226
+static int
227
+setup(void **state)
228
+{
229
+    struct context *c = calloc(1, sizeof(struct context));
230
+    c->options.pull = true;
231
+    c->options.route_nopull = false;
232
+    *state = c;
233
+    return 0;
234
+}
235
+
236
+static int
237
+teardown(void **state)
238
+{
239
+    struct context *c = *state;
240
+    free(c);
241
+    return 0;
242
+}
243
+
244
+int
245
+main(void)
246
+{
247
+    const struct CMUnitTest tests[] = {
248
+        cmocka_unit_test_setup_teardown(test_incoming_push_message_basic, setup, teardown),
249
+        cmocka_unit_test_setup_teardown(test_incoming_push_message_error1, setup, teardown),
250
+        cmocka_unit_test_setup_teardown(test_incoming_push_message_error2, setup, teardown),
251
+        cmocka_unit_test_setup_teardown(test_incoming_push_message_not_updatable_option, setup, teardown),
252
+        cmocka_unit_test_setup_teardown(test_incoming_push_message_1, setup, teardown),
253
+        cmocka_unit_test_setup_teardown(test_incoming_push_message_bad_format, setup, teardown),
254
+        cmocka_unit_test_setup_teardown(test_incoming_push_message_mix, setup, teardown),
255
+        cmocka_unit_test_setup_teardown(test_incoming_push_message_mix2, setup, teardown)
256
+    };
257
+
258
+    return cmocka_run_group_tests(tests, NULL, NULL);
259
+}