Back to index

opendkim  2.6.2
reputation.c
Go to the documentation of this file.
00001 /*
00002 **  Copyright (c) 2007, 2008 Sendmail, Inc. and its suppliers.
00003 **     All rights reserved.
00004 **
00005 **  Copyright (c) 2011, 2012, The OpenDKIM Project.  All rights reserved.
00006 **
00007 **  $Id: reputation.c,v 1.27.2.1 2010/10/27 21:43:09 cm-msk Exp $
00008 */
00009 
00010 #ifndef lint
00011 static char reputation_c_id[] = "@(#)$Id: stats.c,v 1.27.2.1 2010/10/27 21:43:09 cm-msk Exp $";
00012 #endif /* !lint */
00013 
00014 #include "build-config.h"
00015 
00016 #ifdef _FFR_REPUTATION
00017 
00018 /* system includes */
00019 #include <sys/param.h>
00020 #include <sys/types.h>
00021 #include <string.h>
00022 #include <stdlib.h>
00023 #include <stdio.h>
00024 #include <math.h>
00025 #include <assert.h>
00026 
00027 /* libopendkim includes */
00028 #include <dkim.h>
00029 
00030 /* opendkim includes */
00031 #include "reputation.h"
00032 #include "opendkim.h"
00033 #include "opendkim-db.h"
00034 
00035 /* macros */
00036 #define DKIMF_REP_DEFCACHE  "db:"
00037 #define       DKIMF_REP_MAXHASHES  64
00038 #define       DKIMF_REP_NULLDOMAIN "UNSIGNED"
00039 #define       DKIMF_REP_LOWTIME    "LOW-TIME"
00040 
00041 /* data types */
00042 struct reputation
00043 {
00044        time_t        rep_ttl;
00045        time_t        rep_lastflush;
00046        unsigned int  rep_factor;
00047        unsigned int  rep_minimum;
00048        DKIMF_DB      rep_reps;
00049        DKIMF_DB      rep_dups;
00050        DKIMF_DB      rep_limits;
00051        DKIMF_DB      rep_limitmods;
00052        DKIMF_DB      rep_ratios;
00053        DKIMF_DB      rep_counts;
00054        DKIMF_DB      rep_spam;
00055        DKIMF_DB      rep_lowtime;
00056        pthread_mutex_t      rep_lock;
00057 };
00058 
00059 struct reps
00060 {
00061        time_t        reps_retrieved;
00062        unsigned long reps_count;
00063        unsigned long reps_limit;
00064        unsigned long reps_spam;
00065        float         reps_ratio;
00066 };
00067 
00068 /*
00069 **  DKIMF_REP_INIT -- initialize reputation
00070 **
00071 **  Parameters:
00072 **     rep -- reputation DB query handle (returned)
00073 **     factor -- number of slices in a reputation limit
00074 **     minimum -- always accept at least this many messages
00075 **     cachettl -- TTL for cache entries
00076 **     cache -- data set to which to cache
00077 **     dups -- data set to which to record duplicates
00078 **     limits -- DB from which to get per-domain limits
00079 **     limitmods -- DB from which to get per-domain limit modifiers
00080 **     ratios -- DB from which to get per-domain ratios
00081 **     lowtime -- DB from which to check for low-time domain status
00082 **
00083 **  Return value:
00084 **     0 on success, -1 on error.
00085 */
00086 
00087 int
00088 dkimf_rep_init(DKIMF_REP *rep, time_t factor, unsigned int minimum,
00089                unsigned int cachettl, char *cache, char *dups, DKIMF_DB limits,
00090                DKIMF_DB limitmods, DKIMF_DB ratios, DKIMF_DB lowtime)
00091 {
00092        int status;
00093        DKIMF_REP new;
00094 
00095        assert(rep != NULL);
00096        assert(ratios != NULL);
00097        assert(factor != 0);
00098 
00099        new = malloc(sizeof *new);
00100        if (new == NULL)
00101               return -1;
00102 
00103 #ifdef USE_MDB
00104        if (cache == NULL || dups == NULL)
00105               return -1;
00106 #else /* USE_MDB */
00107        if (cache == NULL)
00108               cache = DKIMF_REP_DEFCACHE;
00109        if (dups == NULL)
00110               dups = DKIMF_REP_DEFCACHE;
00111 #endif /* USE_MDB */
00112 
00113        new->rep_lastflush = time(NULL);
00114        new->rep_ttl = cachettl;
00115        new->rep_factor = factor;
00116        new->rep_limits = limits;
00117        new->rep_limitmods = limitmods;
00118        new->rep_ratios = ratios;
00119        new->rep_lowtime = lowtime;
00120        new->rep_minimum = minimum;
00121 
00122        if (pthread_mutex_init(&new->rep_lock, NULL) != 0)
00123        {
00124               free(new);
00125               return -1;
00126        }
00127 
00128        status = dkimf_db_open(&new->rep_reps, cache, 0, NULL, NULL);
00129        if (status != 0)
00130        {
00131               free(new);
00132               return -1;
00133        }
00134 
00135        status = dkimf_db_open(&new->rep_dups, dups, 0, NULL, NULL);
00136        if (status != 0)
00137        {
00138               dkimf_db_close(new->rep_reps);
00139               free(new);
00140               return -1;
00141        }
00142 
00143        *rep = new;
00144 
00145        return 0;
00146 }
00147 
00148 /*
00149 **  DKIMF_REP_CLOSE -- shut down reputation
00150 **
00151 **  Parameters:
00152 **     rephandle -- reputation DB query handle
00153 **
00154 **  Return value:
00155 **     None.
00156 */
00157 
00158 void
00159 dkimf_rep_close(DKIMF_REP rephandle)
00160 {
00161        assert(rephandle != NULL);
00162 
00163        (void) dkimf_db_close(rephandle->rep_reps);
00164        (void) dkimf_db_close(rephandle->rep_dups);
00165 
00166        (void) pthread_mutex_destroy(&rephandle->rep_lock);
00167 }
00168 
00169 /*
00170 **  DKIMF_REP_CHECK -- check reputation
00171 **
00172 **  Parameters:
00173 **     rep -- reputation service handle
00174 **     sig -- a valid signature on this message
00175 **     spam -- spammy or not spammy?  That is the question.
00176 **     hash -- hash of the message, for counting dups
00177 **     hashlen -- number of bytes in the hash
00178 **     limit -- limit for this signer (returned)
00179 **     ratio -- spam ratio for this signer (returned)
00180 **     count -- message count for this signer (returned)
00181 **     spamcnt -- spam count for this signer (returned)
00182 **     errbuf -- buffer to receive errors
00183 **     errlen -- bytes available at errbuf
00184 **
00185 **  Return value:
00186 **     2 -- no data found for this domain
00187 **     1 -- deny the request
00188 **     0 -- allow the request
00189 **     -1 -- error
00190 **
00191 **  Notes:
00192 **     If "sig" is NULL, the null domain record is queried.
00193 */
00194 
00195 int
00196 dkimf_rep_check(DKIMF_REP rep, DKIM_SIGINFO *sig, _Bool spam,
00197                 void *hash, size_t hashlen, unsigned long *limit,
00198                 float *ratio, unsigned long *count, unsigned long *spamcnt,
00199                 char *errbuf, size_t errlen)
00200 {
00201        _Bool f;
00202        size_t dlen;
00203        size_t hlen;
00204        time_t when;
00205        time_t now;
00206        void *hh;
00207        void *bh;
00208        struct dkimf_db_data req[5];
00209        struct reps reps;
00210        char buf[BUFRSZ + 1];
00211        char domain[DKIM_MAXHOSTNAMELEN + 1];
00212        unsigned char hashbuf[DKIMF_REP_MAXHASHES];
00213 
00214        assert(rep != NULL);
00215 
00216        (void) time(&now);
00217 
00218        pthread_mutex_lock(&rep->rep_lock);
00219 
00220        /* flush the caches if needed */
00221        if (rep->rep_lastflush + rep->rep_ttl < now)
00222        {
00223               f = TRUE;
00224               
00225               req[0].dbdata_buffer = (void *) &when;
00226               req[0].dbdata_buflen = sizeof when;
00227               req[0].dbdata_flags = DKIMF_DB_DATA_BINARY;
00228 
00229               hlen = sizeof hashbuf;
00230               while (dkimf_db_walk(rep->rep_dups, f, hashbuf, &hlen,
00231                                    req, 1) == 0)
00232               {
00233                      if (when + rep->rep_ttl < now)
00234                      {
00235                             (void) dkimf_db_delete(rep->rep_dups, hashbuf,
00236                                                    hlen);
00237                      }
00238 
00239                      req[0].dbdata_buffer = (void *) &when;
00240                      req[0].dbdata_buflen = sizeof when;
00241                      req[0].dbdata_flags = DKIMF_DB_DATA_BINARY;
00242 
00243                      f = FALSE;
00244                      hlen = sizeof hashbuf;
00245               }
00246 
00247               req[0].dbdata_buffer = (void *) &reps;
00248               req[0].dbdata_buflen = sizeof reps;
00249               req[0].dbdata_flags = DKIMF_DB_DATA_BINARY;
00250 
00251               f = TRUE;
00252               hlen = sizeof domain;
00253               memset(domain, '\0', sizeof domain);
00254               while (dkimf_db_walk(rep->rep_reps, f, domain, &hlen,
00255                                    req, 1) == 0)
00256               {
00257                      if (reps.reps_retrieved + rep->rep_ttl < now)
00258                      {
00259                             (void) dkimf_db_delete(rep->rep_reps, domain,
00260                                                    hlen);
00261                      }
00262 
00263                      req[0].dbdata_buffer = (void *) &reps;
00264                      req[0].dbdata_buflen = sizeof reps;
00265                      req[0].dbdata_flags = DKIMF_DB_DATA_BINARY;
00266 
00267                      f = FALSE;
00268                      hlen = sizeof domain;
00269                      memset(domain, '\0', sizeof domain);
00270               }
00271 
00272               rep->rep_lastflush = now;
00273        }
00274 
00275        /* get the ratio and limit for this domain */
00276        req[0].dbdata_buffer = (void *) &reps;
00277        req[0].dbdata_buflen = sizeof reps;
00278        req[0].dbdata_flags = DKIMF_DB_DATA_BINARY;
00279 
00280        if (sig == NULL)
00281               dkim_strlcpy(domain, DKIMF_REP_NULLDOMAIN, sizeof domain);
00282        else
00283               dkim_strlcpy(domain, dkim_sig_getdomain(sig), sizeof domain);
00284 
00285        dlen = strlen(domain);
00286 
00287        /* check cache first */
00288        f = FALSE;
00289        if (dkimf_db_get(rep->rep_reps, domain, dlen, req, 1, &f) != 0)
00290        {
00291               if (errbuf != NULL)
00292                      dkimf_db_strerror(rep->rep_reps, errbuf, errlen);
00293               pthread_mutex_unlock(&rep->rep_lock);
00294               return -1;
00295        }
00296 
00297        if (!f)
00298        {
00299               _Bool lowtime = FALSE;
00300               char *p = NULL;
00301 
00302               /* cache miss; build a new cache entry */
00303               reps.reps_count = 0;
00304               reps.reps_limit = ULONG_MAX;
00305               reps.reps_spam = 0;
00306               reps.reps_retrieved = time(NULL);
00307 
00308               req[0].dbdata_buffer = buf;
00309               req[0].dbdata_buflen = sizeof buf;
00310               req[0].dbdata_flags = 0;
00311 
00312               if (rep->rep_lowtime != NULL)
00313               {
00314                      /* see if it's a low-time domain */
00315                      if (dkimf_db_get(rep->rep_lowtime, domain, dlen, req,
00316                                       1, &f) != 0)
00317                      {
00318                             if (errbuf != NULL)
00319                             {
00320                                    dkimf_db_strerror(rep->rep_reps,
00321                                                      errbuf, errlen);
00322                             }
00323                             pthread_mutex_unlock(&rep->rep_lock);
00324                             return -1;
00325                      }
00326 
00327                      if (f)
00328                             lowtime = (atoi(buf) != 0);
00329 
00330                      memset(buf, '\0', sizeof buf);
00331 
00332                      req[0].dbdata_buffer = buf;
00333                      req[0].dbdata_buflen = sizeof buf;
00334                      req[0].dbdata_flags = 0;
00335               }
00336 
00337               if (lowtime)
00338               {
00339                      dkim_strlcpy(domain, DKIMF_REP_LOWTIME, sizeof domain);
00340                      dlen = strlen(domain);
00341               }
00342               
00343               f = FALSE;
00344 
00345               /* get the total message limit */
00346               if (rep->rep_limits != NULL)
00347               {
00348                      int fields = 1;
00349 
00350                      if (dkimf_db_type(rep->rep_limits) == DKIMF_DB_TYPE_REPUTE)
00351                             fields = 5;
00352 
00353                      memset(req, '\0', sizeof req);
00354 
00355                      req[fields - 1].dbdata_buffer = buf;
00356                      req[fields - 1].dbdata_buflen = sizeof buf;
00357                      req[fields - 1].dbdata_flags = 0;
00358 
00359                      if (dkimf_db_get(rep->rep_limits, domain, dlen, req,
00360                                       fields, &f) != 0)
00361                      {
00362                             if (errbuf != NULL)
00363                             {
00364                                    dkimf_db_strerror(rep->rep_reps,
00365                                                      errbuf, errlen);
00366                             }
00367                             pthread_mutex_unlock(&rep->rep_lock);
00368                             return -1;
00369                      }
00370 
00371                      if (!f && !lowtime && sig != NULL)
00372                      {
00373                             if (dkimf_db_get(rep->rep_limits,
00374                                              DKIMF_REP_LOWTIME,
00375                                              strlen(DKIMF_REP_LOWTIME),
00376                                              req, fields, &f) != 0)
00377                             {
00378                                    if (errbuf != NULL)
00379                                    {
00380                                           dkimf_db_strerror(rep->rep_reps,
00381                                                             errbuf,
00382                                                             errlen);
00383                                    }
00384                                    pthread_mutex_unlock(&rep->rep_lock);
00385                                    return -1;
00386                             }
00387                      }
00388 
00389                      if (!f)
00390                      {
00391                             if (dkimf_db_get(rep->rep_limits, "*", 1, req,
00392                                              fields, &f) != 0)
00393                             {
00394                                    if (errbuf != NULL)
00395                                    {
00396                                           dkimf_db_strerror(rep->rep_reps,
00397                                                             errbuf,
00398                                                             errlen);
00399                                    }
00400                                    pthread_mutex_unlock(&rep->rep_lock);
00401                                    return -1;
00402                             }
00403                      }
00404 
00405                      if (!f || req[fields - 1].dbdata_buflen >= sizeof buf)
00406                      {
00407                             pthread_mutex_unlock(&rep->rep_lock);
00408                             return 2;
00409                      }
00410 
00411                      buf[req[fields - 1].dbdata_buflen] = '\0';
00412 
00413                      reps.reps_limit = (unsigned long) (ceil((double) strtoul(buf, &p, 10) / (double) rep->rep_factor) + 1.);
00414                      if (p != NULL && *p != '\0')
00415                      {
00416                             if (errbuf != NULL)
00417                             {
00418                                    snprintf(errbuf, errlen,
00419                                             "failed to parse limit reply");
00420                             }
00421                             pthread_mutex_unlock(&rep->rep_lock);
00422                             return -1;
00423                      }
00424 
00425                      if (rep->rep_limitmods != NULL)
00426                      {
00427                             f = FALSE;
00428 
00429                             req[0].dbdata_buffer = buf;
00430                             req[0].dbdata_buflen = sizeof buf;
00431                             req[0].dbdata_flags = 0;
00432 
00433                             if (dkimf_db_get(rep->rep_limitmods,
00434                                              domain, dlen,
00435                                              req, 1, &f) != 0)
00436                             {
00437                                    if (errbuf != NULL)
00438                                    {
00439                                           dkimf_db_strerror(rep->rep_reps,
00440                                                             errbuf,
00441                                                             errlen);
00442                                    }
00443                                    pthread_mutex_unlock(&rep->rep_lock);
00444                                    return -1;
00445                             }
00446 
00447                             if (f && req[0].dbdata_buflen < sizeof buf)
00448                             {
00449                                    unsigned int mod = 0;
00450 
00451                                    buf[req[0].dbdata_buflen] = '\0';
00452                                    mod = strtoul(&buf[1], &p, 10);
00453                                    if (*p != '\0')
00454                                           buf[0] = '\0';
00455 
00456                                    switch (buf[0])
00457                                    {
00458                                      case '+':
00459                                           reps.reps_limit += mod;
00460                                           break;
00461 
00462                                      case '*':
00463                                           reps.reps_limit *= mod;
00464                                           break;
00465 
00466                                      case '-':
00467                                           reps.reps_limit -= mod;
00468                                           break;
00469 
00470                                      case '/':
00471                                           if (mod != 0)
00472                                                  reps.reps_limit /= mod;
00473                                           break;
00474 
00475                                      case '=':
00476                                           reps.reps_limit = mod;
00477                                           break;
00478                                    }
00479                             }
00480                      }
00481               }
00482 
00483               /* get the spam ratio */
00484               req[0].dbdata_buffer = buf;
00485               req[0].dbdata_buflen = sizeof buf;
00486               req[0].dbdata_flags = 0;
00487 
00488               f = FALSE;
00489 
00490               if (dkimf_db_get(rep->rep_ratios, domain, dlen, req,
00491                                1, &f) != 0)
00492               {
00493                      if (errbuf != NULL)
00494                      {
00495                             dkimf_db_strerror(rep->rep_reps,
00496                                               errbuf, errlen);
00497                      }
00498                      pthread_mutex_unlock(&rep->rep_lock);
00499                      return -1;
00500               }
00501 
00502               if (!f && !lowtime && sig != NULL)
00503               {
00504                      if (dkimf_db_get(rep->rep_ratios,
00505                                       DKIMF_REP_LOWTIME,
00506                                       strlen(DKIMF_REP_LOWTIME),
00507                                       req, 1, &f) != 0)
00508                      {
00509                             if (errbuf != NULL)
00510                             {
00511                                    dkimf_db_strerror(rep->rep_reps,
00512                                                      errbuf, errlen);
00513                             }
00514                             pthread_mutex_unlock(&rep->rep_lock);
00515                             return -1;
00516                      }
00517               }
00518 
00519               if (!f)
00520               {
00521                      if (dkimf_db_get(rep->rep_ratios, "*", 1, req,
00522                                       1, &f) != 0)
00523                      {
00524                             if (errbuf != NULL)
00525                             {
00526                                    dkimf_db_strerror(rep->rep_reps,
00527                                                      errbuf, errlen);
00528                             }
00529                             pthread_mutex_unlock(&rep->rep_lock);
00530                             return -1;
00531                      }
00532               }
00533 
00534               if (!f || req[0].dbdata_buflen >= sizeof buf)
00535               {
00536                      pthread_mutex_unlock(&rep->rep_lock);
00537                      return 2;
00538               }
00539 
00540               buf[req[0].dbdata_buflen] = '\0';
00541               p = NULL;
00542               reps.reps_ratio = strtof(buf, &p);
00543               if (p != NULL && *p != '\0')
00544               {
00545                      if (errbuf != NULL)
00546                      {
00547                             snprintf(errbuf, errlen,
00548                                      "failed to parse ratio reply");
00549                      }
00550                      pthread_mutex_unlock(&rep->rep_lock);
00551                      return -1;
00552               }
00553        }
00554 
00555        req[0].dbdata_buffer = (void *) &when;
00556        req[0].dbdata_buflen = sizeof when;
00557        req[0].dbdata_flags = DKIMF_DB_DATA_BINARY;
00558 
00559        f = FALSE;
00560 
00561        if (dkimf_db_get(rep->rep_dups, hash, hashlen, req, 1, &f) != 0)
00562        {
00563               if (errbuf != NULL)
00564                      dkimf_db_strerror(rep->rep_reps, errbuf, errlen);
00565               pthread_mutex_unlock(&rep->rep_lock);
00566               return -1;
00567        }
00568 
00569        /* up the counts if this is new */
00570        if (!f)
00571        {
00572               reps.reps_count++;
00573               if (spam)
00574                      reps.reps_spam++;
00575 
00576               /* write it to the cache */
00577               if (dkimf_db_put(rep->rep_reps, domain, dlen,
00578                                &reps, sizeof reps) != 0)
00579               {
00580                      dkimf_db_strerror(rep->rep_reps, errbuf, errlen);
00581                      pthread_mutex_unlock(&rep->rep_lock);
00582                      return -1;
00583               }
00584        }
00585 
00586        /* export requested stats */
00587        if (limit != NULL)
00588               *limit = reps.reps_limit;
00589        if (ratio != NULL)
00590               *ratio = reps.reps_ratio;
00591        if (count != NULL)
00592               *count = reps.reps_count;
00593        if (spamcnt != NULL)
00594               *spamcnt = reps.reps_spam;
00595 
00596        /* if accepting it now would be within limits */
00597        if (reps.reps_count <= rep->rep_minimum ||
00598            (reps.reps_count <= reps.reps_limit &&
00599             (float) reps.reps_spam / (float) reps.reps_count <= reps.reps_ratio))
00600        {
00601               /* remove from rep_dups if found there */
00602               (void) dkimf_db_delete(rep->rep_dups, hash, hashlen);
00603 
00604               pthread_mutex_unlock(&rep->rep_lock);
00605               return 0;
00606        }
00607        else
00608        {
00609               /* record the dup */
00610               (void) dkimf_db_put(rep->rep_dups, hash, hashlen,
00611                                   &now, sizeof now);
00612 
00613               pthread_mutex_unlock(&rep->rep_lock);
00614               return 1;
00615        }
00616 }
00617 
00618 /*
00619 **  DKIMF_REP_CHOWN_CACHE -- set the owner of a cache file
00620 **
00621 **  Parameters:
00622 **     rep -- reputation handle
00623 **     uid -- target UID
00624 **
00625 **  Return value:
00626 **     0 -- success
00627 **     -1 -- failure
00628 */
00629 
00630 int
00631 dkimf_rep_chown_cache(DKIMF_REP rep, uid_t uid)
00632 {
00633        assert(rep != NULL);
00634        assert(uid >= 0);
00635 
00636        if (dkimf_db_chown(rep->rep_reps, uid) == 1)
00637               return 0;
00638        else
00639               return -1;
00640 }
00641 #endif /* _FFR_REPUTATION */