spf_expand.c

Go to the documentation of this file.
00001 /*
00002  * This program is free software; you can redistribute it and/or modify
00003  * it under the terms of either:
00004  *
00005  *   a) The GNU Lesser General Public License as published by the Free
00006  *        Software Foundation; either version 2.1, or (at your option) any
00007  *        later version,
00008  *
00009  *   OR
00010  *
00011  *   b) The two-clause BSD license.
00012  *
00013  * These licenses can be found with the distribution in the file LICENSES
00014  */
00015 
00021 #include "spf_sys_config.h"
00022 
00023 
00024 #ifdef STDC_HEADERS
00025 # include <stdio.h>             /* stdin / stdout */
00026 # include <stdlib.h>       /* malloc / free */
00027 # include <ctype.h>             /* isupper / tolower */
00028 #endif
00029 
00030 #ifdef HAVE_STRING_H
00031 # include <string.h>       /* strstr / strdup */
00032 #else
00033 # ifdef HAVE_STRINGS_H
00034 #  include <strings.h>     /* strstr / strdup */
00035 # endif
00036 #endif
00037 
00038 #if TIME_WITH_SYS_TIME
00039 # include <sys/time.h>
00040 # include <time.h>
00041 #else
00042 # if HAVE_SYS_TIME_H
00043 #  include <sys/time.h>
00044 # else
00045 #  include <time.h>
00046 # endif
00047 #endif
00048 #ifdef HAVE_STRING_H
00049 #include <string.h>
00050 #endif
00051 
00052 
00053 #include "spf.h"
00054 #include "spf_internal.h"
00055 #include "spf_record.h"
00056 
00057 
00058 #define COMPUTE
00059 // #define DEBUG
00060 
00061 static const char               client_ver_ipv4[] = "in-addr";
00062 static const char               client_ver_ipv6[] = "ip6";
00063 
00064 
00065 static inline int
00066 SPF_delim_valid(SPF_data_t *d, char c)
00067 {
00068         return (   ( d->dv.delim_dot   && c == '.' )
00069                         || ( d->dv.delim_dash  && c == '-' )
00070                         || ( d->dv.delim_plus  && c == '+' )
00071                         || ( d->dv.delim_equal && c == '=' )
00072                         || ( d->dv.delim_bar   && c == '|' )
00073                         || ( d->dv.delim_under && c == '_' ) );
00074 }
00075 
00081 SPF_errcode_t
00082 SPF_record_expand_data(SPF_server_t *spf_server,
00083                                 SPF_request_t *spf_request,
00084                                 SPF_response_t *spf_response,
00085                                 SPF_data_t *data, size_t data_len,
00086                                 char **bufp, size_t *buflenp)
00087 {
00088         SPF_data_t      *d, *data_end;
00089 
00090         size_t           len;
00091         const char      *p_err; // XXX Check this value, when returned.
00092         char            *p, *p_end;
00093         const char      *p_read;
00094         const char      *p_read_end;
00095         char            *p_write;
00096         char            *p2, *p2_end;
00097 
00098 
00099         const char      *var;
00100         char            *munged_var = NULL;
00101         char            *url_var = NULL;
00102 
00103                         /* Pretty-printing buffers. */
00104         char            ip4_buf[ INET_ADDRSTRLEN ];
00105         char            ip6_buf[ INET6_ADDRSTRLEN ];
00106                         /* Hex buffer for ipv6 (size in nibbles) */
00107         char            ip6_rbuf[ sizeof( struct in6_addr ) * 4 + 1 ];
00108 
00109         char            time_buf[ sizeof( "4294967296" ) ]; /* 2^32 seconds max         */
00110 
00111         int                     num_found;
00112         int                     i;
00113 #ifdef COMPUTE
00114         int                     buflen;
00115         int                     compute_length;
00116         SPF_errcode_t    err;
00117 
00118         buflen = 1;     /* For the terminating '\0' */
00119         compute_length = 1;
00120         p = NULL;
00121         p_end = NULL;
00122 #endif
00123 
00124 
00125         /*
00126          * make sure we were passed valid data to work with
00127          */
00128         SPF_ASSERT_NOTNULL(spf_server);
00129         SPF_ASSERT_NOTNULL(data);
00130         SPF_ASSERT_NOTNULL(bufp);
00131         SPF_ASSERT_NOTNULL(buflenp);
00132 
00133         /* data_end = SPF_mech_end_data( mech ); */ /* doesn't work for mods */
00134         data_end = (SPF_data_t *)((char *)data + data_len);
00135 
00136 #ifndef COMPUTE
00137         /*
00138          * make sure the return buffer is big enough
00139          *
00140          * find max length of all variables
00141          */
00142 
00143         len = 0;
00144         for (d = data; d < data_end; d = SPF_data_next(d)) {
00145                 switch (d->ds.parm_type) {
00146                 case PARM_CIDR:
00147                         break;
00148 
00149                 case PARM_STRING:
00150                         len += d->ds.len;
00151                         break;
00152 
00153                 case PARM_CLIENT_IP:
00154                         len += sizeof( ip6_rbuf );
00155                         break;
00156 
00157                 case PARM_CLIENT_IP_P:
00158                         len += sizeof( ip6_buf );
00159                         break;
00160 
00161                 default:
00162                         /* An interpolated variable. Might be any field from
00163                          * the SPF_request_t */
00164                         if ( spf_request->max_var_len > 8 )
00165                                 len += spf_request->max_var_len * 3; /* url encoding */
00166                         else
00167                                 len += 8 * 3;
00168                         break;
00169                 }
00170         }
00171         len += sizeof('\0');            /* Strictly, ANSI says this is 1. */
00172 
00173         if (*buflenp < len) {
00174                 char            *new_rec;
00175                 size_t          new_len;
00176 
00177                 /* allocate lots so we don't have to remalloc often */
00178                 new_len = len + 64;
00179 
00180                 new_rec = realloc(*bufp, new_len);
00181                 if (new_rec == NULL)
00182                         return SPF_E_NO_MEMORY;
00183 
00184                 *bufp = new_rec;
00185                 *buflenp = new_len;
00186         }
00187         memset(*bufp, '\0', *buflenp);          /* cheaper than NUL at each step */
00188         p = *bufp;
00189         p_end = *bufp + *buflenp;
00190 #endif
00191 
00192 
00193 #ifdef COMPUTE
00194 top:
00195 #ifdef DEBUG
00196         fprintf(stderr, "Pass start compute_length=%d\n", compute_length);
00197 #endif
00198 #endif
00199         /*
00200          * expand the data
00201          */
00202         for (d = data; d < data_end; d = SPF_data_next(d)) {
00203 #ifdef DEBUG
00204                 fprintf(stderr, " Item type=%d at %p\n", d->dc.parm_type, d);
00205 #endif
00206                 if (d->dc.parm_type == PARM_CIDR)
00207                         continue;
00208 
00209                 if (d->ds.parm_type == PARM_STRING) {
00210 #ifdef COMPUTE
00211                         if (compute_length) {
00212                                 buflen += d->ds.len;
00213                                 continue;
00214                         }
00215 #endif
00216                         /* This should NEVER happen now. */
00217                         if (p_end - (p + d->ds.len) <= 0)
00218                                         SPF_error("Failed to allocate enough memory "
00219                                                                 "to expand string.");
00220                         memcpy(p, SPF_data_str(d), d->ds.len);
00221                         p += d->ds.len;
00222                         continue;
00223                 }
00224 
00225                 /* Otherwise, it's a variable. */
00226 
00227                 var = NULL;
00228                 switch (d->dv.parm_type) {
00229                 case PARM_LP_FROM:              /* local-part of envelope-sender */
00230                         var = spf_request->env_from_lp;
00231                         break;
00232 
00233                 case PARM_ENV_FROM:             /* envelope-sender                              */
00234                         var = spf_request->env_from;
00235                         break;
00236 
00237                 case PARM_DP_FROM:              /* envelope-domain                              */
00238                         var = spf_request->env_from_dp;
00239                         break;
00240 
00241                 case PARM_CUR_DOM:              /* current-domain                               */
00242                         var = spf_request->cur_dom;
00243                         break;
00244 
00245                 case PARM_CLIENT_IP:            /* SMTP client IP                               */
00246 #ifdef COMPUTE
00247                         if (compute_length) {
00248                                 len = sizeof(ip6_buf);
00249                                 if (d->dv.url_encode)
00250                                         len *= 3;
00251                                 buflen += len;
00252                                 continue;
00253                         }
00254 #endif
00255                         if (spf_request->client_ver == AF_INET) {
00256                                 p_err = inet_ntop(AF_INET, &spf_request->ipv4,
00257                                                                    ip4_buf, sizeof(ip4_buf));
00258                                 var = ip4_buf;
00259                         }
00260                         else if (spf_request->client_ver == AF_INET6) {
00261                                 p2 = ip6_rbuf;
00262                                 p2_end = p2 + sizeof(ip6_rbuf);
00263 
00264                                 for (i = 0; i < array_elem(spf_request->ipv6.s6_addr); i++) {
00265                                         p2 += snprintf(p2, p2_end - p2, "%.1x.%.1x.",
00266                                                                         spf_request->ipv6.s6_addr[i] >> 4,
00267                                                                         spf_request->ipv6.s6_addr[i] & 0xf);
00268                                 }
00269 
00270                                 /* squash the final '.' */
00271                                 ip6_rbuf[sizeof(struct in6_addr) * 4 - 1] = '\0';
00272 
00273                                 var = ip6_rbuf;
00274                         }
00275                         break;
00276 
00277                 case PARM_CLIENT_IP_P:          /* SMTP client IP (pretty)              */
00278 #ifdef COMPUTE
00279                         if (compute_length) {
00280                                 len = sizeof(ip6_buf);
00281                                 if (d->dv.url_encode)
00282                                         len *= 3;
00283                                 buflen += len;
00284                                 continue;
00285                         }
00286 #endif
00287                         if (spf_request->client_ver == AF_INET) {
00288                                 p_err = inet_ntop(AF_INET, &spf_request->ipv4,
00289                                                                    ip4_buf, sizeof(ip4_buf));
00290                                 var = ip4_buf;
00291                         }
00292                         else if (spf_request->client_ver == AF_INET6) {
00293                                 p_err = inet_ntop(AF_INET6, &spf_request->ipv6,
00294                                                                    ip6_buf, sizeof(ip6_buf));
00295                                 var = ip6_buf;
00296                         }
00297                         break;
00298 
00299                 case PARM_TIME:                         /* time in UTC epoch secs               */
00300 #ifdef COMPUTE
00301                         if (compute_length) {
00302                                 len = sizeof(time_buf);
00303                                 /* This never gets bigger using URL encoding. */
00304                                 buflen += len;
00305                                 continue;
00306                         }
00307 #endif
00308                         snprintf(time_buf, sizeof(time_buf), "%ld",
00309                                           (long)time(NULL));
00310                         var = time_buf;
00311                         break;
00312 
00313                 case PARM_CLIENT_DOM:           /* SMTP client domain name              */
00314                         var = SPF_request_get_client_dom(spf_request);
00315                         if (! var)
00316                                 return SPF_E_NO_MEMORY;
00317                         break;
00318 
00319                 case PARM_CLIENT_VER:           /* IP ver str - in-addr/ip6             */
00320                         if (spf_request->client_ver == AF_INET)
00321                                 var = client_ver_ipv4;
00322                         else if (spf_request->client_ver == AF_INET6)
00323                                 var = client_ver_ipv6;
00324                         break;
00325 
00326                 case PARM_HELO_DOM:             /* HELO/EHLO domain                             */
00327                         var = spf_request->helo_dom;
00328                         break;
00329 
00330                 case PARM_REC_DOM:              /* receiving domain                             */
00331                         var = SPF_request_get_rec_dom(spf_request);
00332                         break;
00333 
00334                 default:
00335 #ifdef DEBUG
00336                         fprintf(stderr, "Invalid variable %d\n", d->dv.parm_type);
00337 #endif
00338                         return SPF_E_INVALID_VAR;
00339                         break;
00340                 }
00341 
00342                 if (var == NULL)
00343                         return SPF_E_UNINIT_VAR;
00344 
00345                 len = strlen(var);
00346 #ifdef COMPUTE
00347                 if (compute_length) {
00348                         if (d->dv.url_encode)
00349                                 len *= 3;
00350                         buflen += len;
00351                         continue;
00352                 }
00353 #endif
00354 
00355                 /* Now we put 'var' through the munging procedure. */
00356                 munged_var = (char *)malloc(len + 1);
00357                 if (munged_var == NULL)
00358                         return SPF_E_NO_MEMORY;
00359                 memset(munged_var, 0, len + 1);
00360 
00361                 p_read_end = var + len;
00362                 p_write = munged_var;
00363 
00364                 /* reverse */
00365 
00366 /* The following code confuses both me and Coverity. Shevek. */
00367 
00368                 if (d->dv.rev) {
00369                         p_read = p_read_end - 1;
00370 
00371                         while ( p_read >= var ) {
00372                                 if ( SPF_delim_valid(d, *p_read) ) {
00373                                         /* Subtract 1 because p_read points to delim, and
00374                                          * p_read_end points to the following delim. */
00375                                         len = p_read_end - p_read - 1;
00376                                         memcpy( p_write, p_read + 1, len );
00377                                         p_write += len;
00378                                         *p_write++ = '.';
00379 
00380                                         p_read_end = p_read;
00381                                 }
00382                                 p_read--;
00383                         }
00384 
00385                         /* Now p_read_end should point one before the start of the
00386                          * string. p_read_end might also point there if the string
00387                          * starts with a delimiter. */
00388                         if (p_read_end >= p_read) {
00389                                 len = p_read_end - p_read - 1;
00390                                 memcpy( p_write, p_read + 1, len );
00391                                 p_write += len;
00392                                 *p_write++ = '.';
00393                         }
00394 
00395                         /* p_write always points to the 'next' character. */
00396                         p_write--;
00397                         *p_write = '\0';
00398                 }
00399                 else {
00400                         p_read = var;
00401 
00402                         while (p_read < p_read_end) {
00403                                 if (SPF_delim_valid(d, *p_read))
00404                                         *p_write++ = '.';
00405                                 else
00406                                         *p_write++ = *p_read;
00407                                 p_read++;
00408                         }
00409 
00410                         *p_write = '\0';
00411                 }
00412 
00413                 /* Now munged_var is a copy of var, possibly reversed, and
00414                  * thus len == strlen(munged_var). However, we continue to
00415                  * manipulate the underlying munged_var since var is const. */
00416 
00417                 /* truncate, from the right hand side. */
00418                 if (d->dv.num_rhs > 0) {
00419                         p_read_end = munged_var + len;          /* const, at '\0' */
00420                         p_write = munged_var + len - 1;
00421                         num_found = 0;
00422                         while (p_write > munged_var) {
00423                                 if (*p_write == '.')
00424                                         num_found++;
00425                                 if (num_found == d->dv.num_rhs)
00426                                         break;
00427                                 p_write--;
00428                         }
00429                         p_write++;              /* Move to just after the '.' */
00430                         /* This moves the '\0' as well. */
00431                         len = p_read_end - p_write;
00432                         memmove(munged_var, p_write, len + 1);
00433                 }
00434 
00435                 var = munged_var;
00436                 /* Now, we have 'var', of length 'len' */
00437 
00438                 /* URL encode */
00439 
00440                 if (d->dv.url_encode) {
00441                         url_var = malloc(len * 3 + 1);
00442                         if (url_var == NULL) {
00443                                 if (munged_var)
00444                                         free(munged_var);
00445                                 return SPF_E_NO_MEMORY;
00446                         }
00447 
00448                         p_read = var;
00449                         p_write = url_var;
00450 
00451                         /* escape non-uric characters (rfc2396) */
00452                         while ( *p_read != '\0' )
00453                         {
00454                                 if ( isalnum( (unsigned char)( *p_read  ) ) )
00455                                         *p_write++ = *p_read++;
00456                                 else
00457                                 {
00458                                         switch( *p_read )
00459                                         {
00460                                         case '-':
00461                                         case '_':
00462                                         case '.':
00463                                         case '!':
00464                                         case '~':
00465                                         case '*':
00466                                         case '\'':
00467                                         case '(':
00468                                         case ')':
00469                                                 *p_write++ = *p_read++;
00470                                                 break;
00471 
00472                                         default:
00473                                                 /* No point doing snprintf with a const '4'
00474                                                  * because we know we're going to get 4
00475                                                  * characters anyway. */
00476                                                 sprintf( p_write, "%%%02x", *p_read );
00477                                                 p_write += 3;
00478                                                 p_read++;
00479                                                 break;
00480                                         }
00481                                 }
00482                         }
00483                         *p_write = '\0';
00484 
00485                         var = url_var;
00486                         len = p_write - url_var;                /* Not actually used. */
00487                 }
00488 
00489 
00490                 /* finish up */
00491                 len = snprintf(p, p_end - p, "%s", var);
00492                 p += len;
00493                 if (p_end - p <= 0) {
00494                         if (munged_var)
00495                                 free(munged_var);
00496                         if (url_var)
00497                                 free(url_var);
00498                         return SPF_E_INTERNAL_ERROR;
00499                 }
00500 
00501                 if (munged_var)
00502                         free(munged_var);
00503                 munged_var = NULL;
00504                 if (url_var)
00505                         free(url_var);
00506                 url_var = NULL;
00507         }
00508 #ifdef DEBUG
00509         fprintf(stderr, "Pass end compute_length=%d\n", compute_length);
00510 #endif
00511 
00512 #ifdef COMPUTE
00513         if (compute_length) {
00514                 compute_length = 0;
00515                 /* Do something about (re-)allocating the buffer. */
00516                 err = SPF_realloc(bufp, buflenp, buflen);
00517                 if (err != SPF_E_SUCCESS)
00518                         return err;
00519                 p = *bufp;
00520                 p_end = *bufp + *buflenp;
00521                 goto top;
00522         }
00523 #endif
00524 
00525         *p++ = '\0';
00526 
00527         return SPF_E_SUCCESS;
00528 }

Generated on Tue Nov 4 13:27:39 2008 for libspf2 by  doxygen 1.5.4