Back to index

opendkim  2.6.2
dkim-policy.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-2011, The OpenDKIM Project.  All rights reserved.
00006 */
00007 
00008 #ifndef lint
00009 static char dkim_policy_c_id[] = "@(#)$Id: dkim-policy.c,v 1.13.10.1 2010/10/27 21:43:08 cm-msk Exp $";
00010 #endif /* !lint */
00011 
00012 #include "build-config.h"
00013 
00014 /* system includes */
00015 #include <sys/param.h>
00016 #include <sys/types.h>
00017 #include <netinet/in.h>
00018 #include <arpa/inet.h>
00019 #include <arpa/nameser.h>
00020 #ifdef HAVE_STDBOOL_H
00021 # include <stdbool.h>
00022 #endif /* HAVE_STDBOOL_H */
00023 #include <netdb.h>
00024 #include <resolv.h>
00025 #include <string.h>
00026 #include <stdio.h>
00027 #include <errno.h>
00028 #include <assert.h>
00029 #include <ctype.h>
00030 
00031 /* libopendkim includes */
00032 #include "dkim-internal.h"
00033 #include "dkim-types.h"
00034 #include "dkim-policy.h"
00035 #ifdef QUERY_CACHE
00036 # include "dkim-cache.h"
00037 #endif /* QUERY_CACHE */
00038 #include "dkim-test.h"
00039 #include "util.h"
00040 #include "dkim-strl.h"
00041 
00042 /* prototypes */
00043 extern void dkim_error __P((DKIM *, const char *, ...));
00044 
00045 /* local definitions needed for DNS queries */
00046 #define MAXPACKET           8192
00047 #if defined(__RES) && (__RES >= 19940415)
00048 # define RES_UNC_T          char *
00049 #else /* __RES && __RES >= 19940415 */
00050 # define RES_UNC_T          unsigned char *
00051 #endif /* __RES && __RES >= 19940415 */
00052 
00053 #ifndef T_AAAA
00054 # define T_AAAA                    28
00055 #endif /* ! T_AAAA */
00056 #ifndef T_RRSIG
00057 # define T_RRSIG            46
00058 #endif /* ! T_RRSIG */
00059 
00060 /*
00061 **  DKIM_GET_POLICY_FILE -- acquire a domain's policy record using a local file
00062 **
00063 **  Parameters:
00064 **     dkim -- DKIM handle
00065 **     query -- query to execute
00066 **     buf -- buffer into which to write policy
00067 **     buflen -- number of bytes available at "buf"
00068 **     qstatus -- query result code (DNS-style)
00069 **
00070 **  Return value:
00071 **     1 -- query completed, answer returned in "buf"
00072 **     0 -- query completed, no answer available
00073 **     -1 -- failure
00074 */
00075 
00076 int
00077 dkim_get_policy_file(DKIM *dkim, unsigned char *query, unsigned char *buf,
00078                      size_t buflen, int *qstatus)
00079 {
00080        _Bool found;
00081        int n;
00082        u_char *path;
00083        unsigned char *p;
00084        FILE *f;
00085        unsigned char inbuf[BUFRSZ + 1];
00086 
00087        assert(dkim != NULL);
00088        assert(query != NULL);
00089        assert(buf != NULL);
00090        assert(qstatus != NULL);
00091 
00092        path = dkim->dkim_libhandle->dkiml_queryinfo;
00093 
00094        f = fopen((char *) path, "r");
00095        if (f == NULL)
00096        {
00097               dkim_error(dkim, "%s: fopen(): %s", path,
00098                          strerror(errno));
00099               return -1;
00100        }
00101 
00102        n = strlen((char *) query);
00103 
00104        memset(inbuf, '\0', sizeof inbuf);
00105 
00106        found = FALSE;
00107        while (!found && fgets((char *) inbuf, sizeof inbuf - 1, f) != NULL)
00108        {
00109               for (p = inbuf; *p != '\0'; p++)
00110               {
00111                      if (*p == '\n' || *p == '#')
00112                      {
00113                             *p = '\0';
00114                             break;
00115                      }
00116               }
00117 
00118               /* is this a match? */
00119               if (strncasecmp((char *) inbuf, (char *) query, n) == 0 &&
00120                   isascii(inbuf[n]) && isspace(inbuf[n]))
00121               {
00122                      found = TRUE;
00123 
00124                      /* move past spaces */
00125                      for (p = &inbuf[n] + 1;
00126                           isascii(*p) && isspace(*p);
00127                           p++)
00128                             continue;
00129 
00130                      strlcpy((char *) buf, (char *) p, buflen);
00131 
00132                      *qstatus = NOERROR;
00133 
00134                      fclose(f);
00135 
00136                      return 1;
00137               }
00138        }
00139 
00140        if (ferror(f))
00141        {
00142               dkim_error(dkim, "%s: fgets(): %s", path, strerror(errno));
00143               fclose(f);
00144               return -1;
00145        }
00146 
00147        fclose(f);
00148 
00149        *qstatus = NXDOMAIN;
00150 
00151        return 0;
00152 }
00153 
00154 /*
00155 **  DKIM_GET_POLICY_DNS_EXCHECK -- existence check for a name
00156 **
00157 **  Parameters:
00158 **     dkim -- DKIM handle
00159 **     query -- query to execute
00160 **     qstatus -- query result code (DNS-style)
00161 **
00162 **  Return value:
00163 **     1 -- domain exists
00164 **     0 -- domain does not exist
00165 **     -1 -- failure
00166 */
00167 
00168 int
00169 dkim_get_policy_dns_excheck(DKIM *dkim, unsigned char *query, int *qstatus)
00170 {
00171        size_t anslen_a;
00172        size_t anslen_aaaa;
00173        size_t anslen_mx;
00174        int status;
00175        DKIM_LIB *lib;
00176        HEADER hdr;
00177        void *q_a;
00178        void *q_aaaa;
00179        void *q_mx;
00180        struct timeval timeout;
00181        unsigned char ansbuf_a[MAXPACKET];
00182        unsigned char ansbuf_aaaa[MAXPACKET];
00183        unsigned char ansbuf_mx[MAXPACKET];
00184 
00185        assert(dkim != NULL);
00186        assert(query != NULL);
00187        assert(qstatus != NULL);
00188 
00189        lib = dkim->dkim_libhandle;
00190 
00191        if (lib->dkiml_querymethod == DKIM_QUERY_FILE)
00192        {
00193               return dkim_get_policy_file(dkim, query, ansbuf_a,
00194                                           sizeof ansbuf_a, qstatus);
00195        }
00196 
00197        timeout.tv_sec = dkim->dkim_timeout;
00198        timeout.tv_usec = 0;
00199 
00200        anslen_a = sizeof ansbuf_a;
00201        status = lib->dkiml_dns_start(lib->dkiml_dns_service, T_A, query,
00202                                      ansbuf_a, anslen_a, &q_a);
00203 
00204        if (status != 0 || q_a == NULL)
00205        {
00206               dkim_error(dkim, "A query failed for '%s'", query);
00207               return -1;
00208        }
00209 
00210        anslen_aaaa = sizeof ansbuf_aaaa;
00211        status = lib->dkiml_dns_start(lib->dkiml_dns_service, T_AAAA, query,
00212                                      ansbuf_aaaa, anslen_aaaa, &q_aaaa);
00213        if (status != 0 || q_aaaa == NULL)
00214        {
00215               (void) lib->dkiml_dns_cancel(lib->dkiml_dns_service, q_a);
00216               dkim_error(dkim, "AAAA query failed for '%s'", query);
00217               return -1;
00218        }
00219 
00220        anslen_mx = sizeof ansbuf_mx;
00221        status = lib->dkiml_dns_start(lib->dkiml_dns_service, T_MX, query,
00222                                      ansbuf_mx, anslen_mx, &q_mx);
00223        if (status != 0 || q_mx == NULL)
00224        {
00225               (void) lib->dkiml_dns_cancel(lib->dkiml_dns_service, q_a);
00226               (void) lib->dkiml_dns_cancel(lib->dkiml_dns_service, q_aaaa);
00227               dkim_error(dkim, "MX query failed for '%s'", query);
00228               return -1;
00229        }
00230 
00231        if (lib->dkiml_dns_callback == NULL)
00232        {
00233               timeout.tv_sec = dkim->dkim_timeout;
00234               timeout.tv_usec = 0;
00235 
00236               status = lib->dkiml_dns_waitreply(lib->dkiml_dns_service,
00237                                                 q_a,
00238                                                  dkim->dkim_timeout == 0 ? NULL
00239                                                                          : &timeout,
00240                                                 &anslen_a, NULL, NULL);
00241 
00242               timeout.tv_sec = dkim->dkim_timeout;
00243               timeout.tv_usec = 0;
00244 
00245               status = lib->dkiml_dns_waitreply(lib->dkiml_dns_service,
00246                                                 q_aaaa,
00247                                                  dkim->dkim_timeout == 0 ? NULL
00248                                                                          : &timeout,
00249                                                 &anslen_aaaa, NULL, NULL);
00250 
00251               timeout.tv_sec = dkim->dkim_timeout;
00252               timeout.tv_usec = 0;
00253 
00254               status = lib->dkiml_dns_waitreply(lib->dkiml_dns_service,
00255                                                 q_mx,
00256                                                  dkim->dkim_timeout == 0 ? NULL
00257                                                                          : &timeout,
00258                                                 &anslen_mx, NULL, NULL);
00259        }
00260        else
00261        {
00262               int which = 0;
00263               struct timeval master;
00264               struct timeval next;
00265               struct timeval *wt;
00266 
00267               (void) gettimeofday(&master, NULL);
00268               master.tv_sec += dkim->dkim_timeout;
00269  
00270               while (which <= 2)
00271               {
00272                      (void) gettimeofday(&next, NULL);
00273                      next.tv_sec += lib->dkiml_callback_int;
00274 
00275                      dkim_min_timeval(&master, &next, &timeout, &wt);
00276 
00277                      switch (which)
00278                      {
00279                        case 0:
00280                             status = lib->dkiml_dns_waitreply(lib->dkiml_dns_service,
00281                                                               q_a,
00282                                                                &timeout,
00283                                                               &anslen_a,
00284                                                               NULL, NULL);
00285 
00286                             break;
00287 
00288                        case 1:
00289                             status = lib->dkiml_dns_waitreply(lib->dkiml_dns_service,
00290                                                               q_aaaa,
00291                                                                &timeout,
00292                                                               &anslen_aaaa,
00293                                                               NULL, NULL);
00294 
00295                             break;
00296 
00297                        case 2:
00298                             status = lib->dkiml_dns_waitreply(lib->dkiml_dns_service,
00299                                                               q_mx,
00300                                                                &timeout,
00301                                                               &anslen_mx,
00302                                                               NULL, NULL);
00303 
00304                             break;
00305                      }
00306 
00307                      if (wt == &next &&
00308                          (status == DKIM_DNS_NOREPLY ||
00309                           status == DKIM_DNS_EXPIRED))
00310                      {
00311                             lib->dkiml_dns_callback(dkim->dkim_user_context);
00312                      }
00313                      else
00314                      {
00315                             if (which == 2)
00316                             {
00317                                    break;
00318                             }
00319                             else
00320                             {
00321                                    which++;
00322 
00323                                    (void) gettimeofday(&master, NULL);
00324                                    master.tv_sec += dkim->dkim_timeout;
00325  
00326                                    continue;
00327                             }
00328                      }
00329               }
00330        }
00331 
00332        (void) lib->dkiml_dns_cancel(lib->dkiml_dns_service, q_a);
00333        (void) lib->dkiml_dns_cancel(lib->dkiml_dns_service, q_aaaa);
00334        (void) lib->dkiml_dns_cancel(lib->dkiml_dns_service, q_mx);
00335 
00336        /* check each for NXDOMAIN or some other issue */
00337        memcpy(&hdr, ansbuf_a, sizeof hdr);
00338        *qstatus = hdr.rcode;
00339        if (hdr.rcode == NOERROR && ntohs(hdr.ancount) != 0)
00340               return 1;
00341 
00342        memcpy(&hdr, ansbuf_aaaa, sizeof hdr);
00343        *qstatus = hdr.rcode;
00344        if (hdr.rcode == NOERROR && ntohs(hdr.ancount) != 0)
00345               return 1;
00346 
00347        memcpy(&hdr, ansbuf_mx, sizeof hdr);
00348        *qstatus = hdr.rcode;
00349        if (hdr.rcode == NOERROR && ntohs(hdr.ancount) != 0)
00350               return 1;
00351 
00352        /* effectively NXDOMAIN */
00353        return 0;
00354 }
00355 
00356 /*
00357 **  DKIM_GET_POLICY_DNS -- acquire a domain's policy record using DNS queries
00358 **
00359 **  Parameters:
00360 **     dkim -- DKIM handle
00361 **     query -- query to execute
00362 **     excheck -- existence check?
00363 **     buf -- buffer into which to write policy
00364 **     buflen -- number of bytes available at "buf"
00365 **     qstatus -- query result code (DNS-style)
00366 **
00367 **  Return value:
00368 **     1 -- policy retrieved, stored in buffer
00369 **     0 -- no policy found
00370 **     -1 -- failure
00371 */
00372 
00373 int
00374 dkim_get_policy_dns(DKIM *dkim, unsigned char *query, _Bool excheck,
00375                     unsigned char *buf, size_t buflen, int *qstatus)
00376 {
00377        int qdcount;
00378        int ancount;
00379        int status;
00380        int rdlength;
00381        int n;
00382        int c;
00383        int type = -1;
00384        int class = -1;
00385 #ifdef QUERY_CACHE
00386        uint32_t ttl;
00387 #endif /* QUERY_CACHE */
00388        size_t anslen;
00389        void *q;
00390        DKIM_LIB *lib;
00391        unsigned char *p;
00392        unsigned char *cp;
00393        unsigned char *eom;
00394        unsigned char *txtfound = NULL;
00395        unsigned char ansbuf[MAXPACKET];
00396        unsigned char namebuf[DKIM_MAXHOSTNAMELEN + 1];
00397        unsigned char outbuf[BUFRSZ + 1];
00398        struct timeval timeout;
00399        HEADER hdr;
00400 
00401        assert(dkim != NULL);
00402        assert(query != NULL);
00403        assert(buf != NULL);
00404        assert(qstatus != NULL);
00405 
00406        lib = dkim->dkim_libhandle;
00407 
00408 #ifdef QUERY_CACHE
00409        if (lib->dkiml_cache != NULL)
00410        {
00411               int err = 0;
00412               size_t blen = buflen;
00413 
00414               dkim->dkim_cache_queries++;
00415 
00416               status = dkim_cache_query(lib->dkiml_cache, query, 0,
00417                                         buf, &blen, &err);
00418 
00419               if (status == 0)
00420               {
00421                      dkim->dkim_cache_hits++;
00422                      return (status == DKIM_STAT_OK ? 0 : -1);
00423               }
00424               /* XXX -- do something with errors here */
00425        }
00426 #endif /* QUERY_CACHE */
00427 
00428        /* see if there's a simulated reply queued; if so, use it */
00429        anslen = dkim_test_dns_get(dkim, ansbuf, sizeof ansbuf);
00430        if (anslen == -1)
00431        {
00432               if (excheck)
00433               {
00434                      return dkim_get_policy_dns_excheck(dkim, query,
00435                                                         qstatus);
00436               }
00437 
00438               timeout.tv_sec = dkim->dkim_timeout;
00439               timeout.tv_usec = 0;
00440 
00441               anslen = sizeof ansbuf;
00442 
00443               status = lib->dkiml_dns_start(lib->dkiml_dns_service,
00444                                             T_TXT, query,
00445                                             ansbuf, anslen, &q);
00446               if (status != 0 || q == NULL)
00447               {
00448                      dkim_error(dkim, "query failed for '%s'", query);
00449                      return -1;
00450               }
00451 
00452               if (lib->dkiml_dns_callback == NULL)
00453               {
00454                      status = lib->dkiml_dns_waitreply(lib->dkiml_dns_service,
00455                                                        q, NULL, &anslen,
00456                                                        NULL,
00457                                                        &dkim->dkim_dnssec_policy);
00458               }
00459               else
00460               {
00461                      struct timeval master;
00462                      struct timeval next;
00463                      struct timeval *wt;
00464 
00465                      (void) gettimeofday(&master, NULL);
00466                      master.tv_sec += dkim->dkim_timeout;
00467 
00468                      for (;;)
00469                      {
00470                             (void) gettimeofday(&next, NULL);
00471                             next.tv_sec += lib->dkiml_callback_int;
00472 
00473                             dkim_min_timeval(&master, &next,
00474                                              &timeout, &wt);
00475 
00476                             status = lib->dkiml_dns_waitreply(lib->dkiml_dns_service,
00477                                                               q,
00478                                                               &timeout,
00479                                                               &anslen,
00480                                                               NULL,
00481                                                               &dkim->dkim_dnssec_policy);
00482 
00483                             if (wt == &next)
00484                             {
00485                                    if (status == DKIM_DNS_NOREPLY ||
00486                                        status == DKIM_DNS_EXPIRED)
00487                                           lib->dkiml_dns_callback(dkim->dkim_user_context);
00488                                    else
00489                                           break;
00490 
00491                             }
00492                             else
00493                             {
00494                                    break;
00495                             }
00496                      }
00497               }
00498 
00499               (void) lib->dkiml_dns_cancel(lib->dkiml_dns_service, q);
00500 
00501               if (status == DKIM_DNS_ERROR || status == DKIM_DNS_EXPIRED)
00502               {
00503                      dkim_error(dkim, "'%s' query %s", query,
00504                                 status == DKIM_DNS_ERROR ? "error"
00505                                                          : "expired");
00506 
00507                      if (status == DKIM_DNS_EXPIRED)
00508                      {
00509                             *qstatus = SERVFAIL;
00510                             return 0;
00511                      }
00512                      else
00513                      {
00514                             return -1;
00515                      }
00516               }
00517        }
00518 
00519        /* set up pointers */
00520        memcpy(&hdr, ansbuf, sizeof hdr);
00521        cp = (u_char *) &ansbuf + HFIXEDSZ;
00522        eom = (u_char *) &ansbuf + anslen;
00523 
00524        /* skip over the name at the front of the answer */
00525        for (qdcount = ntohs((unsigned short) hdr.qdcount);
00526             qdcount > 0;
00527             qdcount--)
00528        {
00529               /* copy it first */
00530               (void) dn_expand((unsigned char *) &ansbuf, eom, cp,
00531                                (char *) namebuf, sizeof namebuf);
00532 
00533               if ((n = dn_skipname(cp, eom)) < 0)
00534               {
00535                      dkim_error(dkim, "'%s' reply corrupt", query);
00536                      return -1;
00537               }
00538 
00539               cp += n;
00540 
00541               /* extract the type and class */
00542               if (cp + INT16SZ + INT16SZ > eom)
00543               {
00544                      dkim_error(dkim, "'%s' reply corrupt", query);
00545                      return -1;
00546               }
00547 
00548               GETSHORT(type, cp);
00549               GETSHORT(class, cp);
00550        }
00551 
00552        if (type != T_TXT || class != C_IN)
00553        {
00554               dkim_error(dkim, "'%s' unexpected reply class/type (%d/%d)",
00555                          query, class, type);
00556               return -1;
00557        }
00558 
00559        /* if truncated, we can't do it */
00560        if (dkim_check_dns_reply(ansbuf, anslen, C_IN, T_TXT) == 1)
00561        {
00562               dkim_error(dkim, "reply for '%s' truncated", query);
00563               return -1;
00564        }
00565 
00566        /* if we got something other than NOERROR, just return it */
00567        *qstatus = hdr.rcode;
00568        if (hdr.rcode != NOERROR)
00569               return 0;
00570 
00571        /* get the answer count */
00572        ancount = ntohs((unsigned short) hdr.ancount);
00573        if (ancount == 0)
00574               return 0;
00575 
00576        /* walk through the answers looking for the right record */
00577        while (--ancount >= 0 && cp < eom)
00578        {
00579               /* grab the label, even though we know what we asked... */
00580               if ((n = dn_expand((unsigned char *) &ansbuf, eom, cp,
00581                                  (RES_UNC_T) namebuf, sizeof namebuf)) < 0)
00582               {
00583                      dkim_error(dkim, "'%s' reply corrupt", query);
00584                      return -1;
00585               }
00586               /* ...and move past it */
00587               cp += n;
00588 
00589               /* extract the type and class */
00590               if (cp + INT16SZ + INT16SZ + INT32SZ + INT16SZ > eom)
00591               {
00592                      dkim_error(dkim, "'%s' reply corrupt", query);
00593                      return -1;
00594               }
00595               GETSHORT(type, cp);                /* TYPE */
00596               GETSHORT(class, cp);               /* CLASS */
00597 #ifdef QUERY_CACHE
00598               GETLONG(ttl, cp);                  /* TTL */
00599 #else /* QUERY_CACHE */
00600               cp += INT32SZ;
00601 #endif /* QUERY_CACHE */
00602               GETSHORT(n, cp);                   /* RDLENGTH */
00603 
00604               /* handle a CNAME (skip it; assume it was resolved) */
00605               if (type == T_CNAME)
00606               {
00607                      cp += n;
00608                      continue;
00609               }
00610               else if (type == T_RRSIG)
00611               {
00612                      cp += n;
00613                      continue;
00614               }
00615               else if (type != T_TXT)
00616               {
00617                      /* reject anything not valid (e.g. wildcards) */
00618                      dkim_error(dkim,
00619                                 "'%s' unexpected reply class/type (%d/%d)",
00620                                 query, class, type);
00621                      return -1;
00622               }
00623 
00624               if (txtfound != NULL)
00625               {
00626                      dkim_error(dkim, "multiple DNS replies for '%s'",
00627                                 query);
00628                      return -1;
00629               }
00630 
00631               /* remember it */
00632               txtfound = cp;
00633               rdlength = n;
00634 
00635               /* ...and move past that */
00636               cp += n;
00637        }
00638 
00639        if (txtfound == NULL)
00640        {
00641               dkim_error(dkim, "'%s' reply was unresolved CNAME", query);
00642               return -1;
00643        }
00644 
00645        cp = txtfound;
00646 
00647        /* XXX -- maybe deal with a partial reply rather than require it all */
00648        if (cp + rdlength > eom || rdlength > BUFRSZ)
00649        {
00650               dkim_error(dkim, "'%s' reply corrupt", query);
00651               return -1;
00652        }
00653 
00654        /* extract the payload */
00655        memset(outbuf, '\0', sizeof outbuf);
00656        p = outbuf;
00657        eom = outbuf + sizeof outbuf - 1;
00658        while (rdlength > 0 && p < eom)
00659        {
00660               c = *cp++;
00661               rdlength--;
00662               while (c > 0 && p < eom)
00663               {
00664                      *p++ = *cp++;
00665                      c--;
00666                      rdlength--;
00667               }
00668        }
00669 
00670 #ifdef QUERY_CACHE
00671        if (dkim->dkim_libhandle->dkiml_cache != NULL)
00672        {
00673               int err = 0;
00674 
00675               status = dkim_cache_insert(dkim->dkim_libhandle->dkiml_cache,
00676                                          query, outbuf, ttl, &err);
00677               /* XXX -- do something with errors here */
00678        }
00679 #endif /* QUERY_CACHE */
00680 
00681        strlcpy((char *) buf, (char *) outbuf, buflen);
00682 
00683        return 1;
00684 }