1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 * mod_version.c
19 * Allow conditional configuration depending on the httpd version
20 *
21 * Andr� Malo (nd/perlig.de), January 2004
22 *
23 * Some stuff coded here is heavily based on the core <IfModule>
24 * containers.
25 *
26 * The module makes the following confgurations possible:
27 *
28 * <IfVersion op major.minor.patch>
29 *     # conditional config here ...
30 *</IfVersion>
31 *
32 * where "op" is one of:
33 * = / ==       equal
34 * >            greater than
35 * >=           greater or equal
36 * <            less than
37 * <=           less or equal
38 *
39 * If minor version and patch level are omitted they are assumed to be 0.
40 *
41 * Alternatively you can match the whole version (including some vendor-added
42 * string of the CORE version, see ap_release.h) against a regular expression:
43 *
44 * <IfVersion op regex>
45 *     # conditional config here ...
46 *</IfVersion>
47 *
48 * where "op" is one of:
49 * = / ==       match; regex must be surrounded by slashes
50 * ~            match; regex MAY NOT be surrounded by slashes
51 *
52 * Note that all operators may be preceeded by an exclamation mark
53 * (without spaces) in order to reverse their meaning.
54 *
55 */
56
57#include "apr.h"
58#include "apr_strings.h"
59#include "apr_lib.h"
60
61#include "httpd.h"
62#include "http_config.h"
63#include "http_log.h"
64
65
66/* module structure */
67module AP_MODULE_DECLARE_DATA version_module;
68
69/* queried httpd version */
70static ap_version_t httpd_version;
71
72
73/*
74 * compare the supplied version with the core one
75 */
76static int compare_version(char *version_string, const char **error)
77{
78    char *p = version_string, *ep;
79    int version[3] = {0, 0, 0};
80    int c = 0;
81
82    *error = "Version appears to be invalid. It must have the format "
83             "major[.minor[.patch]] where major, minor and patch are "
84             "numbers.";
85
86    if (!apr_isdigit(*p)) {
87        return 0;
88    }
89
90    /* parse supplied version */
91    ep = version_string + strlen(version_string);
92    while (p <= ep && c < 3) {
93        if (*p == '.') {
94            *p = '\0';
95        }
96
97        if (!*p) {
98            version[c++] = atoi(version_string);
99            version_string = ++p;
100            continue;
101        }
102
103        if (!apr_isdigit(*p)) {
104            break;
105        }
106
107        ++p;
108    }
109
110    if (p < ep) { /* syntax error */
111        return 0;
112    }
113
114    *error = NULL;
115
116    if      (httpd_version.major > version[0]) {
117        return 1;
118    }
119    else if (httpd_version.major < version[0]) {
120        return -1;
121    }
122    else if (httpd_version.minor > version[1]) {
123        return 1;
124    }
125    else if (httpd_version.minor < version[1]) {
126        return -1;
127    }
128    else if (httpd_version.patch > version[2]) {
129        return 1;
130    }
131    else if (httpd_version.patch < version[2]) {
132        return -1;
133    }
134
135    /* seems to be the same */
136    return 0;
137}
138
139/*
140 * match version against a regular expression
141 */
142static int match_version(apr_pool_t *pool, char *version_string,
143                         const char **error)
144{
145    ap_regex_t *compiled;
146    const char *to_match;
147    int rc;
148
149    compiled = ap_pregcomp(pool, version_string, AP_REG_EXTENDED);
150    if (!compiled) {
151        *error = "Unable to compile regular expression";
152        return 0;
153    }
154
155    *error = NULL;
156
157    to_match = apr_psprintf(pool, "%d.%d.%d%s",
158                            httpd_version.major,
159                            httpd_version.minor,
160                            httpd_version.patch,
161                            httpd_version.add_string);
162
163    rc = !ap_regexec(compiled, to_match, 0, NULL, 0);
164
165    ap_pregfree(pool, compiled);
166    return rc;
167}
168
169/*
170 * Implements the <IfVersion> container
171 */
172static const char *start_ifversion(cmd_parms *cmd, void *mconfig,
173                                   const char *arg1, const char *arg2,
174                                   const char *arg3)
175{
176    const char *endp;
177    int reverse = 0, done = 0, match = 0, compare;
178    const char *p, *error;
179    char c;
180
181    /* supplying one argument is possible, we assume an equality check then */
182    if (!arg2) {
183        arg2 = arg1;
184        arg1 = "=";
185    }
186
187    /* surrounding quotes without operator */
188    if (!arg3 && *arg2 == '>' && !arg2[1]) {
189        arg3 = ">";
190        arg2 = arg1;
191        arg1 = "=";
192    }
193
194    /* the third argument makes version surrounding quotes plus operator
195     * possible.
196     */
197    endp = arg2 + strlen(arg2);
198    if (   endp == arg2
199        || (!(arg3 && *arg3 == '>' && !arg3[1]) && *--endp != '>')) {
200        return apr_pstrcat(cmd->pool, cmd->cmd->name,
201                           "> directive missing closing '>'", NULL);
202    }
203
204    p = arg1;
205    if (*p == '!') {
206        reverse = 1;
207        if (p[1]) {
208            ++p;
209        }
210    }
211
212    c = *p++;
213    if (!*p || (*p == '=' && !p[1] && c != '~')) {
214        if (!httpd_version.major) {
215            ap_get_server_revision(&httpd_version);
216        }
217
218        done = 1;
219        switch (c) {
220        case '=':
221            /* normal comparison */
222            if (*arg2 != '/') {
223                compare = compare_version(apr_pstrmemdup(cmd->temp_pool, arg2,
224                                                         endp-arg2),
225                                          &error);
226                if (error) {
227                    return error;
228                }
229
230                match = !compare;
231                break;
232            }
233
234            /* regexp otherwise */
235            if (endp == ++arg2 || *--endp != '/') {
236                return "Missing delimiting / of regular expression.";
237            }
238
239        case '~':
240            /* regular expression */
241            match = match_version(cmd->temp_pool,
242                                  apr_pstrmemdup(cmd->temp_pool, arg2,
243                                                 endp-arg2),
244                                  &error);
245            if (error) {
246                return error;
247            }
248            break;
249
250        case '<':
251            compare = compare_version(apr_pstrmemdup(cmd->temp_pool, arg2,
252                                                     endp-arg2),
253                                      &error);
254            if (error) {
255                return error;
256            }
257
258            match = ((-1 == compare) || (*p && !compare));
259            break;
260
261        case '>':
262            compare = compare_version(apr_pstrmemdup(cmd->temp_pool, arg2,
263                                                     endp-arg2),
264                                      &error);
265            if (error) {
266                return error;
267            }
268
269            match = ((1 == compare) || (*p && !compare));
270            break;
271
272        default:
273            done = 0;
274            break;
275        }
276    }
277
278    if (!done) {
279        return apr_pstrcat(cmd->pool, "unrecognized operator '", arg1, "'",
280                           NULL);
281    }
282
283    if ((!reverse && match) || (reverse && !match)) {
284        ap_directive_t *parent = NULL;
285        ap_directive_t *current = NULL;
286        const char *retval;
287
288        retval = ap_build_cont_config(cmd->pool, cmd->temp_pool, cmd,
289                                      &current, &parent, "<IfVersion");
290        *(ap_directive_t **)mconfig = current;
291        return retval;
292    }
293
294    *(ap_directive_t **)mconfig = NULL;
295    return ap_soak_end_container(cmd, "<IfVersion");
296}
297
298static const command_rec version_cmds[] = {
299    AP_INIT_TAKE123("<IfVersion", start_ifversion, NULL, EXEC_ON_READ | OR_ALL,
300                    "a comparison operator, a version (and a delimiter)"),
301    { NULL }
302};
303
304AP_DECLARE_MODULE(version) =
305{
306    STANDARD20_MODULE_STUFF,
307    NULL,             /* dir config creater */
308    NULL,             /* dir merger --- default is to override */
309    NULL,             /* server config */
310    NULL,             /* merge server configs */
311    version_cmds,     /* command apr_table_t */
312    NULL,             /* register hooks */
313};
314