From 3eee126eba7314e203a50b6398fa5333cbf12ff7 Mon Sep 17 00:00:00 2001 From: james Date: Sun, 27 Sep 2009 02:12:15 +0000 Subject: Eliminated the limitation on the number of options that can be pushed to clients, including routes. Previously, all pushed options needed to fit within a 1024 byte options string. Remember that to make use of this feature to allow many routes to be pushed to clients, the client config file must specify the max-routes option, and the number of pushed routes cannot exceed this limit. Also, both server and client must include this commit. git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@4991 e7ae566f-a301-0410-adde-c780ea21d3b5 --- Makefile.am | 1 + common.h | 8 +-- crypto.c | 45 +++++++++++++++ crypto.h | 19 +++++++ error.h | 2 +- init.c | 21 +++---- openvpn.h | 10 +++- options.c | 26 +++++---- options.h | 15 +---- push.c | 182 ++++++++++++++++++++++++++++++++++++++---------------------- push.h | 3 + pushlist.h | 42 ++++++++++++++ route.c | 2 +- ssl.c | 6 +- ssl.h | 2 - version.m4 | 2 +- 16 files changed, 271 insertions(+), 115 deletions(-) create mode 100644 pushlist.h diff --git a/Makefile.am b/Makefile.am index 5d90b83..e1b28af 100644 --- a/Makefile.am +++ b/Makefile.am @@ -123,6 +123,7 @@ openvpn_SOURCES = \ ieproxy.h ieproxy.c \ ps.c ps.h \ push.c push.h \ + pushlist.h \ reliable.c reliable.h \ route.c route.h \ schedule.c schedule.h \ diff --git a/common.h b/common.h index 03a8d50..94da09e 100644 --- a/common.h +++ b/common.h @@ -74,12 +74,10 @@ typedef unsigned long ptr_type; #define CCD_DEFAULT "DEFAULT" /* - * This parameter controls the TLS channel buffer size. Among - * other things, this buffer must be large enough to contain - * the full --push/--pull list. If you increase it, do so - * on both server and client. + * This parameter controls the TLS channel buffer size and the + * maximum size of a single TLS message (cleartext). */ -#define TLS_CHANNEL_BUF_SIZE 2048 +#define TLS_CHANNEL_BUF_SIZE 1024 /* * A sort of pseudo-filename for data provided inline within diff --git a/crypto.c b/crypto.c index 10c0bee..444f036 100644 --- a/crypto.c +++ b/crypto.c @@ -1796,4 +1796,49 @@ free_ssl_lib (void) } #endif /* USE_SSL */ + +/* + * md5 functions + */ + +void +md5_state_init (struct md5_state *s) +{ + MD5_Init (&s->ctx); +} + +void +md5_state_update (struct md5_state *s, void *data, size_t len) +{ + MD5_Update (&s->ctx, data, len); +} + +void +md5_state_final (struct md5_state *s, struct md5_digest *out) +{ + MD5_Final (out->digest, &s->ctx); +} + +void +md5_digest_clear (struct md5_digest *digest) +{ + CLEAR (*digest); +} + +bool +md5_digest_defined (const struct md5_digest *digest) +{ + int i; + for (i = 0; i < MD5_DIGEST_LENGTH; ++i) + if (digest->digest[i]) + return true; + return false; +} + +bool +md5_digest_equal (const struct md5_digest *d1, const struct md5_digest *d2) +{ + return memcmp(d1->digest, d2->digest, MD5_DIGEST_LENGTH) == 0; +} + #endif /* USE_CRYPTO */ diff --git a/crypto.h b/crypto.h index f0b49bb..3a5d5f2 100644 --- a/crypto.h +++ b/crypto.h @@ -398,5 +398,24 @@ key_ctx_bi_defined(const struct key_ctx_bi* key) return key->encrypt.cipher || key->encrypt.hmac || key->decrypt.cipher || key->decrypt.hmac; } +/* + * md5 functions + */ + +struct md5_state { + MD5_CTX ctx; +}; + +struct md5_digest { + uint8_t digest [MD5_DIGEST_LENGTH]; +}; + +void md5_state_init (struct md5_state *s); +void md5_state_update (struct md5_state *s, void *data, size_t len); +void md5_state_final (struct md5_state *s, struct md5_digest *out); +void md5_digest_clear (struct md5_digest *digest); +bool md5_digest_defined (const struct md5_digest *digest); +bool md5_digest_equal (const struct md5_digest *d1, const struct md5_digest *d2); + #endif /* USE_CRYPTO */ #endif /* CRYPTO_H */ diff --git a/error.h b/error.h index 368aa62..5c6659b 100644 --- a/error.h +++ b/error.h @@ -33,7 +33,7 @@ #ifdef ENABLE_PKCS11 #define ERR_BUF_SIZE 8192 #else -#define ERR_BUF_SIZE 1024 +#define ERR_BUF_SIZE 1280 #endif struct gc_arena; diff --git a/init.c b/init.c index a2821b9..9d29af7 100644 --- a/init.c +++ b/init.c @@ -992,15 +992,12 @@ do_route (const struct options *options, */ #if P2MP static void -save_pulled_options_string (struct context *c, const char *newstring) +save_pulled_options_digest (struct context *c, const struct md5_digest *newdigest) { - if (c->c1.pulled_options_string_save) - free (c->c1.pulled_options_string_save); - - c->c1.pulled_options_string_save = NULL; - - if (newstring) - c->c1.pulled_options_string_save = string_alloc (newstring, NULL); + if (newdigest) + c->c1.pulled_options_digest_save = *newdigest; + else + md5_digest_clear (&c->c1.pulled_options_digest_save); } #endif @@ -1144,7 +1141,7 @@ do_close_tun_simple (struct context *c) c->c1.tuntap = NULL; c->c1.tuntap_owned = false; #if P2MP - save_pulled_options_string (c, NULL); /* delete C1-saved pulled_options_string */ + save_pulled_options_digest (c, NULL); /* delete C1-saved pulled_options_digest */ #endif } @@ -1244,8 +1241,8 @@ do_up (struct context *c, bool pulled_options, unsigned int option_types_found) if (!c->c2.did_open_tun && PULL_DEFINED (&c->options) && c->c1.tuntap - && (!c->c1.pulled_options_string_save || !c->c2.pulled_options_string - || strcmp (c->c1.pulled_options_string_save, c->c2.pulled_options_string))) + && (!md5_digest_defined (&c->c1.pulled_options_digest_save) || !md5_digest_defined (&c->c2.pulled_options_digest) + || !md5_digest_equal (&c->c1.pulled_options_digest_save, &c->c2.pulled_options_digest))) { /* if so, close tun, delete routes, then reinitialize tun and add routes */ msg (M_INFO, "NOTE: Pulled options changed on restart, will need to close and reopen TUN/TAP device."); @@ -1260,7 +1257,7 @@ do_up (struct context *c, bool pulled_options, unsigned int option_types_found) if (c->c2.did_open_tun) { #if P2MP - save_pulled_options_string (c, c->c2.pulled_options_string); + save_pulled_options_digest (c, &c->c2.pulled_options_digest); #endif /* if --route-delay was specified, start timer */ diff --git a/openvpn.h b/openvpn.h index d7e8c56..b850fd0 100644 --- a/openvpn.h +++ b/openvpn.h @@ -189,8 +189,8 @@ struct context_1 bool ifconfig_pool_persist_owned; #endif - /* if client mode, option strings we pulled from server */ - char *pulled_options_string_save; + /* if client mode, hash of option strings we pulled from server */ + struct md5_digest pulled_options_digest_save; /* save user/pass for authentication */ struct user_pass *auth_user_pass; @@ -427,7 +427,11 @@ struct context_2 #endif struct event_timeout push_request_interval; - const char *pulled_options_string; + bool did_pre_pull_restore; + + /* hash of pulled options, so we can compare when options change */ + struct md5_state pulled_options_state; + struct md5_digest pulled_options_digest; struct event_timeout scheduled_exit; #endif diff --git a/options.c b/options.c index c1692e7..ce16b56 100644 --- a/options.c +++ b/options.c @@ -960,11 +960,15 @@ show_p2mp_parms (const struct options *o) msg (D_SHOW_PARMS, " server_bridge_netmask = %s", print_in_addr_t (o->server_bridge_netmask, 0, &gc)); msg (D_SHOW_PARMS, " server_bridge_pool_start = %s", print_in_addr_t (o->server_bridge_pool_start, 0, &gc)); msg (D_SHOW_PARMS, " server_bridge_pool_end = %s", print_in_addr_t (o->server_bridge_pool_end, 0, &gc)); - if (o->push_list) + if (o->push_list.head) { - const struct push_list *l = o->push_list; - const char *printable_push_list = l->options; - msg (D_SHOW_PARMS, " push_list = '%s'", printable_push_list); + const struct push_entry *e = o->push_list.head; + while (e) + { + if (e->enable) + msg (D_SHOW_PARMS, " push_entry = '%s'", e->option); + e = e->next; + } } SHOW_BOOL (ifconfig_pool_defined); msg (D_SHOW_PARMS, " ifconfig_pool_start = %s", print_in_addr_t (o->ifconfig_pool_start, 0, &gc)); @@ -1065,12 +1069,7 @@ options_detach (struct options *o) gc_detach (&o->gc); o->routes = NULL; #if P2MP_SERVER - if (o->push_list) /* clone push_list */ - { - const struct push_list *old = o->push_list; - ALLOC_OBJ_GC (o->push_list, struct push_list, &o->gc); - strcpy (o->push_list->options, old->options); - } + clone_push_list(o); #endif } @@ -2190,6 +2189,8 @@ pre_pull_restore (struct options *o) o->foreign_option_index = pp->foreign_option_index; } + + o->push_continuation = 0; } #endif @@ -4881,6 +4882,11 @@ add_option (struct options *options, VERIFY_PERMISSION (OPT_P_GENERAL); options->pull = true; } + else if (streq (p[0], "push-continuation") && p[1]) + { + VERIFY_PERMISSION (OPT_P_PULL_MODE); + options->push_continuation = atoi(p[1]); + } else if (streq (p[0], "auth-user-pass")) { VERIFY_PERMISSION (OPT_P_GENERAL); diff --git a/options.h b/options.h index 9210aca..fbf0e3c 100644 --- a/options.h +++ b/options.h @@ -40,6 +40,7 @@ #include "manage.h" #include "proxy.h" #include "lzo.h" +#include "pushlist.h" /* * Maximum number of parameters associated with an option, @@ -57,17 +58,6 @@ extern const char title_string[]; #if P2MP -#if P2MP_SERVER -/* parameters to be pushed to peer */ - -#define MAX_PUSH_LIST_LEN TLS_CHANNEL_BUF_SIZE /* This parm is related to PLAINTEXT_BUFFER_SIZE in ssl.h */ - -struct push_list { - /* newline delimited options, like config file */ - char options[MAX_PUSH_LIST_LEN]; -}; -#endif - /* certain options are saved before --pull modifications are applied */ struct options_pre_pull { @@ -362,7 +352,7 @@ struct options in_addr_t server_bridge_pool_start; in_addr_t server_bridge_pool_end; - struct push_list *push_list; + struct push_list push_list; bool ifconfig_pool_defined; in_addr_t ifconfig_pool_start; in_addr_t ifconfig_pool_end; @@ -405,6 +395,7 @@ struct options bool client; bool pull; /* client pull of config options from server */ + int push_continuation; const char *auth_user_pass_file; struct options_pre_pull *pre_pull; diff --git a/push.c b/push.c index cbc3c10..36febe1 100644 --- a/push.c +++ b/push.c @@ -99,9 +99,10 @@ incoming_push_message (struct context *c, const struct buffer *buffer) if (status == PUSH_MSG_ERROR) msg (D_PUSH_ERRORS, "WARNING: Received bad push/pull message: %s", BSTR (buffer)); - else if (status == PUSH_MSG_REPLY) + else if (status == PUSH_MSG_REPLY || status == PUSH_MSG_CONTINUATION) { - do_up (c, true, option_types_found); /* delay bringing tun/tap up until --push parms received from remote */ + if (status == PUSH_MSG_REPLY) + do_up (c, true, option_types_found); /* delay bringing tun/tap up until --push parms received from remote */ event_timeout_clear (&c->c2.push_request_interval); } @@ -115,60 +116,114 @@ send_push_request (struct context *c) } #if P2MP_SERVER + bool send_push_reply (struct context *c) { struct gc_arena gc = gc_new (); - struct buffer buf = alloc_buf_gc (MAX_PUSH_LIST_LEN + 256, &gc); - bool ret = false; + struct buffer buf = alloc_buf_gc (TLS_CHANNEL_BUF_SIZE, &gc); + struct push_entry *e = c->options.push_list.head; + bool multi_push = false; + static char cmd[] = "PUSH_REPLY"; + const int extra = 64; /* extra space for possible trailing ifconfig and push-continuation */ + const int safe_cap = BCAP (&buf) - extra; - buf_printf (&buf, "PUSH_REPLY"); + buf_printf (&buf, cmd); - if (c->options.push_list && strlen (c->options.push_list->options)) - buf_printf (&buf, ",%s", c->options.push_list->options); + while (e) + { + if (e->enable) + { + const int l = strlen (e->option); + if (BLEN (&buf) + l >= safe_cap) + { + buf_printf (&buf, ",push-continuation 2"); + { + const bool status = send_control_channel_string (c, BSTR (&buf), D_PUSH); + if (!status) + goto fail; + multi_push = true; + buf_reset_len (&buf); + buf_printf (&buf, cmd); + } + } + if (BLEN (&buf) + l >= safe_cap) + { + msg (M_WARN, "--push option is too long"); + goto fail; + } + buf_printf (&buf, ",%s", e->option); + } + e = e->next; + } if (c->c2.push_ifconfig_defined && c->c2.push_ifconfig_local && c->c2.push_ifconfig_remote_netmask) buf_printf (&buf, ",ifconfig %s %s", print_in_addr_t (c->c2.push_ifconfig_local, 0, &gc), print_in_addr_t (c->c2.push_ifconfig_remote_netmask, 0, &gc)); + if (multi_push) + buf_printf (&buf, ",push-continuation 1"); - if (strlen (BSTR (&buf)) < MAX_PUSH_LIST_LEN) - ret = send_control_channel_string (c, BSTR (&buf), D_PUSH); - else - msg (M_WARN, "Maximum length of --push buffer (%d) has been exceeded", MAX_PUSH_LIST_LEN); + if (BLEN (&buf) > sizeof(cmd)-1) + { + const bool status = send_control_channel_string (c, BSTR (&buf), D_PUSH); + if (!status) + goto fail; + } gc_free (&gc); - return ret; + return true; + + fail: + gc_free (&gc); + return false; } -void -push_option (struct options *o, const char *opt, int msglevel) +static void +push_option_ex (struct options *o, const char *opt, bool enable, int msglevel) { - int len; - bool first = false; - if (!string_class (opt, CC_ANY, CC_COMMA)) { msg (msglevel, "PUSH OPTION FAILED (illegal comma (',') in string): '%s'", opt); } else { - if (!o->push_list) + struct push_entry *e; + ALLOC_OBJ_CLEAR_GC (e, struct push_entry, &o->gc); + e->enable = true; + e->option = opt; + if (o->push_list.head) { - ALLOC_OBJ_CLEAR_GC (o->push_list, struct push_list, &o->gc); - first = true; + ASSERT(o->push_list.tail); + o->push_list.tail->next = e; + o->push_list.tail = e; } - - len = strlen (o->push_list->options); - if (len + strlen (opt) + 2 >= MAX_PUSH_LIST_LEN) + else { - msg (msglevel, "Maximum length of --push buffer (%d) has been exceeded", MAX_PUSH_LIST_LEN); + ASSERT(!o->push_list.tail); + o->push_list.head = e; + o->push_list.tail = e; } - else + } +} + +void +push_option (struct options *o, const char *opt, int msglevel) +{ + push_option_ex (o, opt, true, msglevel); +} + +void +clone_push_list (struct options *o) +{ + if (o->push_list.head) + { + const struct push_entry *e = o->push_list.head; + push_reset (o); + while (e) { - if (!first) - strcat (o->push_list->options, ","); - strcat (o->push_list->options, opt); + push_option_ex (o, string_alloc (e->option, &o->gc), true, M_FATAL); + e = e->next; } } } @@ -184,7 +239,7 @@ push_options (struct options *o, char **p, int msglevel, struct gc_arena *gc) void push_reset (struct options *o) { - o->push_list = NULL; + CLEAR (o->push_list); } #endif @@ -224,14 +279,31 @@ process_incoming_push_msg (struct context *c, const uint8_t ch = buf_read_u8 (&buf); if (ch == ',') { - pre_pull_restore (&c->options); - c->c2.pulled_options_string = string_alloc (BSTR (&buf), &c->c2.gc); + struct buffer buf_orig = buf; + if (!c->c2.did_pre_pull_restore) + { + pre_pull_restore (&c->options); + md5_state_init (&c->c2.pulled_options_state); + c->c2.did_pre_pull_restore = true; + } if (apply_push_options (&c->options, &buf, permission_mask, option_types_found, c->c2.es)) - ret = PUSH_MSG_REPLY; + switch (c->options.push_continuation) + { + case 0: + case 1: + md5_state_update (&c->c2.pulled_options_state, BPTR(&buf_orig), BLEN(&buf_orig)); + md5_state_final (&c->c2.pulled_options_state, &c->c2.pulled_options_digest); + ret = PUSH_MSG_REPLY; + break; + case 2: + md5_state_update (&c->c2.pulled_options_state, BPTR(&buf_orig), BLEN(&buf_orig)); + ret = PUSH_MSG_CONTINUATION; + break; + } } else if (ch == '\0') { @@ -243,36 +315,27 @@ process_incoming_push_msg (struct context *c, } #if P2MP_SERVER + /* * Remove iroutes from the push_list. */ void remove_iroutes_from_push_route_list (struct options *o) { - if (o && o->push_list && o->iroutes) + if (o && o->push_list.head && o->iroutes) { struct gc_arena gc = gc_new (); - struct push_list *pl; - struct buffer in, out; - char *line; - bool first = true; - - /* prepare input and output buffers */ - ALLOC_OBJ_CLEAR_GC (pl, struct push_list, &gc); - ALLOC_ARRAY_CLEAR_GC (line, char, MAX_PUSH_LIST_LEN, &gc); - - buf_set_read (&in, (const uint8_t*) o->push_list->options, strlen (o->push_list->options)); - buf_set_write (&out, (uint8_t*) pl->options, sizeof (pl->options)); + struct push_entry *e = o->push_list.head; /* cycle through the push list */ - while (buf_parse (&in, ',', line, MAX_PUSH_LIST_LEN)) + while (e) { char *p[MAX_PARMS]; - bool copy = true; + bool enable = true; /* parse the push item */ CLEAR (p); - if (parse_line (line, p, SIZE (p), "[PUSH_ROUTE_REMOVE]", 1, D_ROUTE_DEBUG, &gc)) + if (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]) @@ -292,7 +355,7 @@ remove_iroutes_from_push_route_list (struct options *o) { if (network == ir->network && netmask == netbits_to_netmask (ir->netbits >= 0 ? ir->netbits : 32)) { - copy = false; + enable = false; break; } } @@ -301,28 +364,17 @@ remove_iroutes_from_push_route_list (struct options *o) } /* should we copy the push item? */ - if (copy) - { - if (!first) - buf_printf (&out, ","); - buf_printf (&out, "%s", line); - first = false; - } - else - msg (D_PUSH, "REMOVE PUSH ROUTE: '%s'", line); - } - -#if 0 - msg (M_INFO, "BEFORE: '%s'", o->push_list->options); - msg (M_INFO, "AFTER: '%s'", pl->options); -#endif + e->enable = enable; + if (!enable) + msg (D_PUSH, "REMOVE PUSH ROUTE: '%s'", e->option); - /* copy new push list back to options */ - *o->push_list = *pl; + e = e->next; + } gc_free (&gc); } } + #endif #endif diff --git a/push.h b/push.h index 30ecb6a..a48b077 100644 --- a/push.h +++ b/push.h @@ -34,6 +34,7 @@ #define PUSH_MSG_REPLY 2 #define PUSH_MSG_REQUEST_DEFERRED 3 #define PUSH_MSG_AUTH_FAILURE 4 +#define PUSH_MSG_CONTINUATION 5 void incoming_push_message (struct context *c, const struct buffer *buffer); @@ -50,6 +51,8 @@ void receive_auth_failed (struct context *c, const struct buffer *buffer); #if P2MP_SERVER +void clone_push_list (struct options *o); + 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); diff --git a/pushlist.h b/pushlist.h new file mode 100644 index 0000000..1c2f4e0 --- /dev/null +++ b/pushlist.h @@ -0,0 +1,42 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2009 OpenVPN Technologies, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if !defined(PUSHLIST_H) && P2MP && P2MP_SERVER +#define PUSHLIST_H + +/* parameters to be pushed to peer */ + +struct push_entry { + struct push_entry *next; + bool enable; + const char *option; +}; + +struct push_list { + struct push_entry *head; + struct push_entry *tail; +}; + + +#endif diff --git a/route.c b/route.c index 988c7ab..24d4bd8 100644 --- a/route.c +++ b/route.c @@ -740,7 +740,7 @@ delete_routes (struct route_list *rl, const struct tuntap *tt, unsigned int flag } undo_redirect_default_route_to_vpn (rl, tt, flags, es); - CLEAR (*rl); + clear_route_list (rl); } #ifdef ENABLE_DEBUG diff --git a/ssl.c b/ssl.c index 687c3f3..224721a 100644 --- a/ssl.c +++ b/ssl.c @@ -2069,8 +2069,8 @@ key_state_init (struct tls_session *session, struct key_state *ks) ALLOC_OBJ_CLEAR (ks->rec_ack, struct reliable_ack); /* allocate buffers */ - ks->plaintext_read_buf = alloc_buf (PLAINTEXT_BUFFER_SIZE); - ks->plaintext_write_buf = alloc_buf (PLAINTEXT_BUFFER_SIZE); + ks->plaintext_read_buf = alloc_buf (TLS_CHANNEL_BUF_SIZE); + ks->plaintext_write_buf = alloc_buf (TLS_CHANNEL_BUF_SIZE); ks->ack_write_buf = alloc_buf (BUF_SIZE (&session->opt->frame)); reliable_init (ks->send_reliable, BUF_SIZE (&session->opt->frame), FRAME_HEADROOM (&session->opt->frame), TLS_RELIABLE_N_SEND_BUFFERS, @@ -3750,7 +3750,7 @@ tls_process (struct tls_multi *multi, int status; ASSERT (buf_init (buf, 0)); - status = key_state_read_plaintext (multi, ks, buf, PLAINTEXT_BUFFER_SIZE); + status = key_state_read_plaintext (multi, ks, buf, TLS_CHANNEL_BUF_SIZE); update_time (); if (status == -1) { diff --git a/ssl.h b/ssl.h index 7d4404b..910c77c 100644 --- a/ssl.h +++ b/ssl.h @@ -278,8 +278,6 @@ * Buffer sizes (also see mtu.h). */ -#define PLAINTEXT_BUFFER_SIZE TLS_CHANNEL_BUF_SIZE - /* Maximum length of common name */ #define TLS_CN_LEN 64 diff --git a/version.m4 b/version.m4 index 887de1e..61bdb7d 100644 --- a/version.m4 +++ b/version.m4 @@ -1,5 +1,5 @@ dnl define the OpenVPN version -define(PRODUCT_VERSION,[2.1_rc19d]) +define(PRODUCT_VERSION,[2.1_rc19e]) dnl define the TAP version define(PRODUCT_TAP_ID,[tap0901]) define(PRODUCT_TAP_WIN32_MIN_MAJOR,[9]) -- cgit