diff options
-rw-r--r-- | Changes.rst | 4 | ||||
-rw-r--r-- | doc/openvpn.8 | 51 | ||||
-rw-r--r-- | src/openvpn/options.c | 146 | ||||
-rw-r--r-- | src/openvpn/options.h | 2 |
4 files changed, 203 insertions, 0 deletions
diff --git a/Changes.rst b/Changes.rst index a6bb2a5..1ac3c2b 100644 --- a/Changes.rst +++ b/Changes.rst @@ -5,6 +5,10 @@ Version 2.4.0 New features ------------ +pull-filter + New option to explicitly allow or reject options pushed by the server. + May be used multiple times and is applied in the order specified. + push-remove new option to remove options on a per-client basis from the "push" list (more fine-grained than "push-reset") diff --git a/doc/openvpn.8 b/doc/openvpn.8 index e1dd7cd..03f31bb 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -3830,6 +3830,57 @@ in situations where you don't trust the server to have control over the client's routing table. .\"********************************************************* .TP +.B \-\-pull\-filter accept|ignore|reject \fItext\fR +Filter options received from the server if the option starts with +\fItext\fR. Runs on client. The action flag +.B accept +allows the option, +.B ignore +removes it and +.B reject +flags an error and triggers a SIGUSR1 restart. +The filters may be specified multiple times, and each filter is +applied in the order it is specified. The filtering of each +option stops as soon as a match is found. Unmatched options are accepted +by default. + +Prefix comparison is used to match \fItext\fR against the +received option so that + +.nf +.ft 3 +.in +4 +\-\-pull\-filter ignore "route" +.in -4 +.ft +.fi + +would remove all pushed options starting with +.B route +which would include, for example, +.B route\-gateway. +Enclose \fItext\fR in quotes to embed spaces. + +.nf +.ft 3 +.in +4 +\-\-pull\-filter accept "route 192.168.1." +\-\-pull\-filter ignore "route " +.in -4 +.ft +.fi + +would remove all routes that do not start with 192.168.1. + +This option may be used only on clients. +Note that +.B reject +may result in a repeated cycle of failure and reconnect, +unless multiple remotes are specified and connection to the next remote +succeeds. To silently ignore an option pushed by the server, use +.B ignore. +.\"********************************************************* +.TP .B \-\-auth\-user\-pass [up] Authenticate with server using username/password. .B up diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 55630c7..23f407c 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -489,6 +489,11 @@ static const char usage_message[] = "--pull : Accept certain config file options from the peer as if they\n" " were part of the local config file. Must be specified\n" " when connecting to a '--mode server' remote host.\n" + "--pull-filter accept|ignore|reject t : Filter each option received from the\n" + " server if it starts with the text t. The action flag accept,\n" + " ignore or reject causes the option to be allowed, removed or\n" + " rejected with error. May be specified multiple times, and\n" + " each filter is applied in the order of appearance.\n" "--auth-retry t : How to handle auth failures. Set t to\n" " none (default), interact, or nointeract.\n" "--static-challenge t e : Enable static challenge/response protocol using\n" @@ -876,6 +881,37 @@ uninit_options (struct options *o) } } +struct pull_filter +{ +# define PUF_TYPE_UNDEF 0 /** undefined filter type */ +# define PUF_TYPE_ACCEPT 1 /** filter type to accept a matching option */ +# define PUF_TYPE_IGNORE 2 /** filter type to ignore a matching option */ +# define PUF_TYPE_REJECT 3 /** filter type to reject and trigger SIGUSR1 */ + int type; + int size; + char *pattern; + struct pull_filter *next; +}; + +struct pull_filter_list +{ + struct pull_filter *head; + struct pull_filter *tail; +}; + +static const char * +pull_filter_type_name (int type) +{ + if (type == PUF_TYPE_ACCEPT) + return "accept"; + if (type == PUF_TYPE_IGNORE) + return "ignore"; + if (type == PUF_TYPE_REJECT) + return "reject"; + else + return "???"; +} + #ifndef ENABLE_SMALL #define SHOW_PARM(name, value, format) msg(D_SHOW_PARMS, " " #name " = " format, (value)) @@ -1407,6 +1443,20 @@ show_connection_entries (const struct options *o) msg (D_SHOW_PARMS, "Connection profiles END"); } +static void +show_pull_filter_list (const struct pull_filter_list *l) +{ + struct pull_filter *f; + if (!l) + return; + + msg (D_SHOW_PARMS, " Pull filters:"); + for (f = l->head; f; f = f->next) + { + msg (D_SHOW_PARMS, " %s \"%s\"", pull_filter_type_name(f->type), f->pattern); + } +} + #endif void @@ -1537,6 +1587,8 @@ show_settings (const struct options *o) SHOW_BOOL (route_nopull); SHOW_BOOL (route_gateway_via_dhcp); SHOW_BOOL (allow_pull_fqdn); + show_pull_filter_list (o->pull_filter_list); + if (o->routes) print_route_options (o->routes, D_SHOW_PARMS); @@ -1797,6 +1849,35 @@ alloc_remote_entry (struct options *options, const int msglevel) return e; } +static struct pull_filter_list * +alloc_pull_filter_list (struct options *o) +{ + if (!o->pull_filter_list) + ALLOC_OBJ_CLEAR_GC (o->pull_filter_list, struct pull_filter_list, &o->gc); + return o->pull_filter_list; +} + +static struct pull_filter * +alloc_pull_filter (struct options *o, const int msglevel) +{ + struct pull_filter_list *l = alloc_pull_filter_list (o); + struct pull_filter *f; + + ALLOC_OBJ_CLEAR_GC (f, struct pull_filter, &o->gc); + if (l->head) + { + ASSERT (l->tail); + l->tail->next = f; + } + else + { + ASSERT (!l->tail); + l->head = f; + } + l->tail = f; + return f; +} + void connection_entry_load_re (struct connection_entry *ce, const struct remote_entry *re) { @@ -2000,6 +2081,8 @@ options_postprocess_verify_ce (const struct options *options, const struct conne msg (M_USAGE, "--mode server only works with --dev tun or --dev tap"); if (options->pull) msg (M_USAGE, "--pull cannot be used with --mode server"); + if (options->pull_filter_list) + msg (M_USAGE, "--pull-filter cannot be used with --mode server"); if (!(proto_is_udp(ce->proto) || ce->proto == PROTO_TCP_SERVER)) msg (M_USAGE, "--mode server currently only supports " "--proto udp or --proto tcp-server or proto tcp6-server"); @@ -3964,6 +4047,45 @@ parse_argv (struct options *options, } } +/** + * Filter an option line by all pull filters. + * + * If a match is found, the line is modified depending on + * the filter type, and returns true. If the filter type is + * reject, SIGUSR1 is triggered and the return value is false. + * In that case the caller must end the push processing. + */ +static bool +apply_pull_filter (const struct options *o, char *line) +{ + struct pull_filter *f; + + if (!o->pull_filter_list) return true; + + for (f = o->pull_filter_list->head; f; f = f->next) + { + if (f->type == PUF_TYPE_ACCEPT && strncmp (line, f->pattern, f->size) == 0) + { + msg (D_LOW, "Pushed option accepted by filter: '%s'", line); + return true; + } + else if (f->type == PUF_TYPE_IGNORE && strncmp (line, f->pattern, f->size) == 0) + { + msg (D_PUSH, "Pushed option removed by filter: '%s'", line); + *line = '\0'; + return true; + } + else if (f->type == PUF_TYPE_REJECT && strncmp (line, f->pattern, f->size) == 0) + { + msg (M_WARN, "Pushed option rejected by filter: '%s'. Restarting.", line); + *line = '\0'; + throw_signal_soft (SIGUSR1, "Offending option received from server"); + return false; + } + } + return true; +} + bool apply_push_options (struct options *options, struct buffer *buf, @@ -3981,6 +4103,10 @@ apply_push_options (struct options *options, char *p[MAX_PARMS]; CLEAR (p); ++line_num; + if (!apply_pull_filter(options, line)) + { + return false; /* Cause push/pull error and stop push processing */ + } if (parse_line (line, p, SIZE (p), file, line_num, msglevel, &options->gc)) { add_option (options, p, file, line_num, 0, msglevel, permission_mask, option_types_found, es); @@ -5373,6 +5499,26 @@ add_option (struct options *options, VERIFY_PERMISSION (OPT_P_GENERAL); options->route_nopull = true; } + else if (streq (p[0], "pull-filter") && p[1] && p[2] && !p[3]) + { + struct pull_filter *f; + VERIFY_PERMISSION (OPT_P_GENERAL) + f = alloc_pull_filter (options, msglevel); + + if (strcmp ("accept", p[1]) == 0) + f->type = PUF_TYPE_ACCEPT; + else if (strcmp ("ignore", p[1]) == 0) + f->type = PUF_TYPE_IGNORE; + else if (strcmp ("reject", p[1]) == 0) + f->type = PUF_TYPE_REJECT; + else + { + msg (msglevel, "Unknown --pull-filter type: %s", p[1]); + goto err; + } + f->pattern = p[2]; + f->size = strlen(p[2]); + } else if (streq (p[0], "allow-pull-fqdn") && !p[1]) { VERIFY_PERMISSION (OPT_P_GENERAL); diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 2fa375f..514511b 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -597,6 +597,8 @@ struct options const char *keying_material_exporter_label; int keying_material_exporter_length; #endif + + struct pull_filter_list *pull_filter_list; }; #define streq(x, y) (!strcmp((x), (y))) |