Back to index

opendkim  2.6.6
dkim-keys.c
Go to the documentation of this file.
00001 /*
00002 **  Copyright (c) 2005-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_keys_c_id[] = "@(#)$Id: dkim-keys.c,v 1.18.10.1 2010/10/27 21:43:08 cm-msk Exp $";
00010 #endif /* !lint */
00011 
00012 /* system includes */
00013 #include <sys/param.h>
00014 #include <sys/types.h>
00015 #include <netinet/in.h>
00016 #include <arpa/inet.h>
00017 #include <arpa/nameser.h>
00018 #include <netdb.h>
00019 #include <resolv.h>
00020 #include <assert.h>
00021 #include <stdio.h>
00022 #include <ctype.h>
00023 #include <string.h>
00024 #include <errno.h>
00025 
00026 #include "build-config.h"
00027 
00028 /* libopendkim includes */
00029 #include "dkim-internal.h"
00030 #include "dkim-types.h"
00031 #include "dkim-keys.h"
00032 #include "dkim-cache.h"
00033 #include "dkim-test.h"
00034 #include "util.h"
00035 #include "dkim-strl.h"
00036 
00037 /* prototypes */
00038 extern void dkim_error __P((DKIM *, const char *, ...));
00039 
00040 /* local definitions needed for DNS queries */
00041 #define MAXPACKET           8192
00042 #if defined(__RES) && (__RES >= 19940415)
00043 # define RES_UNC_T          char *
00044 #else /* __RES && __RES >= 19940415 */
00045 # define RES_UNC_T          unsigned char *
00046 #endif /* __RES && __RES >= 19940415 */
00047 #ifndef T_RRSIG
00048 # define T_RRSIG            46
00049 #endif /* ! T_RRSIG */
00050 
00051 /*
00052 **  DKIM_GET_KEY_DNS -- retrieve a DKIM key from DNS
00053 **
00054 **  Parameters:
00055 **     dkim -- DKIM handle
00056 **     sig -- DKIM_SIGINFO handle
00057 **     buf -- buffer into which to write the result
00058 **     buflen -- bytes available at "buf"
00059 **
00060 **  Return value:
00061 **     A DKIM_STAT_* constant.
00062 */
00063 
00064 DKIM_STAT
00065 dkim_get_key_dns(DKIM *dkim, DKIM_SIGINFO *sig, u_char *buf, size_t buflen)
00066 {
00067 #ifdef QUERY_CACHE
00068        _Bool cached = FALSE;
00069        uint32_t ttl = 0;
00070 #endif /* QUERY_CACHE */
00071        int status;
00072        int qdcount;
00073        int ancount;
00074        int error;
00075        int dnssec = DKIM_DNSSEC_UNKNOWN;
00076        int c;
00077        int n = 0;
00078        int rdlength = 0;
00079        int type = -1;
00080        int class = -1;
00081        size_t anslen;
00082        void *q;
00083        DKIM_LIB *lib;
00084        unsigned char *txtfound = NULL;
00085        unsigned char *p;
00086        unsigned char *cp;
00087        unsigned char *eom;
00088        unsigned char *eob;
00089        unsigned char qname[DKIM_MAXHOSTNAMELEN + 1];
00090        unsigned char ansbuf[MAXPACKET];
00091        struct timeval timeout;
00092        HEADER hdr;
00093 
00094        assert(dkim != NULL);
00095        assert(sig != NULL);
00096        assert(sig->sig_selector != NULL);
00097        assert(sig->sig_domain != NULL);
00098        assert(sig->sig_query == DKIM_QUERY_DNS);
00099 
00100        lib = dkim->dkim_libhandle;
00101 
00102        n = snprintf((char *) qname, sizeof qname - 1, "%s.%s.%s",
00103                     sig->sig_selector, DKIM_DNSKEYNAME, sig->sig_domain);
00104        if (n == -1 || n > sizeof qname - 1)
00105        {
00106               dkim_error(dkim, "key query name too large");
00107               return DKIM_STAT_NORESOURCE;
00108        }
00109 
00110 #ifdef QUERY_CACHE
00111        /* see if we have this data already cached */
00112        if (dkim->dkim_libhandle->dkiml_cache != NULL)
00113        {
00114               int err = 0;
00115               size_t blen = buflen;
00116 
00117               dkim->dkim_cache_queries++;
00118 
00119               status = dkim_cache_query(dkim->dkim_libhandle->dkiml_cache,
00120                                         qname, 0, buf, &blen, &err);
00121               if (status == 0)
00122               {
00123                      dkim->dkim_cache_hits++;
00124                      return DKIM_STAT_OK;
00125               }
00126               /* XXX -- do something with errors here */
00127        }
00128 #endif /* QUERY_CACHE */
00129 
00130        /* see if there's a simulated reply queued; if so, use it */
00131        anslen = dkim_test_dns_get(dkim, ansbuf, sizeof ansbuf);
00132        if (anslen == -1)
00133        {
00134               anslen = sizeof ansbuf;
00135 
00136               timeout.tv_sec = dkim->dkim_timeout;
00137               timeout.tv_usec = 0;
00138 
00139               status = lib->dkiml_dns_start(lib->dkiml_dns_service, T_TXT,
00140                                             qname, ansbuf, anslen, &q);
00141 
00142               if (status != 0)
00143               {
00144                      dkim_error(dkim, "'%s' query failed", qname);
00145                      return DKIM_STAT_KEYFAIL;
00146               }
00147        
00148               if (lib->dkiml_dns_callback == NULL)
00149               {
00150                      timeout.tv_sec = dkim->dkim_timeout;
00151                      timeout.tv_usec = 0;
00152 
00153                      status = lib->dkiml_dns_waitreply(lib->dkiml_dns_service,
00154                                                        q,
00155                                                        dkim->dkim_timeout == 0 ? NULL
00156                                                                                : &timeout,
00157                                                        &anslen, &error,
00158                                                        &dnssec);
00159               }
00160               else
00161               {
00162                      struct timeval master;
00163                      struct timeval next;
00164                      struct timeval *wt;
00165 
00166                      (void) gettimeofday(&master, NULL);
00167                      master.tv_sec += dkim->dkim_timeout;
00168 
00169                      for (;;)
00170                      {
00171                             (void) gettimeofday(&next, NULL);
00172                             next.tv_sec += lib->dkiml_callback_int;
00173 
00174                             dkim_min_timeval(&master, &next,
00175                                              &timeout, &wt);
00176 
00177                             status = lib->dkiml_dns_waitreply(lib->dkiml_dns_service,
00178                                                               q,
00179                                                               dkim->dkim_timeout == 0 ? NULL
00180                                                                                       : &timeout,
00181                                                               &anslen,
00182                                                               &error,
00183                                                               &dnssec);
00184 
00185                             if (wt == &next)
00186                             {
00187                                    if (status == DKIM_DNS_NOREPLY ||
00188                                        status == DKIM_DNS_EXPIRED)
00189                                           lib->dkiml_dns_callback(dkim->dkim_user_context);
00190                                    else
00191                                           break;
00192                             }
00193                             else
00194                             {
00195                                    break;
00196                             }
00197                      }
00198               }
00199 
00200               if (status == DKIM_DNS_EXPIRED)
00201               {
00202                      (void) lib->dkiml_dns_cancel(lib->dkiml_dns_service, q);
00203                      dkim_error(dkim, "'%s' query timed out", qname);
00204                      return DKIM_STAT_KEYFAIL;
00205               }
00206               else if (status == DKIM_DNS_ERROR)
00207               {
00208                      (void) lib->dkiml_dns_cancel(lib->dkiml_dns_service, q);
00209                      dkim_error(dkim, "'%s' query failed", qname);
00210                      return DKIM_STAT_KEYFAIL;
00211               }
00212 
00213               (void) lib->dkiml_dns_cancel(lib->dkiml_dns_service, q);
00214 
00215               sig->sig_dnssec_key = dnssec;
00216        }
00217 
00218        /* set up pointers */
00219        memcpy(&hdr, ansbuf, sizeof hdr);
00220        cp = (u_char *) &ansbuf + HFIXEDSZ;
00221        eom = (u_char *) &ansbuf + anslen;
00222 
00223        /* skip over the name at the front of the answer */
00224        for (qdcount = ntohs((unsigned short) hdr.qdcount);
00225             qdcount > 0;
00226             qdcount--)
00227        {
00228               /* copy it first */
00229               (void) dn_expand((unsigned char *) &ansbuf, eom, cp,
00230                                (char *) qname, sizeof qname);
00231  
00232               if ((n = dn_skipname(cp, eom)) < 0)
00233               {
00234                      dkim_error(dkim, "'%s' reply corrupt", qname);
00235                      return DKIM_STAT_KEYFAIL;
00236               }
00237               cp += n;
00238 
00239               /* extract the type and class */
00240               if (cp + INT16SZ + INT16SZ > eom)
00241               {
00242                      dkim_error(dkim, "'%s' reply corrupt", qname);
00243                      return DKIM_STAT_KEYFAIL;
00244               }
00245               GETSHORT(type, cp);
00246               GETSHORT(class, cp);
00247        }
00248 
00249        if (type != T_TXT || class != C_IN)
00250        {
00251               dkim_error(dkim, "'%s' unexpected reply class/type (%d/%d)",
00252                          qname, class, type);
00253               return DKIM_STAT_KEYFAIL;
00254        }
00255 
00256        /* if NXDOMAIN, return DKIM_STAT_NOKEY */
00257        if (hdr.rcode == NXDOMAIN)
00258        {
00259               dkim_error(dkim, "'%s' record not found", qname);
00260               return DKIM_STAT_NOKEY;
00261        }
00262 
00263        /* if truncated, we can't do it */
00264        if (dkim_check_dns_reply(ansbuf, anslen, C_IN, T_TXT) == 1)
00265        {
00266               dkim_error(dkim, "'%s' reply truncated", qname);
00267               return DKIM_STAT_KEYFAIL;
00268        }
00269 
00270        /* get the answer count */
00271        ancount = ntohs((unsigned short) hdr.ancount);
00272        if (ancount == 0)
00273               return DKIM_STAT_NOKEY;
00274 
00275        /*
00276        **  Extract the data from the first TXT answer.
00277        */
00278 
00279        while (--ancount >= 0 && cp < eom)
00280        {
00281               /* grab the label, even though we know what we asked... */
00282               if ((n = dn_expand((unsigned char *) &ansbuf, eom, cp,
00283                                  (RES_UNC_T) qname, sizeof qname)) < 0)
00284               {
00285                      dkim_error(dkim, "'%s' reply corrupt", qname);
00286                      return DKIM_STAT_KEYFAIL;
00287               }
00288               /* ...and move past it */
00289               cp += n;
00290 
00291               /* extract the type and class */
00292               if (cp + INT16SZ + INT16SZ + INT32SZ + INT16SZ > eom)
00293               {
00294                      dkim_error(dkim, "'%s' reply corrupt", qname);
00295                      return DKIM_STAT_KEYFAIL;
00296               }
00297 
00298               GETSHORT(type, cp);                /* TYPE */
00299               GETSHORT(class, cp);               /* CLASS */
00300 #ifdef QUERY_CACHE
00301               /* get the TTL */
00302               GETLONG(ttl, cp);                  /* TTL */
00303 #else /* QUERY_CACHE */
00304               /* skip the TTL */
00305               cp += INT32SZ;                            /* TTL */
00306 #endif /* QUERY_CACHE */
00307               GETSHORT(n, cp);                   /* RDLENGTH */
00308 
00309               /* skip CNAME if found; assume it was resolved */
00310               if (type == T_CNAME)
00311               {
00312                      cp += n;
00313                      continue;
00314               }
00315               else if (type == T_RRSIG)
00316               {
00317                      cp += n;
00318                      continue;
00319               }
00320               else if (type != T_TXT)
00321               {
00322                      dkim_error(dkim, "'%s' reply was unexpected type %d",
00323                                 qname, type);
00324                      return DKIM_STAT_KEYFAIL;
00325               }
00326 
00327               if (txtfound != NULL)
00328               {
00329                      dkim_error(dkim, "multiple DNS replies for '%s'",
00330                                 qname);
00331                      return DKIM_STAT_MULTIDNSREPLY;
00332               }
00333 
00334               /* remember where this one started */
00335               txtfound = cp;
00336               rdlength = n;
00337 
00338               /* move forward for now */
00339               cp += n;
00340        }
00341 
00342        /* if ancount went below 0, there were no good records */
00343        if (txtfound == NULL)
00344        {
00345               dkim_error(dkim, "'%s' reply was unresolved CNAME", qname);
00346               return DKIM_STAT_KEYFAIL;
00347        }
00348 
00349        /* come back to the one we found */
00350        cp = txtfound;
00351 
00352        /*
00353        **  XXX -- maybe deal with a partial reply rather than require
00354        **        it all
00355        */
00356 
00357        if (cp + rdlength > eom)
00358        {
00359               dkim_error(dkim, "'%s' reply corrupt", qname);
00360               return DKIM_STAT_SYNTAX;
00361        }
00362 
00363        /* extract the payload */
00364        memset(buf, '\0', buflen);
00365        p = buf;
00366        eob = buf + buflen - 1;
00367        while (rdlength > 0 && p < eob)
00368        {
00369               c = *cp++;
00370               rdlength--;
00371               while (c > 0 && p < eob)
00372               {
00373                      *p++ = *cp++;
00374                      c--;
00375                      rdlength--;
00376               }
00377        }
00378 
00379 #ifdef QUERY_CACHE
00380        if (!cached && buf[0] != '\0' &&
00381            dkim->dkim_libhandle->dkiml_cache != NULL)
00382        {
00383               int err = 0;
00384 
00385               status = dkim_cache_insert(dkim->dkim_libhandle->dkiml_cache,
00386                                          qname, buf, ttl, &err);
00387               /* XXX -- do something with errors here */
00388        }
00389 #endif /* QUERY_CACHE */
00390 
00391        return DKIM_STAT_OK;
00392 }
00393 
00394 /*
00395 **  DKIM_GET_KEY_FILE -- retrieve a DKIM key from a text file (for testing)
00396 **
00397 **  Parameters:
00398 **     dkim -- DKIM handle
00399 **     sig -- DKIM_SIGINFO handle
00400 **     buf -- buffer into which to write the result
00401 **     buflen -- bytes available at "buf"
00402 **
00403 **  Return value:
00404 **     A DKIM_STAT_* constant.
00405 **
00406 **  Notes:
00407 **     The file opened is defined by the library option DKIM_OPTS_QUERYINFO
00408 **     and must be set prior to use of this function.  Failing to do
00409 **     so will cause this function to return DKIM_STAT_KEYFAIL every time.
00410 **     The file should contain lines of the form:
00411 ** 
00412 **            <selector>._domainkey.<domain> <space> key-data
00413 **
00414 **     Case matching on the left is case-sensitive, but libopendkim already
00415 **     wraps the domain name to lowercase.
00416 */
00417 
00418 DKIM_STAT
00419 dkim_get_key_file(DKIM *dkim, DKIM_SIGINFO *sig, u_char *buf, size_t buflen)
00420 {
00421        int n;
00422        FILE *f;
00423        u_char *p;
00424        u_char *p2;
00425        u_char *path;
00426        char name[DKIM_MAXHOSTNAMELEN + 1];
00427 
00428        assert(dkim != NULL);
00429        assert(sig != NULL);
00430        assert(sig->sig_selector != NULL);
00431        assert(sig->sig_domain != NULL);
00432        assert(sig->sig_query == DKIM_QUERY_FILE);
00433 
00434        path = dkim->dkim_libhandle->dkiml_queryinfo;
00435        if (path[0] == '\0')
00436        {
00437               dkim_error(dkim, "query file not defined");
00438               return DKIM_STAT_KEYFAIL;
00439        }
00440 
00441        f = fopen((char *) path, "r");
00442        if (f == NULL)
00443        {
00444               dkim_error(dkim, "%s: fopen(): %s", path, strerror(errno));
00445               return DKIM_STAT_KEYFAIL;
00446        }
00447 
00448        n = snprintf(name, sizeof name, "%s.%s.%s", sig->sig_selector,
00449                     DKIM_DNSKEYNAME, sig->sig_domain);
00450        if (n == -1 || n > sizeof name)
00451        {
00452               dkim_error(dkim, "key query name too large");
00453               fclose(f);
00454               return DKIM_STAT_NORESOURCE;
00455        }
00456 
00457        memset(buf, '\0', buflen);
00458        while (fgets((char *) buf, buflen, f) != NULL)
00459        {
00460               if (buf[0] == '#')
00461                      continue;
00462 
00463               p2 = NULL;
00464 
00465               for (p = buf; *p != '\0'; p++)
00466               {
00467                      if (*p == '\n')
00468                      {
00469                             *p = '\0';
00470                             break;
00471                      }
00472                      else if (isascii(*p) && isspace(*p))
00473                      {
00474                             *p = '\0';
00475                             p2 = p + 1;
00476                      }
00477                      else if (p2 != NULL)
00478                      {
00479                             break;
00480                      }
00481               }
00482 
00483               if (strcasecmp((char *) name, (char *) buf) == 0 && p2 != NULL)
00484               {
00485                      strlcpy((char *) buf, (char *) p2, buflen);
00486                      fclose(f);
00487                      return DKIM_STAT_OK;
00488               }
00489        }
00490 
00491        fclose(f);
00492 
00493        return DKIM_STAT_NOKEY;
00494 }