From a8c135f27e4708965029944662676c15d30541be Mon Sep 17 00:00:00 2001 From: Stefan Fritsch Date: Sat, 28 May 2011 07:01:47 +0000 Subject: [PATCH] Add some features to ap_expr for use by mod_include: * a restricted mode that does not allow to bypass request access restrictions * new variables DOCUMENT_URI (alias for REQUEST_URI), LAST_MODIFIED * -A as an alias for -U * an additional data entry in ap_expr_eval_ctx_t for use by the consumer * an extensible ap_expr_exec_ctx() API that allows to use that data entry git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1128564 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 7 ++ docs/manual/expr.xml | 9 +++ include/ap_expr.h | 17 ++++ include/ap_mmn.h | 4 +- server/util_expr_eval.c | 175 ++++++++++++++++++++++++---------------- 5 files changed, 143 insertions(+), 69 deletions(-) diff --git a/CHANGES b/CHANGES index f48e92e4a8a..9883c78cd97 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,13 @@ Changes with Apache 2.3.13 + *) core: Add some features to ap_expr for use by mod_include: a restricted + mode that does not allow to bypass request access restrictions; new + variables DOCUMENT_URI (alias for REQUEST_URI), LAST_MODIFIED; -A as an + alias for -U; an additional data entry in ap_expr_eval_ctx_t for use by + the consumer; an extensible ap_expr_exec_ctx() API that allows to use that + data entry. [Stefan Fritsch] + *) mod_include: Merge directory configs instead of one SSI* config directive causing all other per-directory SSI* config directives to be reset. [Stefan Fritsch] diff --git a/docs/manual/expr.xml b/docs/manual/expr.xml index d61f21dc3f6..03bf655ab34 100644 --- a/docs/manual/expr.xml +++ b/docs/manual/expr.xml @@ -158,6 +158,8 @@ listfunction ::= listfuncname "(" word ")" The scheme part of the request's URI REQUEST_URI The URI of the request + DOCUMENT_URI + Same as REQUEST_URI REQUEST_FILENAME The full local filesystem path to the file or script matching the request, if this has already been determined by the server at the @@ -166,6 +168,11 @@ listfunction ::= listfuncname "(" word ")" REQUEST_URI SCRIPT_FILENAME Same as REQUEST_FILENAME + LAST_MODIFIED + The date and time of last modification of the file in the format + 20101231235959, if this has already been determined by + the server at the time LAST_MODIFIED is referenced. + SCRIPT_USER The user name of the owner of the script. SCRIPT_GROUP @@ -374,6 +381,8 @@ listfunction ::= listfuncname "(" word ")" currently-configured access controls for that path. This uses an internal subrequest to do the check, so use it with care - it can impact your server's performance! + -A + Alias for -U -n True if string is not empty -z diff --git a/include/ap_expr.h b/include/ap_expr.h index eb4215400d7..3366f9c8629 100644 --- a/include/ap_expr.h +++ b/include/ap_expr.h @@ -59,6 +59,11 @@ typedef struct { #define AP_EXPR_FLAGS_SSL_EXPR_COMPAT 1 /** Don't add siginificant request headers to the Vary response header */ #define AP_EXPR_FLAGS_DONT_VARY 2 +/** Don't allow functions/vars that bypass the current request's access + * restrictions or would otherwise leak confidential information. + * Used by e.g. mod_include. + */ +#define AP_EXPR_FLAGS_RESTRICTED 4 /** @@ -119,8 +124,20 @@ typedef struct { * interested in this information. */ const char **vary_this; + /** Arbitrary context data provided by the caller for custom functions */ + void *data; } ap_expr_eval_ctx_t; +/** + * Evaluate a parse tree, full featured version + * @param ctx The evaluation context with all data filled in + * @return > 0 if expression evaluates to true, == 0 if false, < 0 on error + * @note *ctx->err will be set to NULL on success, or to an error message on + * error + * @note request headers used during evaluation will be added to the Vary: + * response header if ctx->vary_this is set. + */ +AP_DECLARE(int) ap_expr_exec_ctx(ap_expr_eval_ctx_t *ctx); /** * The parser can be extended with variable lookup, functions, and diff --git a/include/ap_mmn.h b/include/ap_mmn.h index 4d4f612c7c3..c1343e1b635 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -323,6 +323,8 @@ * 20110329.3 (2.3.12-dev) Add format field to ap_errorlog_info. * 20110329.4 (2.3.13-dev) bgrowth and max_balancers to proxy_server_conf. * 20110329.5 (2.3.13-dev) Add ap_regexec_len() + * 20110329.6 (2.3.13-dev) Add AP_EXPR_FLAGS_RESTRICTED, ap_expr_eval_ctx_t->data, + * ap_expr_exec_ctx() */ #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */ @@ -330,7 +332,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20110329 #endif -#define MODULE_MAGIC_NUMBER_MINOR 5 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 6 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a diff --git a/server/util_expr_eval.c b/server/util_expr_eval.c index 96c489d781f..bb7ebc41a32 100644 --- a/server/util_expr_eval.c +++ b/server/util_expr_eval.c @@ -384,7 +384,7 @@ static ap_expr_t *ap_expr_info_make(int type, const char *name, ap_expr_t *info = apr_palloc(ctx->pool, sizeof(ap_expr_t)); ap_expr_lookup_parms parms; parms.type = type; - parms.flags = 0; + parms.flags = ctx->flags; parms.pool = ctx->pool; parms.ptemp = ctx->ptemp; parms.name = name; @@ -691,12 +691,47 @@ AP_DECLARE(int) ap_expr_exec(request_rec *r, const ap_expr_info_t *info, return ap_expr_exec_re(r, info, 0, NULL, NULL, err); } +AP_DECLARE(int) ap_expr_exec_ctx(ap_expr_eval_ctx_t *ctx) +{ + int rc; + + AP_DEBUG_ASSERT(ctx->p != NULL); + /* XXX: allow r, c == NULL */ + AP_DEBUG_ASSERT(ctx->r != NULL); + AP_DEBUG_ASSERT(ctx->c != NULL); + AP_DEBUG_ASSERT(ctx->s != NULL); + AP_DEBUG_ASSERT(ctx->err != NULL); + AP_DEBUG_ASSERT(ctx->info != NULL); + if (ctx->re_pmatch) { + AP_DEBUG_ASSERT(ctx->re_source != NULL); + AP_DEBUG_ASSERT(ctx->re_nmatch > 0); + } + + *ctx->err = NULL; + rc = ap_expr_eval(ctx, ctx->info->root_node); + if (*ctx->err != NULL) { + ap_log_rerror(LOG_MARK(ctx->info), APLOG_ERR, 0, ctx->r, + "Evaluation of expression from %s:%d failed: %s", + ctx->info->filename, ctx->info->line_number, *ctx->err); + return -1; + } else { + rc = rc ? 1 : 0; + ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE4, 0, ctx->r, + "Evaluation of expression from %s:%d gave: %d", + ctx->info->filename, ctx->info->line_number, rc); + + if (ctx->vary_this) + apr_table_merge(ctx->r->headers_out, "Vary", *ctx->vary_this); + + return rc; + } +} + AP_DECLARE(int) ap_expr_exec_re(request_rec *r, const ap_expr_info_t *info, apr_size_t nmatch, ap_regmatch_t *pmatch, const char **source, const char **err) { ap_expr_eval_ctx_t ctx; - int rc; int dont_vary = (info->flags & AP_EXPR_FLAGS_DONT_VARY); const char *tmp_source = NULL, *vary_this = NULL; ap_regmatch_t tmp_pmatch[10]; @@ -711,35 +746,15 @@ AP_DECLARE(int) ap_expr_exec_re(request_rec *r, const ap_expr_info_t *info, ctx.re_pmatch = pmatch; ctx.re_source = source; ctx.vary_this = dont_vary ? NULL : &vary_this; + ctx.data = NULL; if (!pmatch) { ctx.re_nmatch = 10; ctx.re_pmatch = tmp_pmatch; ctx.re_source = &tmp_source; } - else { - AP_DEBUG_ASSERT(source != NULL); - AP_DEBUG_ASSERT(nmatch > 0); - } - - *err = NULL; - rc = ap_expr_eval(&ctx, info->root_node); - if (*err != NULL) { - ap_log_rerror(LOG_MARK(info), APLOG_ERR, 0, r, - "Evaluation of expression from %s:%d failed: %s", - info->filename, info->line_number, *err); - return -1; - } else { - rc = rc ? 1 : 0; - ap_log_rerror(LOG_MARK(info), APLOG_TRACE4, 0, r, - "Evaluation of expression from %s:%d gave: %d", - info->filename, info->line_number, rc); - if (vary_this) - apr_table_merge(r->headers_out, "Vary", vary_this); - - return rc; - } + return ap_expr_exec_ctx(&ctx); } static void add_vary(ap_expr_eval_ctx_t *ctx, const char *name) @@ -911,12 +926,12 @@ static int op_file_min(ap_expr_eval_ctx_t *ctx, const void *data, const char *ar apr_finfo_t sb; const char *name = (const char *)data; if (apr_stat(&sb, arg, APR_FINFO_MIN, ctx->p) != APR_SUCCESS) - return 0; + return FALSE; switch (name[0]) { case 'd': return (sb.filetype == APR_DIR); case 'e': - return 1; + return TRUE; case 'f': return (sb.filetype == APR_REG); case 's': @@ -924,7 +939,7 @@ static int op_file_min(ap_expr_eval_ctx_t *ctx, const void *data, const char *ar default: ap_assert(0); } - return 0; + return FALSE; } static int op_file_link(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) @@ -933,10 +948,10 @@ static int op_file_link(ap_expr_eval_ctx_t *ctx, const void *data, const char *a #if !defined(OS2) if (apr_stat(&sb, arg, APR_FINFO_MIN | APR_FINFO_LINK, ctx->p) == APR_SUCCESS && sb.filetype == APR_LNK) { - return 1; + return TRUE; } #endif - return 0; + return FALSE; } static int op_file_xbit(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) @@ -944,24 +959,24 @@ static int op_file_xbit(ap_expr_eval_ctx_t *ctx, const void *data, const char *a apr_finfo_t sb; if (apr_stat(&sb, arg, APR_FINFO_PROT| APR_FINFO_LINK, ctx->p) == APR_SUCCESS && (sb.protection & (APR_UEXECUTE | APR_GEXECUTE | APR_WEXECUTE))) { - return 1; + return TRUE; } - return 0; + return FALSE; } static int op_url_subr(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) { - int rc = 0; + int rc = FALSE; request_rec *rsub, *r = ctx->r; if (!r) - return 0; + return FALSE; /* avoid some infinite recursions */ if (r->main && r->main->uri && r->uri && strcmp(r->main->uri, r->uri) == 0) - return 0; + return FALSE; rsub = ap_sub_req_lookup_uri(arg, r, NULL); if (rsub->status < 400) { - rc = 1; + rc = TRUE; } ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE5, 0, r, "Subrequest for -U %s at %s:%d gave status: %d", @@ -973,16 +988,16 @@ static int op_url_subr(ap_expr_eval_ctx_t *ctx, const void *data, const char *ar static int op_file_subr(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) { - int rc = 0; + int rc = FALSE; apr_finfo_t sb; request_rec *rsub, *r = ctx->r; if (!r) - return 0; + return FALSE; rsub = ap_sub_req_lookup_file(arg, r, NULL); if (rsub->status < 300 && /* double-check that file exists since default result is 200 */ apr_stat(&sb, rsub->filename, APR_FINFO_MIN, ctx->p) == APR_SUCCESS) { - rc = 1; + rc = TRUE; } ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE5, 0, r, "Subrequest for -F %s at %s:%d gave status: %d", @@ -1064,6 +1079,8 @@ static const char *request_var_names[] = { "REQUEST_LOG_ID", /* 20 */ "SCRIPT_USER", /* 21 */ "SCRIPT_GROUP", /* 22 */ + "DOCUMENT_URI", /* 23 */ + "LAST_MODIFIED", /* 24 */ NULL }; @@ -1132,6 +1149,17 @@ static const char *request_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) apr_gid_name_get(&result, r->finfo.group, ctx->p); return result; } + case 23: + return r->uri; + case 24: + { + apr_time_exp_t tm; + apr_time_exp_lt(&tm, r->mtime); + return apr_psprintf(ctx->p, "%02d%02d%02d%02d%02d%02d%02d", + (tm.tm_year / 100) + 19, (tm.tm_year % 100), + tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, + tm.tm_sec); + } default: ap_assert(0); return NULL; @@ -1326,6 +1354,7 @@ struct expr_provider_single { const void *func; const char *name; ap_expr_lookup_fn_t *arg_parsing_func; + int restricted; }; struct expr_provider_multi { @@ -1342,46 +1371,47 @@ static const struct expr_provider_multi var_providers[] = { }; static const struct expr_provider_single string_func_providers[] = { - { osenv_func, "osenv", NULL }, - { env_func, "env", NULL }, - { req_table_func, "resp", NULL }, - { req_table_func, "req", NULL }, + { osenv_func, "osenv", NULL, 0 }, + { env_func, "env", NULL, 0 }, + { req_table_func, "resp", NULL, 0 }, + { req_table_func, "req", NULL, 0 }, /* 'http' as alias for 'req' for compatibility with ssl_expr */ - { req_table_func, "http", NULL }, - { req_table_func, "note", NULL }, - { req_table_func, "reqenv", NULL }, - { tolower_func, "tolower", NULL }, - { toupper_func, "toupper", NULL }, - { escape_func, "escape", NULL }, - { unescape_func, "unescape", NULL }, - { file_func, "file", NULL }, - { filesize_func, "filesize", NULL }, + { req_table_func, "http", NULL, 0 }, + { req_table_func, "note", NULL, 0 }, + { req_table_func, "reqenv", NULL, 0 }, + { tolower_func, "tolower", NULL, 0 }, + { toupper_func, "toupper", NULL, 0 }, + { escape_func, "escape", NULL, 0 }, + { unescape_func, "unescape", NULL, 0 }, + { file_func, "file", NULL, 1 }, + { filesize_func, "filesize", NULL, 1 }, { NULL, NULL, NULL} }; /* XXX: base64 encode/decode ? */ static const struct expr_provider_single unary_op_providers[] = { - { op_nz, "n", NULL }, - { op_nz, "z", NULL }, - { op_R, "R", subnet_parse_arg }, - { op_T, "T", NULL }, - { op_file_min, "d", NULL }, - { op_file_min, "e", NULL }, - { op_file_min, "f", NULL }, - { op_file_min, "s", NULL }, - { op_file_link, "L", NULL }, - { op_file_link, "h", NULL }, - { op_file_xbit, "x", NULL }, - { op_file_subr, "F", NULL }, - { op_url_subr, "U", NULL }, + { op_nz, "n", NULL, 0 }, + { op_nz, "z", NULL, 0 }, + { op_R, "R", subnet_parse_arg, 0 }, + { op_T, "T", NULL, 0 }, + { op_file_min, "d", NULL, 1 }, + { op_file_min, "e", NULL, 1 }, + { op_file_min, "f", NULL, 1 }, + { op_file_min, "s", NULL, 1 }, + { op_file_link, "L", NULL, 1 }, + { op_file_link, "h", NULL, 1 }, + { op_file_xbit, "x", NULL, 1 }, + { op_file_subr, "F", NULL, 0 }, + { op_url_subr, "U", NULL, 0 }, + { op_url_subr, "A", NULL, 0 }, { NULL, NULL, NULL } }; static const struct expr_provider_single binary_op_providers[] = { - { op_ipmatch, "ipmatch", subnet_parse_arg }, - { op_fnmatch, "fnmatch", NULL }, - { op_strmatch, "strmatch", NULL }, - { op_strcmatch, "strcmatch", NULL }, + { op_ipmatch, "ipmatch", subnet_parse_arg, 0 }, + { op_fnmatch, "fnmatch", NULL, 0 }, + { op_strmatch, "strmatch", NULL, 0 }, + { op_strcmatch, "strcmatch", NULL, 0 }, { NULL, NULL, NULL } }; @@ -1423,6 +1453,15 @@ static int core_expr_lookup(ap_expr_lookup_parms *parms) } while (prov->func) { if (strcasecmp(prov->name, parms->name) == 0) { + if ((parms->flags & AP_EXPR_FLAGS_RESTRICTED) + && prov->restricted) { + *parms->err = + apr_psprintf(parms->ptemp, + "%s%s not available in restricted context", + (parms->type == AP_EXPR_FUNC_STRING) ? "" : "-", + prov->name); + return !OK; + } *parms->func = prov->func; if (prov->arg_parsing_func) { return prov->arg_parsing_func(parms); -- 2.47.2