Back to index

opendkim  2.6.6
dkim-cache.c
Go to the documentation of this file.
00001 /*
00002 **  Copyright (c) 2007-2009 Sendmail, Inc. and its suppliers.
00003 **    All rights reserved.
00004 **
00005 **  Copyright (c) 2009, The OpenDKIM Project.  All rights reserved.
00006 */
00007 
00008 #include "build-config.h"
00009 
00010 #ifdef QUERY_CACHE
00011 
00012 #ifndef lint
00013 static char dkim_cache_c_id[] = "@(#)$Id: dkim-cache.c,v 1.7 2009/11/22 08:15:50 grooverdan Exp $";
00014 #endif /* !lint */
00015 
00016 /* system includes */
00017 #include <sys/param.h>
00018 #include <sys/types.h>
00019 #include <sys/stat.h>
00020 #include <assert.h>
00021 #include <stdio.h>
00022 #include <fcntl.h>
00023 #include <errno.h>
00024 #include <pthread.h>
00025 #include <string.h>
00026 
00027 /* libdb includes */
00028 #include <db.h>
00029 
00030 /* libopendkim includes */
00031 #include "dkim-internal.h"
00032 #include "dkim-cache.h"
00033 #include "dkim-strl.h"
00034 
00035 /* limits, macros, etc. */
00036 #define       BUFRSZ               1024
00037 #define DB_MODE                    (S_IRUSR|S_IWUSR)
00038 
00039 #ifndef DB_NOTFOUND
00040 # define DB_NOTFOUND        1
00041 #endif /* ! DB_NOTFOUND */
00042 
00043 #ifndef DB_VERSION_MAJOR
00044 # define DB_VERSION_MAJOR   1
00045 #endif /* ! DB_VERSION_MAJOR */
00046 
00047 #define DB_VERSION_CHECK(x,y,z) ((DB_VERSION_MAJOR == (x) && \
00048                               DB_VERSION_MINOR == (y) && \
00049                               DB_VERSION_PATCH >= (z)) || \
00050                              (DB_VERSION_MAJOR == (x) && \
00051                               DB_VERSION_MINOR > (y)) || \
00052                              DB_VERSION_MAJOR > (x))
00053 
00054 /* data types */
00055 struct dkim_cache_entry
00056 {
00057        int           cache_ttl;
00058        time_t        cache_when;
00059        char          cache_data[BUFRSZ + 1];
00060 };
00061 
00062 /* globals */
00063 static pthread_mutex_t cache_stats_lock; /* stats lock */
00064 static u_int c_hits = 0;           /* cache hits */
00065 static u_int c_queries = 0;        /* cache queries */
00066 static u_int c_expired = 0;        /* expired cache hits */
00067 static pthread_mutex_t cache_lock; /* cache lock */
00068 
00069 /*
00070 **  DKIM_CACHE_INIT -- initialize an on-disk cache of entries
00071 **
00072 **  Parameters:
00073 **     err -- error code (returned)
00074 **     tmpdir -- temporary directory to use (may be NULL)
00075 **
00076 **  Return value:
00077 **     A DB handle referring to the cache, or NULL on error.
00078 */
00079 
00080 DB *
00081 dkim_cache_init(int *err, char *tmpdir)
00082 {
00083        int status = 0;
00084        DB *cache = NULL;
00085 
00086        c_hits = 0;
00087        c_queries = 0;
00088        c_expired = 0;
00089 
00090        (void) pthread_mutex_init(&cache_stats_lock, NULL);
00091        (void) pthread_mutex_init(&cache_lock, NULL);
00092 
00093 #if DB_VERSION_CHECK(3,0,0)
00094        status = db_create(&cache, NULL, 0);
00095        if (status == 0)
00096        {
00097 # if DB_VERSION_CHECK(4,2,0)
00098               /* tell libdb which temporary directory to use */
00099               if (tmpdir != NULL && tmpdir[0] != '\0')
00100               {
00101                      DB_ENV *env = NULL;
00102 
00103 #  if DB_VERSION_CHECK(4,3,0)
00104                      env = cache->get_env(cache);
00105 #  else /* DB_VERSION_CHECK(4,3,0) */
00106                      (void) cache->get_env(cache, &env);
00107 #  endif /* DB_VERISON_CHECK(4,3,0) */
00108 
00109                      if (env != NULL)
00110                             (void) env->set_tmp_dir(env, tmpdir);
00111               }
00112 # endif /* DB_VERISON_CHECK(4,2,0) */
00113 
00114 # if DB_VERSION_CHECK(4,1,25)
00115               status = cache->open(cache, NULL, NULL, NULL, DB_HASH,
00116                                    DB_CREATE, DB_MODE);
00117 # else /* DB_VERSION_CHECK(4,1,25) */
00118               status = cache->open(cache, NULL, NULL, DB_HASH,
00119                                    DB_CREATE, DB_MODE);
00120 # endif /* DB_VERSION_CHECK(4,1,25) */
00121        }
00122 #elif DB_VERSION_CHECK(2,0,0)
00123        status = db_open(NULL, DB_HASH, DB_CREATE, DB_MODE,
00124                         NULL, NULL, &cache);
00125 #else /* ! DB_VERSION_CHECK(2,0,0) */
00126        cache = dbopen(NULL, (O_CREAT|O_RDWR), DB_MODE, DB_HASH, NULL);
00127        if (cache == NULL)
00128               status = errno;
00129 #endif /* DB_VERSION_CHECK */
00130 
00131        if (status != 0)
00132        {
00133               if (err != NULL)
00134                      *err = status;
00135 
00136               return NULL;
00137        }
00138 
00139        return cache;
00140 }
00141 
00142 /*
00143 **  DKIM_CACHE_QUERY -- query an on-disk cache of entries
00144 **
00145 **  Parameters:
00146 **     db -- DB handle referring to the cache
00147 **     str -- key to query
00148 **     ttl -- time-to-live; ignore any record older than this; if 0, apply
00149 **            the TTL in the record
00150 **     buf -- buffer into which to write any cached data found
00151 **     buflen -- number of bytes at "buffer" (returned); caller should set
00152 **               this to the maximum space available and use the returned
00153 **               value as the length of the data returned
00154 **     err -- error code (returned)
00155 **
00156 **  Return value:
00157 **     -1 -- error; caller should check "err"
00158 **     0 -- no error; record found and data returned
00159 **     1 -- no data found or data has expired
00160 */
00161 
00162 int
00163 dkim_cache_query(DB *db, char *str, int ttl, char *buf, size_t *buflen,
00164                  int *err)
00165 {
00166        int status;
00167        time_t now;
00168        DBT q;
00169        DBT d;
00170        struct dkim_cache_entry ce;
00171 
00172        assert(db != NULL);
00173        assert(str != NULL);
00174        assert(buf != NULL);
00175        assert(err != NULL);
00176 
00177        memset(&q, '\0', sizeof q);
00178        memset(&d, '\0', sizeof d);
00179 
00180        q.data = str;
00181        q.size = strlen(q.data);
00182 
00183 #if DB_VERSION_CHECK(2,0,0)
00184        d.flags = DB_DBT_USERMEM;
00185        d.data = (void *) &ce;
00186        d.ulen = sizeof ce;
00187 #endif /* DB_VERSION_CHECK(2,0,0) */
00188 
00189        (void) time(&now);
00190 
00191        pthread_mutex_lock(&cache_stats_lock);
00192        c_queries++;
00193        pthread_mutex_unlock(&cache_stats_lock);
00194 
00195        pthread_mutex_lock(&cache_lock);
00196 
00197 #if DB_VERSION_CHECK(2,0,0)
00198        status = db->get(db, NULL, &q, &d, 0);
00199 #else /* DB_VERSION_CHECK(2,0,0) */
00200        status = db->get(db, &q, &d, 0);
00201 #endif /* DB_VERSION_CHECK(2,0,0) */
00202 
00203        pthread_mutex_unlock(&cache_lock);
00204 
00205        if (status == 0)
00206        {
00207 #if !DB_VERSION_CHECK(2,0,0)
00208               memset(&ce, '\0', sizeof ce);
00209               memcpy(&ce, d.data, MIN(sizeof ce, d.size));
00210 #endif /* ! DB_VERSION_CHECK(2,0,0) */
00211 
00212               if (ttl != 0)
00213                      ce.cache_ttl = ttl;
00214               if (ce.cache_when + ce.cache_ttl < now)
00215               {
00216                      pthread_mutex_lock(&cache_stats_lock);
00217                      c_expired++;
00218                      pthread_mutex_unlock(&cache_stats_lock);
00219 
00220                      return 1;
00221               }
00222 
00223               pthread_mutex_lock(&cache_stats_lock);
00224               c_hits++;
00225               pthread_mutex_unlock(&cache_stats_lock);
00226 
00227               strlcpy(buf, ce.cache_data, *buflen);
00228               *buflen = strlen(ce.cache_data);
00229               return 0;
00230        }
00231        else if (status != DB_NOTFOUND)
00232        {
00233               *err = status;
00234               return -1;
00235        }
00236        else
00237        {
00238               return 1;
00239        }
00240 }
00241 
00242 /*
00243 **  DKIM_CACHE_INSERT -- insert data into an on-disk cache of entries
00244 **
00245 **  Parameters:
00246 **     db -- DB handle referring to the cache
00247 **     str -- key to insert
00248 **     data -- data to insert
00249 **     ttl -- time-to-live
00250 **     err -- error code (returned)
00251 **
00252 **  Return value:
00253 **     -1 -- error; caller should check "err"
00254 **     0 -- cache updated
00255 */
00256 
00257 int
00258 dkim_cache_insert(DB *db, char *str, char *data, int ttl, int *err)
00259 {
00260        int status;
00261        time_t now;
00262        DBT q;
00263        DBT d;
00264        struct dkim_cache_entry ce;
00265 
00266        assert(db != NULL);
00267        assert(str != NULL);
00268        assert(data != NULL);
00269        assert(err != NULL);
00270 
00271        (void) time(&now);
00272 
00273        memset(&q, '\0', sizeof q);
00274        memset(&d, '\0', sizeof d);
00275 
00276        q.data = str;
00277        q.size = strlen(str);
00278 
00279        d.data = (void *) &ce;
00280        d.size = sizeof ce;
00281 
00282        ce.cache_when = now;
00283        ce.cache_ttl = ttl;
00284        strlcpy(ce.cache_data, data, sizeof ce.cache_data);
00285 
00286        pthread_mutex_lock(&cache_lock);
00287 
00288 #if DB_VERSION_CHECK(2,0,0)
00289        status = db->put(db, NULL, &q, &d, 0);
00290 #else /* DB_VERSION_CHECK(2,0,0) */
00291        status = db->put(db, &q, &d, 0);
00292 #endif /* DB_VERSION_CHECK(2,0,0) */
00293 
00294        pthread_mutex_unlock(&cache_lock);
00295 
00296        if (status == 0)
00297        {
00298               return 0;
00299        }
00300        else
00301        {
00302               *err = status;
00303               return -1;
00304        }
00305 }
00306 
00307 /*
00308 **  DKIM_CACHE_EXPIRE -- expire records in an on-disk cache of entries
00309 **
00310 **  Parameters:
00311 **     db -- DB handle referring to the cache
00312 **     ttl -- time-to-live; delete any record older than this; if 0, apply
00313 **            the TTL in the record
00314 **     err -- error code (returned)
00315 **
00316 **  Return value:
00317 **     -1 -- error; caller should check "err"
00318 **     otherwise -- count of deleted records
00319 */
00320 
00321 int
00322 dkim_cache_expire(DB *db, int ttl, int *err)
00323 {
00324 #if !DB_VERSION_CHECK(2,0,0)
00325        bool first = TRUE;
00326 #endif /* ! DB_VERSION_CHECK(2,0,0) */
00327        bool delete;
00328        int deleted = 0;
00329        int status;
00330        time_t now;
00331 #if DB_VERSION_CHECK(2,0,0)
00332        DBC *dbc;
00333 #endif /* DB_VERSION_CHECK(2,0,0) */
00334        DBT q;
00335        DBT d;
00336        char name[DKIM_MAXHOSTNAMELEN + 1];
00337        struct dkim_cache_entry ce;
00338 
00339        assert(db != NULL);
00340        assert(err != NULL);
00341 
00342        memset(&q, '\0', sizeof q);
00343        memset(&d, '\0', sizeof d);
00344 
00345        (void) time(&now);
00346 
00347        pthread_mutex_lock(&cache_lock);
00348 
00349 #if DB_VERSION_CHECK(2,0,0)
00350        status = db->cursor(db, NULL, &dbc, 0);
00351        if (status != 0)
00352        {
00353               *err = status;
00354               pthread_mutex_unlock(&cache_lock);
00355               return -1;
00356        }
00357 #endif /* DB_VERSION_CHECK(2,0,0) */
00358 
00359        for (;;)
00360        {
00361               memset(name, '\0', sizeof name);
00362               memset(&ce, '\0', sizeof ce);
00363 
00364 #if DB_VERSION_CHECK(3,0,0)
00365               q.data = name;
00366               q.flags = DB_DBT_USERMEM;
00367               q.ulen = sizeof name;
00368 #endif /* DB_VERSION_CHECK(3,0,0) */
00369 
00370 #if DB_VERSION_CHECK(3,0,0)
00371               d.data = (void *) &ce;
00372               d.flags = DB_DBT_USERMEM;
00373               d.ulen = sizeof ce;
00374 #endif /* DB_VERSION_CHECK(3,0,0) */
00375 
00376 #if DB_VERSION_CHECK(2,0,0)
00377               status = dbc->c_get(dbc, &q, &d, DB_NEXT);
00378               if (status == DB_NOTFOUND)
00379               {
00380                      break;
00381               }
00382               else if (status != 0)
00383               {
00384                      *err = status;
00385                      break;
00386               }
00387 #else /* DB_VERSION_CHECK(2,0,0) */
00388               status = db->seq(db, &q, &d, first ? R_FIRST : R_NEXT);
00389               if (status == DB_NOTFOUND)
00390               {
00391                      break;
00392               }
00393               else if (status != 0)
00394               {
00395                      *err = status;
00396                      break;
00397               }
00398 
00399               first = FALSE;
00400 
00401               memcpy(name, q.data, MIN(sizeof name, q.size));
00402               memcpy((void *) &ce, d.data, MIN(sizeof ce, d.size));
00403 #endif /* DB_VERSION_CHECK(2,0,0) */
00404 
00405               delete = FALSE;
00406               if (ttl == 0)
00407               {
00408                      if (ce.cache_when + ce.cache_ttl < now)
00409                             delete = TRUE;
00410               }
00411               else
00412               {
00413                      if (ce.cache_when + ttl < now)
00414                             delete = TRUE;
00415               }
00416 
00417               if (delete)
00418               {
00419 #if DB_VERSION_CHECK(2,0,0)
00420                      status = dbc->c_del(dbc, 0);
00421 #else /* DB_VERSION_CHECK(2,0,0) */
00422                      status = db->del(db, &q, R_CURSOR);
00423 #endif /* DB_VERSION_CHECK(2,0,0) */
00424                      if (status != 0)
00425                      {
00426                             *err = status;
00427                             deleted = -1;
00428                             break;
00429                      }
00430 
00431                      deleted++;
00432               }
00433        }
00434 
00435 #if DB_VERSION_CHECK(2,0,0)
00436        (void) dbc->c_close(dbc);
00437 #endif /* DB_VERSION_CHECK(2,0,0) */
00438 
00439        pthread_mutex_unlock(&cache_lock);
00440 
00441        return deleted;
00442 }
00443 
00444 /*
00445 **  DKIM_CACHE_CLOSE -- close a cache database
00446 **
00447 **  Parameters:
00448 **     db -- cache DB handle
00449 **
00450 **  Return value:
00451 **     None.
00452 */
00453 
00454 void
00455 dkim_cache_close(DB *db)
00456 {
00457        assert(db != NULL);
00458 
00459 #if DB_VERSION_CHECK(2,0,0)
00460        (void) db->close(db, 0);
00461 #else /* DB_VERSION_CHECK(2,0,0) */
00462        (void) db->close(db);
00463 #endif /* DB_VERSION_CHECK(2,0,0) */
00464 
00465        (void) pthread_mutex_destroy(&cache_lock);
00466 }
00467 
00468 /*
00469 **  DKIM_CACHE_STATS -- retrieve cache performance statistics
00470 **
00471 **  Parameters:
00472 **     queries -- number of queries handled (returned)
00473 **     hits -- number of cache hits (returned)
00474 **     expired -- number of expired hits (returned)
00475 **
00476 **  Return value:
00477 **     None.
00478 **
00479 **  Notes:
00480 **     Any of the parameters may be NULL if the corresponding datum
00481 **     is not of interest.
00482 */
00483 
00484 void
00485 dkim_cache_stats(u_int *queries, u_int *hits, u_int *expired)
00486 {
00487        pthread_mutex_lock(&cache_stats_lock);
00488 
00489        if (queries != NULL)
00490               *queries = c_queries;
00491 
00492        if (hits != NULL)
00493               *hits = c_hits;
00494 
00495        if (expired != NULL)
00496               *expired = c_expired;
00497 
00498        pthread_mutex_unlock(&cache_stats_lock);
00499 }
00500 
00501 #endif /* QUERY_CACHE */