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_request.h" 20#include "http_protocol.h" 21#include "http_config.h" 22#include "mpm_common.h" 23 24#include "apr.h" 25#include "apr_strings.h" 26#include "apr_time.h" 27#define APR_WANT_STRFUNC 28#include "apr_want.h" 29#include "apr_dbm.h" 30 31#if APR_HAVE_UNISTD_H 32#include <unistd.h> 33#endif 34 35#include "ap_socache.h" 36 37#if AP_NEED_SET_MUTEX_PERMS 38#include "unixd.h" 39#endif 40 41/* Use of the context structure must be thread-safe after the initial 42 * create/init; callers must hold the mutex. */ 43struct ap_socache_instance_t { 44 const char *data_file; 45 /* Pool must only be used with the mutex held. */ 46 apr_pool_t *pool; 47 apr_time_t last_expiry; 48 apr_interval_time_t expiry_interval; 49}; 50 51/** 52 * Support for DBM library 53 */ 54#define DBM_FILE_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD ) 55 56#define DEFAULT_DBM_PREFIX "socache-dbm-" 57 58/* ### this should use apr_dbm_usednames. */ 59#if !defined(DBM_FILE_SUFFIX_DIR) && !defined(DBM_FILE_SUFFIX_PAG) 60#if defined(DBM_SUFFIX) 61#define DBM_FILE_SUFFIX_DIR DBM_SUFFIX 62#define DBM_FILE_SUFFIX_PAG DBM_SUFFIX 63#elif defined(__FreeBSD__) || (defined(DB_LOCK) && defined(DB_SHMEM)) 64#define DBM_FILE_SUFFIX_DIR ".db" 65#define DBM_FILE_SUFFIX_PAG ".db" 66#else 67#define DBM_FILE_SUFFIX_DIR ".dir" 68#define DBM_FILE_SUFFIX_PAG ".pag" 69#endif 70#endif 71 72static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s); 73 74static apr_status_t socache_dbm_remove(ap_socache_instance_t *ctx, 75 server_rec *s, const unsigned char *id, 76 unsigned int idlen, apr_pool_t *p); 77 78static const char *socache_dbm_create(ap_socache_instance_t **context, 79 const char *arg, 80 apr_pool_t *tmp, apr_pool_t *p) 81{ 82 ap_socache_instance_t *ctx; 83 84 *context = ctx = apr_pcalloc(p, sizeof *ctx); 85 86 if (arg && *arg) { 87 ctx->data_file = ap_server_root_relative(p, arg); 88 if (!ctx->data_file) { 89 return apr_psprintf(tmp, "Invalid cache file path %s", arg); 90 } 91 } 92 93 apr_pool_create(&ctx->pool, p); 94 95 return NULL; 96} 97 98#if AP_NEED_SET_MUTEX_PERMS 99static int try_chown(apr_pool_t *p, server_rec *s, 100 const char *name, const char *suffix) 101{ 102 if (suffix) 103 name = apr_pstrcat(p, name, suffix, NULL); 104 if (-1 == chown(name, ap_unixd_config.user_id, 105 (gid_t)-1 /* no gid change */ )) 106 { 107 if (errno != ENOENT) 108 ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(errno), s, APLOGNO(00802) 109 "Can't change owner of %s", name); 110 return -1; 111 } 112 return 0; 113} 114#endif 115 116 117static apr_status_t socache_dbm_init(ap_socache_instance_t *ctx, 118 const char *namespace, 119 const struct ap_socache_hints *hints, 120 server_rec *s, apr_pool_t *p) 121{ 122 apr_dbm_t *dbm; 123 apr_status_t rv; 124 125 /* for the DBM we need the data file */ 126 if (ctx->data_file == NULL) { 127 const char *path = apr_pstrcat(p, DEFAULT_DBM_PREFIX, namespace, 128 NULL); 129 130 ctx->data_file = ap_runtime_dir_relative(p, path); 131 132 if (ctx->data_file == NULL) { 133 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00803) 134 "could not use default path '%s' for DBM socache", 135 path); 136 return APR_EINVAL; 137 } 138 } 139 140 /* open it once to create it and to make sure it _can_ be created */ 141 apr_pool_clear(ctx->pool); 142 143 if ((rv = apr_dbm_open(&dbm, ctx->data_file, 144 APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) { 145 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00804) 146 "Cannot create socache DBM file `%s'", 147 ctx->data_file); 148 return rv; 149 } 150 apr_dbm_close(dbm); 151 152 ctx->expiry_interval = (hints && hints->expiry_interval 153 ? hints->expiry_interval : apr_time_from_sec(30)); 154 155#if AP_NEED_SET_MUTEX_PERMS 156 /* 157 * We have to make sure the Apache child processes have access to 158 * the DBM file. But because there are brain-dead platforms where we 159 * cannot exactly determine the suffixes we try all possibilities. 160 */ 161 if (geteuid() == 0 /* is superuser */) { 162 try_chown(p, s, ctx->data_file, NULL); 163 if (try_chown(p, s, ctx->data_file, DBM_FILE_SUFFIX_DIR)) 164 if (try_chown(p, s, ctx->data_file, ".db")) 165 try_chown(p, s, ctx->data_file, ".dir"); 166 if (try_chown(p, s, ctx->data_file, DBM_FILE_SUFFIX_PAG)) 167 if (try_chown(p, s, ctx->data_file, ".db")) 168 try_chown(p, s, ctx->data_file, ".pag"); 169 } 170#endif 171 socache_dbm_expire(ctx, s); 172 173 return APR_SUCCESS; 174} 175 176static void socache_dbm_destroy(ap_socache_instance_t *ctx, server_rec *s) 177{ 178 /* the correct way */ 179 unlink(apr_pstrcat(ctx->pool, ctx->data_file, DBM_FILE_SUFFIX_DIR, NULL)); 180 unlink(apr_pstrcat(ctx->pool, ctx->data_file, DBM_FILE_SUFFIX_PAG, NULL)); 181 /* the additional ways to be sure */ 182 unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".dir", NULL)); 183 unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".pag", NULL)); 184 unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".db", NULL)); 185 unlink(ctx->data_file); 186 187 return; 188} 189 190static apr_status_t socache_dbm_store(ap_socache_instance_t *ctx, 191 server_rec *s, const unsigned char *id, 192 unsigned int idlen, apr_time_t expiry, 193 unsigned char *ucaData, 194 unsigned int nData, apr_pool_t *pool) 195{ 196 apr_dbm_t *dbm; 197 apr_datum_t dbmkey; 198 apr_datum_t dbmval; 199 apr_status_t rv; 200 201 /* be careful: do not try to store too much bytes in a DBM file! */ 202#ifdef PAIRMAX 203 if ((idlen + nData) >= PAIRMAX) { 204 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00805) 205 "data size too large for DBM socache: %d >= %d", 206 (idlen + nData), PAIRMAX); 207 return APR_ENOSPC; 208 } 209#else 210 if ((idlen + nData) >= 950 /* at least less than approx. 1KB */) { 211 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00806) 212 "data size too large for DBM socache: %d >= %d", 213 (idlen + nData), 950); 214 return APR_ENOSPC; 215 } 216#endif 217 218 /* create DBM key */ 219 dbmkey.dptr = (char *)id; 220 dbmkey.dsize = idlen; 221 222 /* create DBM value */ 223 dbmval.dsize = sizeof(apr_time_t) + nData; 224 dbmval.dptr = (char *)ap_malloc(dbmval.dsize); 225 memcpy((char *)dbmval.dptr, &expiry, sizeof(apr_time_t)); 226 memcpy((char *)dbmval.dptr+sizeof(apr_time_t), ucaData, nData); 227 228 /* and store it to the DBM file */ 229 apr_pool_clear(ctx->pool); 230 231 if ((rv = apr_dbm_open(&dbm, ctx->data_file, 232 APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) { 233 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00807) 234 "Cannot open socache DBM file `%s' for writing " 235 "(store)", 236 ctx->data_file); 237 free(dbmval.dptr); 238 return rv; 239 } 240 if ((rv = apr_dbm_store(dbm, dbmkey, dbmval)) != APR_SUCCESS) { 241 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00808) 242 "Cannot store socache object to DBM file `%s'", 243 ctx->data_file); 244 apr_dbm_close(dbm); 245 free(dbmval.dptr); 246 return rv; 247 } 248 apr_dbm_close(dbm); 249 250 /* free temporary buffers */ 251 free(dbmval.dptr); 252 253 /* allow the regular expiring to occur */ 254 socache_dbm_expire(ctx, s); 255 256 return APR_SUCCESS; 257} 258 259static apr_status_t socache_dbm_retrieve(ap_socache_instance_t *ctx, server_rec *s, 260 const unsigned char *id, unsigned int idlen, 261 unsigned char *dest, unsigned int *destlen, 262 apr_pool_t *p) 263{ 264 apr_dbm_t *dbm; 265 apr_datum_t dbmkey; 266 apr_datum_t dbmval; 267 unsigned int nData; 268 apr_time_t expiry; 269 apr_time_t now; 270 apr_status_t rc; 271 272 /* allow the regular expiring to occur */ 273 socache_dbm_expire(ctx, s); 274 275 /* create DBM key and values */ 276 dbmkey.dptr = (char *)id; 277 dbmkey.dsize = idlen; 278 279 /* and fetch it from the DBM file 280 * XXX: Should we open the dbm against r->pool so the cleanup will 281 * do the apr_dbm_close? This would make the code a bit cleaner. 282 */ 283 apr_pool_clear(ctx->pool); 284 if ((rc = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 285 DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) { 286 ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, APLOGNO(00809) 287 "Cannot open socache DBM file `%s' for reading " 288 "(fetch)", 289 ctx->data_file); 290 return rc; 291 } 292 rc = apr_dbm_fetch(dbm, dbmkey, &dbmval); 293 if (rc != APR_SUCCESS) { 294 apr_dbm_close(dbm); 295 return APR_NOTFOUND; 296 } 297 if (dbmval.dptr == NULL || dbmval.dsize <= sizeof(apr_time_t)) { 298 apr_dbm_close(dbm); 299 return APR_EGENERAL; 300 } 301 302 /* parse resulting data */ 303 nData = dbmval.dsize-sizeof(apr_time_t); 304 if (nData > *destlen) { 305 apr_dbm_close(dbm); 306 return APR_ENOSPC; 307 } 308 309 *destlen = nData; 310 memcpy(&expiry, dbmval.dptr, sizeof(apr_time_t)); 311 memcpy(dest, (char *)dbmval.dptr + sizeof(apr_time_t), nData); 312 313 apr_dbm_close(dbm); 314 315 /* make sure the stuff is still not expired */ 316 now = apr_time_now(); 317 if (expiry <= now) { 318 socache_dbm_remove(ctx, s, id, idlen, p); 319 return APR_NOTFOUND; 320 } 321 322 return APR_SUCCESS; 323} 324 325static apr_status_t socache_dbm_remove(ap_socache_instance_t *ctx, 326 server_rec *s, const unsigned char *id, 327 unsigned int idlen, apr_pool_t *p) 328{ 329 apr_dbm_t *dbm; 330 apr_datum_t dbmkey; 331 apr_status_t rv; 332 333 /* create DBM key and values */ 334 dbmkey.dptr = (char *)id; 335 dbmkey.dsize = idlen; 336 337 /* and delete it from the DBM file */ 338 apr_pool_clear(ctx->pool); 339 340 if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 341 DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) { 342 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00810) 343 "Cannot open socache DBM file `%s' for writing " 344 "(delete)", 345 ctx->data_file); 346 return rv; 347 } 348 apr_dbm_delete(dbm, dbmkey); 349 apr_dbm_close(dbm); 350 351 return APR_SUCCESS; 352} 353 354static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s) 355{ 356 apr_dbm_t *dbm; 357 apr_datum_t dbmkey; 358 apr_datum_t dbmval; 359 apr_time_t expiry; 360 int elts = 0; 361 int deleted = 0; 362 int expired; 363 apr_datum_t *keylist; 364 int keyidx; 365 int i; 366 apr_time_t now; 367 apr_status_t rv; 368 369 /* 370 * make sure the expiration for still not-accessed 371 * socache entries is done only from time to time 372 */ 373 now = apr_time_now(); 374 375 if (now < ctx->last_expiry + ctx->expiry_interval) { 376 return; 377 } 378 379 ctx->last_expiry = now; 380 381 /* 382 * Here we have to be very carefully: Not all DBM libraries are 383 * smart enough to allow one to iterate over the elements and at the 384 * same time delete expired ones. Some of them get totally crazy 385 * while others have no problems. So we have to do it the slower but 386 * more safe way: we first iterate over all elements and remember 387 * those which have to be expired. Then in a second pass we delete 388 * all those expired elements. Additionally we reopen the DBM file 389 * to be really safe in state. 390 */ 391 392#define KEYMAX 1024 393 394 for (;;) { 395 /* allocate the key array in a memory sub pool */ 396 apr_pool_clear(ctx->pool); 397 398 if ((keylist = apr_palloc(ctx->pool, sizeof(dbmkey)*KEYMAX)) == NULL) { 399 break; 400 } 401 402 /* pass 1: scan DBM database */ 403 keyidx = 0; 404 if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 405 DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) { 406 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00811) 407 "Cannot open socache DBM file `%s' for " 408 "scanning", 409 ctx->data_file); 410 break; 411 } 412 apr_dbm_firstkey(dbm, &dbmkey); 413 while (dbmkey.dptr != NULL) { 414 elts++; 415 expired = FALSE; 416 apr_dbm_fetch(dbm, dbmkey, &dbmval); 417 if (dbmval.dsize <= sizeof(apr_time_t) || dbmval.dptr == NULL) 418 expired = TRUE; 419 else { 420 memcpy(&expiry, dbmval.dptr, sizeof(apr_time_t)); 421 if (expiry <= now) 422 expired = TRUE; 423 } 424 if (expired) { 425 if ((keylist[keyidx].dptr = apr_pmemdup(ctx->pool, dbmkey.dptr, dbmkey.dsize)) != NULL) { 426 keylist[keyidx].dsize = dbmkey.dsize; 427 keyidx++; 428 if (keyidx == KEYMAX) 429 break; 430 } 431 } 432 apr_dbm_nextkey(dbm, &dbmkey); 433 } 434 apr_dbm_close(dbm); 435 436 /* pass 2: delete expired elements */ 437 if (apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 438 DBM_FILE_MODE, ctx->pool) != APR_SUCCESS) { 439 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00812) 440 "Cannot re-open socache DBM file `%s' for " 441 "expiring", 442 ctx->data_file); 443 break; 444 } 445 for (i = 0; i < keyidx; i++) { 446 apr_dbm_delete(dbm, keylist[i]); 447 deleted++; 448 } 449 apr_dbm_close(dbm); 450 451 if (keyidx < KEYMAX) 452 break; 453 } 454 455 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00813) 456 "DBM socache expiry: " 457 "old: %d, new: %d, removed: %d", 458 elts, elts-deleted, deleted); 459} 460 461static void socache_dbm_status(ap_socache_instance_t *ctx, request_rec *r, 462 int flags) 463{ 464 apr_dbm_t *dbm; 465 apr_datum_t dbmkey; 466 apr_datum_t dbmval; 467 int elts; 468 long size; 469 int avg; 470 apr_status_t rv; 471 472 elts = 0; 473 size = 0; 474 475 apr_pool_clear(ctx->pool); 476 if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 477 DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) { 478 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00814) 479 "Cannot open socache DBM file `%s' for status " 480 "retrival", 481 ctx->data_file); 482 return; 483 } 484 /* 485 * XXX - Check the return value of apr_dbm_firstkey, apr_dbm_fetch - TBD 486 */ 487 apr_dbm_firstkey(dbm, &dbmkey); 488 for ( ; dbmkey.dptr != NULL; apr_dbm_nextkey(dbm, &dbmkey)) { 489 apr_dbm_fetch(dbm, dbmkey, &dbmval); 490 if (dbmval.dptr == NULL) 491 continue; 492 elts += 1; 493 size += dbmval.dsize; 494 } 495 apr_dbm_close(dbm); 496 if (size > 0 && elts > 0) 497 avg = (int)(size / (long)elts); 498 else 499 avg = 0; 500 ap_rprintf(r, "cache type: <b>DBM</b>, maximum size: <b>unlimited</b><br>"); 501 ap_rprintf(r, "current entries: <b>%d</b>, current size: <b>%ld</b> bytes<br>", elts, size); 502 ap_rprintf(r, "average entry size: <b>%d</b> bytes<br>", avg); 503 return; 504} 505 506static apr_status_t socache_dbm_iterate(ap_socache_instance_t *ctx, 507 server_rec *s, void *userctx, 508 ap_socache_iterator_t *iterator, 509 apr_pool_t *pool) 510{ 511 apr_dbm_t *dbm; 512 apr_datum_t dbmkey; 513 apr_datum_t dbmval; 514 apr_time_t expiry; 515 int expired; 516 apr_time_t now; 517 apr_status_t rv; 518 519 /* 520 * make sure the expired records are omitted 521 */ 522 now = apr_time_now(); 523 if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 524 DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) { 525 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00815) 526 "Cannot open socache DBM file `%s' for " 527 "iterating", ctx->data_file); 528 return rv; 529 } 530 rv = apr_dbm_firstkey(dbm, &dbmkey); 531 while (rv == APR_SUCCESS && dbmkey.dptr != NULL) { 532 expired = FALSE; 533 apr_dbm_fetch(dbm, dbmkey, &dbmval); 534 if (dbmval.dsize <= sizeof(apr_time_t) || dbmval.dptr == NULL) 535 expired = TRUE; 536 else { 537 memcpy(&expiry, dbmval.dptr, sizeof(apr_time_t)); 538 if (expiry <= now) 539 expired = TRUE; 540 } 541 if (!expired) { 542 rv = iterator(ctx, s, userctx, 543 (unsigned char *)dbmkey.dptr, dbmkey.dsize, 544 (unsigned char *)dbmval.dptr + sizeof(apr_time_t), 545 dbmval.dsize - sizeof(apr_time_t), pool); 546 ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00816) 547 "dbm `%s' entry iterated", ctx->data_file); 548 if (rv != APR_SUCCESS) 549 return rv; 550 } 551 rv = apr_dbm_nextkey(dbm, &dbmkey); 552 } 553 apr_dbm_close(dbm); 554 555 if (rv != APR_SUCCESS && rv != APR_EOF) { 556 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00817) 557 "Failure reading first/next socache DBM file `%s' record", 558 ctx->data_file); 559 return rv; 560 } 561 return APR_SUCCESS; 562} 563 564static const ap_socache_provider_t socache_dbm = { 565 "dbm", 566 AP_SOCACHE_FLAG_NOTMPSAFE, 567 socache_dbm_create, 568 socache_dbm_init, 569 socache_dbm_destroy, 570 socache_dbm_store, 571 socache_dbm_retrieve, 572 socache_dbm_remove, 573 socache_dbm_status, 574 socache_dbm_iterate 575}; 576 577static void register_hooks(apr_pool_t *p) 578{ 579 ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "dbm", 580 AP_SOCACHE_PROVIDER_VERSION, 581 &socache_dbm); 582} 583 584AP_DECLARE_MODULE(socache_dbm) = { 585 STANDARD20_MODULE_STUFF, 586 NULL, NULL, NULL, NULL, NULL, 587 register_hooks 588}; 589