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#include "httpd.h" 18#include "http_log.h" 19#include "http_config.h" 20#include "ap_provider.h" 21#include "http_request.h" 22#include "http_protocol.h" 23#include "http_core.h" 24#include "apr_dbd.h" 25#include "mod_dbd.h" 26#include "apr_strings.h" 27#include "mod_authz_dbd.h" 28 29#include "mod_auth.h" 30 31 32module AP_MODULE_DECLARE_DATA authz_dbd_module; 33 34/* Export a hook for modules that manage clientside sessions 35 * (e.g. mod_auth_cookie) 36 * to deal with those when we successfully login/logout at the server 37 * 38 * XXX: WHY would this be specific to dbd_authz? Why wouldn't we track 39 * this across all authz user providers in a lower level mod, such as 40 * mod_auth_basic/digest? 41 */ 42APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(authz_dbd, AUTHZ_DBD, int, client_login, 43 (request_rec *r, int code, const char *action), 44 (r, code, action), OK, DECLINED) 45 46 47typedef struct { 48 const char *query; 49 const char *redir_query; 50 int redirect; 51} authz_dbd_cfg ; 52 53static ap_dbd_t *(*dbd_handle)(request_rec*) = NULL; 54static void (*dbd_prepare)(server_rec*, const char*, const char*) = NULL; 55 56static const char *const noerror = "???"; 57 58static void *authz_dbd_cr_cfg(apr_pool_t *pool, char *dummy) 59{ 60 authz_dbd_cfg *ret = apr_pcalloc(pool, sizeof(authz_dbd_cfg)); 61 ret->redirect = -1; 62 return ret; 63} 64static void *authz_dbd_merge_cfg(apr_pool_t *pool, void *BASE, void *ADD) 65{ 66 authz_dbd_cfg *base = BASE; 67 authz_dbd_cfg *add = ADD; 68 authz_dbd_cfg *ret = apr_palloc(pool, sizeof(authz_dbd_cfg)); 69 70 ret->query = (add->query == NULL) ? base->query : add->query; 71 ret->redir_query = (add->redir_query == NULL) 72 ? base->redir_query : add->redir_query; 73 ret->redirect = (add->redirect == -1) ? base->redirect : add->redirect; 74 return ret; 75} 76static const char *authz_dbd_prepare(cmd_parms *cmd, void *cfg, 77 const char *query) 78{ 79 static unsigned int label_num = 0; 80 char *label; 81 const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS); 82 if (err) 83 return err; 84 85 if (dbd_prepare == NULL) { 86 dbd_prepare = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_prepare); 87 if (dbd_prepare == NULL) { 88 return "You must load mod_dbd to enable AuthzDBD functions"; 89 } 90 dbd_handle = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire); 91 } 92 label = apr_psprintf(cmd->pool, "authz_dbd_%d", ++label_num); 93 94 dbd_prepare(cmd->server, query, label); 95 96 /* save the label here for our own use */ 97 return ap_set_string_slot(cmd, cfg, label); 98} 99static const command_rec authz_dbd_cmds[] = { 100 AP_INIT_FLAG("AuthzDBDLoginToReferer", ap_set_flag_slot, 101 (void*)APR_OFFSETOF(authz_dbd_cfg, redirect), ACCESS_CONF, 102 "Whether to redirect to referer on successful login"), 103 AP_INIT_TAKE1("AuthzDBDQuery", authz_dbd_prepare, 104 (void*)APR_OFFSETOF(authz_dbd_cfg, query), ACCESS_CONF, 105 "SQL query for DBD Authz or login"), 106 AP_INIT_TAKE1("AuthzDBDRedirectQuery", authz_dbd_prepare, 107 (void*)APR_OFFSETOF(authz_dbd_cfg, redir_query), ACCESS_CONF, 108 "SQL query to get per-user redirect URL after login"), 109 {NULL} 110}; 111 112static int authz_dbd_login(request_rec *r, authz_dbd_cfg *cfg, 113 const char *action) 114{ 115 int rv; 116 const char *newuri = NULL; 117 int nrows; 118 const char *message; 119 ap_dbd_t *dbd = dbd_handle(r); 120 apr_dbd_prepared_t *query; 121 apr_dbd_results_t *res = NULL; 122 apr_dbd_row_t *row = NULL; 123 124 if (cfg->query == NULL) { 125 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01642) 126 "No query configured for %s!", action); 127 return HTTP_INTERNAL_SERVER_ERROR; 128 } 129 query = apr_hash_get(dbd->prepared, cfg->query, APR_HASH_KEY_STRING); 130 if (query == NULL) { 131 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01643) 132 "Error retrieving Query for %s!", action); 133 return HTTP_INTERNAL_SERVER_ERROR; 134 } 135 136 rv = apr_dbd_pvquery(dbd->driver, r->pool, dbd->handle, &nrows, 137 query, r->user, NULL); 138 if (rv == 0) { 139 if (nrows != 1) { 140 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01644) 141 "authz_dbd: %s of user %s updated %d rows", 142 action, r->user, nrows); 143 } 144 } 145 else { 146 message = apr_dbd_error(dbd->driver, dbd->handle, rv); 147 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01645) 148 "authz_dbd: query for %s failed; user %s [%s]", 149 action, r->user, message?message:noerror); 150 return HTTP_INTERNAL_SERVER_ERROR; 151 } 152 153 if (cfg->redirect == 1) { 154 newuri = apr_table_get(r->headers_in, "Referer"); 155 } 156 157 if (!newuri && cfg->redir_query) { 158 query = apr_hash_get(dbd->prepared, cfg->redir_query, 159 APR_HASH_KEY_STRING); 160 if (query == NULL) { 161 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01646) 162 "authz_dbd: no redirect query!"); 163 /* OK, this is non-critical; we can just not-redirect */ 164 } 165 else if ((rv = apr_dbd_pvselect(dbd->driver, r->pool, dbd->handle, 166 &res, query, 0, r->user, NULL)) == 0) { 167 for (rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1); 168 rv != -1; 169 rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1)) { 170 if (rv != 0) { 171 message = apr_dbd_error(dbd->driver, dbd->handle, rv); 172 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01647) 173 "authz_dbd in get_row; action=%s user=%s [%s]", 174 action, r->user, message?message:noerror); 175 } 176 else if (newuri == NULL) { 177 newuri = apr_dbd_get_entry(dbd->driver, row, 0); 178 } 179 /* we can't break out here or row won't get cleaned up */ 180 } 181 } 182 else { 183 message = apr_dbd_error(dbd->driver, dbd->handle, rv); 184 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01648) 185 "authz_dbd/redirect for %s of %s [%s]", 186 action, r->user, message?message:noerror); 187 } 188 } 189 if (newuri != NULL) { 190 r->status = HTTP_MOVED_TEMPORARILY; 191 apr_table_set(r->err_headers_out, "Location", newuri); 192 } 193 authz_dbd_run_client_login(r, OK, action); 194 return OK; 195} 196 197static int authz_dbd_group_query(request_rec *r, authz_dbd_cfg *cfg, 198 apr_array_header_t *groups) 199{ 200 /* SELECT group FROM authz WHERE user = %s */ 201 int rv; 202 const char *message; 203 ap_dbd_t *dbd = dbd_handle(r); 204 apr_dbd_prepared_t *query; 205 apr_dbd_results_t *res = NULL; 206 apr_dbd_row_t *row = NULL; 207 const char **group; 208 209 if (cfg->query == NULL) { 210 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01649) 211 "No query configured for dbd-group!"); 212 return HTTP_INTERNAL_SERVER_ERROR; 213 } 214 query = apr_hash_get(dbd->prepared, cfg->query, APR_HASH_KEY_STRING); 215 if (query == NULL) { 216 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01650) 217 "Error retrieving query for dbd-group!"); 218 return HTTP_INTERNAL_SERVER_ERROR; 219 } 220 rv = apr_dbd_pvselect(dbd->driver, r->pool, dbd->handle, &res, 221 query, 0, r->user, NULL); 222 if (rv == 0) { 223 for (rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1); 224 rv != -1; 225 rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1)) { 226 if (rv == 0) { 227 group = apr_array_push(groups); 228 *group = apr_dbd_get_entry(dbd->driver, row, 0); 229 } 230 else { 231 message = apr_dbd_error(dbd->driver, dbd->handle, rv); 232 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01651) 233 "authz_dbd in get_row; group query for user=%s [%s]", 234 r->user, message?message:noerror); 235 return HTTP_INTERNAL_SERVER_ERROR; 236 } 237 } 238 } 239 else { 240 message = apr_dbd_error(dbd->driver, dbd->handle, rv); 241 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01652) 242 "authz_dbd, in groups query for %s [%s]", 243 r->user, message?message:noerror); 244 return HTTP_INTERNAL_SERVER_ERROR; 245 } 246 return OK; 247} 248 249static authz_status dbdgroup_check_authorization(request_rec *r, 250 const char *require_args, 251 const void *parsed_require_args) 252{ 253 int i, rv; 254 const char *w; 255 apr_array_header_t *groups = NULL; 256 257 const char *err = NULL; 258 const ap_expr_info_t *expr = parsed_require_args; 259 const char *require; 260 261 const char *t; 262 authz_dbd_cfg *cfg = ap_get_module_config(r->per_dir_config, 263 &authz_dbd_module); 264 265 if (!r->user) { 266 return AUTHZ_DENIED_NO_USER; 267 } 268 269 if (groups == NULL) { 270 groups = apr_array_make(r->pool, 4, sizeof(const char*)); 271 rv = authz_dbd_group_query(r, cfg, groups); 272 if (rv != OK) { 273 return AUTHZ_GENERAL_ERROR; 274 } 275 } 276 277 require = ap_expr_str_exec(r, expr, &err); 278 if (err) { 279 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02590) 280 "authz_dbd authorize: require dbd-group: Can't " 281 "evaluate require expression: %s", err); 282 return AUTHZ_DENIED; 283 } 284 285 t = require; 286 while (t[0]) { 287 w = ap_getword_white(r->pool, &t); 288 for (i=0; i < groups->nelts; ++i) { 289 if (!strcmp(w, ((const char**)groups->elts)[i])) { 290 return AUTHZ_GRANTED; 291 } 292 } 293 } 294 295 return AUTHZ_DENIED; 296} 297 298static authz_status dbdlogin_check_authorization(request_rec *r, 299 const char *require_args, 300 const void *parsed_require_args) 301{ 302 authz_dbd_cfg *cfg = ap_get_module_config(r->per_dir_config, 303 &authz_dbd_module); 304 305 if (!r->user) { 306 return AUTHZ_DENIED_NO_USER; 307 } 308 309 return (authz_dbd_login(r, cfg, "login") == OK ? AUTHZ_GRANTED : AUTHZ_DENIED); 310} 311 312static authz_status dbdlogout_check_authorization(request_rec *r, 313 const char *require_args, 314 const void *parsed_require_args) 315{ 316 authz_dbd_cfg *cfg = ap_get_module_config(r->per_dir_config, 317 &authz_dbd_module); 318 319 if (!r->user) { 320 return AUTHZ_DENIED_NO_USER; 321 } 322 323 return (authz_dbd_login(r, cfg, "logout") == OK ? AUTHZ_GRANTED : AUTHZ_DENIED); 324} 325 326static const char *dbd_parse_config(cmd_parms *cmd, const char *require_line, 327 const void **parsed_require_line) 328{ 329 const char *expr_err = NULL; 330 ap_expr_info_t *expr; 331 332 expr = ap_expr_parse_cmd(cmd, require_line, AP_EXPR_FLAG_STRING_RESULT, 333 &expr_err, NULL); 334 335 if (expr_err) 336 return apr_pstrcat(cmd->temp_pool, 337 "Cannot parse expression in require line: ", 338 expr_err, NULL); 339 340 *parsed_require_line = expr; 341 342 return NULL; 343} 344 345static const authz_provider authz_dbdgroup_provider = 346{ 347 &dbdgroup_check_authorization, 348 &dbd_parse_config, 349}; 350 351static const authz_provider authz_dbdlogin_provider = 352{ 353 &dbdlogin_check_authorization, 354 NULL, 355}; 356 357 358static const authz_provider authz_dbdlogout_provider = 359{ 360 &dbdlogout_check_authorization, 361 NULL, 362}; 363 364static void authz_dbd_hooks(apr_pool_t *p) 365{ 366 ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "dbd-group", 367 AUTHZ_PROVIDER_VERSION, 368 &authz_dbdgroup_provider, 369 AP_AUTH_INTERNAL_PER_CONF); 370 ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "dbd-login", 371 AUTHZ_PROVIDER_VERSION, 372 &authz_dbdlogin_provider, 373 AP_AUTH_INTERNAL_PER_CONF); 374 ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "dbd-logout", 375 AUTHZ_PROVIDER_VERSION, 376 &authz_dbdlogout_provider, 377 AP_AUTH_INTERNAL_PER_CONF); 378} 379 380AP_DECLARE_MODULE(authz_dbd) = 381{ 382 STANDARD20_MODULE_STUFF, 383 authz_dbd_cr_cfg, 384 authz_dbd_merge_cfg, 385 NULL, 386 NULL, 387 authz_dbd_cmds, 388 authz_dbd_hooks 389}; 390 391