/** * Parses an SPF domainspec. * * @param spf_server The SPF server on whose behalf the record is being compiled. * @param spf_response The SPF response in which to store errors. * @param data Output buffer pointer. * @param data_used Output parameter for amount of data written to output buffer. * @param data_avail Input parameter for size of output buffer. * @param src Input buffer pointer. * @param src_len Input buffer length. * @param big_err The error code to return on an over-length condition. * @param cidr_ok True if a CIDR mask is permitted on this domainspec. * @param is_mod True if this is a modifier. */ static SPF_errcode_t SPF_c_parse_domainspec(SPF_server_t *spf_server, SPF_response_t *spf_response, SPF_data_t *data, size_t *data_used, size_t data_avail, const char *src, size_t src_len, SPF_errcode_t big_err, SPF_cidr_t cidr_ok, int is_mod) { SPF_errcode_t err; /* Generic parsing iterators and boundaries */ size_t len; if (spf_server->debug) SPF_debugf("Parsing domainspec starting at %s, cidr is %s", src, cidr_ok == CIDR_OPTIONAL ? "optional" : cidr_ok == CIDR_ONLY ? "only" : cidr_ok == CIDR_NONE ? "forbidden" : "ERROR!" ); /* * create the CIDR length info */ if (cidr_ok == CIDR_OPTIONAL || cidr_ok == CIDR_ONLY) { err = SPF_c_parse_cidr(spf_response, &data->dc, src, &src_len); if (err != SPF_E_SUCCESS) return err; if (data->dc.ipv4 != 0 || data->dc.ipv6 != 0) { len = SPF_data_len(data); SPF_ADD_LEN_TO(*data_used, len, data_avail); data = SPF_data_next(data); } if (cidr_ok == CIDR_ONLY && src_len > 0) { /* We had a mechanism followed by a '/', thus it HAS to be * a CIDR, and the peculiar-looking error message is * justified. However, we don't know _which_ CIDR. */ return SPF_response_add_error_ptr(spf_response, SPF_E_INVALID_CIDR, NULL, src, "Invalid CIDR after mechanism"); } } return SPF_c_parse_macro(spf_server, spf_response, data, data_used, data_avail, src, src_len, big_err, is_mod); }
/** * This could better collect errors, like the compiler does. * This requires that *bufp be either malloced to *buflenp, or NULL * This may realloc *bufp. */ SPF_errcode_t SPF_record_expand_data(SPF_server_t *spf_server, SPF_request_t *spf_request, SPF_response_t *spf_response, SPF_data_t *data, size_t data_len, char **bufp, size_t *buflenp) { SPF_data_t *d, *data_end; size_t len; const char *p_err; // XXX Check this value, when returned. char *p, *p_end; const char *p_read; const char *p_read_end; char *p_write; char *p2, *p2_end; const char *var; char *munged_var = NULL; char *url_var = NULL; /* Pretty-printing buffers. */ char ip4_buf[ INET_ADDRSTRLEN ]; char ip6_buf[ INET6_ADDRSTRLEN ]; /* Hex buffer for ipv6 (size in nibbles) */ char ip6_rbuf[ sizeof( struct in6_addr ) * 4 + 1 ]; char time_buf[ sizeof( "4294967296" ) ]; /* 2^32 seconds max */ int num_found; int i; size_t buflen; int compute_length; SPF_errcode_t err; /* * make sure we were passed valid data to work with */ SPF_ASSERT_NOTNULL(spf_server); SPF_ASSERT_NOTNULL(data); SPF_ASSERT_NOTNULL(bufp); SPF_ASSERT_NOTNULL(buflenp); buflen = 1; /* For the terminating '\0' */ compute_length = 1; p = NULL; p_end = NULL; /* data_end = SPF_mech_end_data( mech ); */ /* doesn't work for mods */ data_end = (SPF_data_t *)((char *)data + data_len); top: #ifdef DEBUG fprintf(stderr, "Pass start compute_length=%d\n", compute_length); #endif /* * expand the data */ for (d = data; d < data_end; d = SPF_data_next(d)) { #ifdef DEBUG fprintf(stderr, " Item type=%d at %p\n", d->dc.parm_type, d); #endif if (d->dc.parm_type == PARM_CIDR) continue; if (d->ds.parm_type == PARM_STRING) { if (compute_length) { buflen += d->ds.len; continue; } /* This should NEVER happen now. */ if (p_end - (p + d->ds.len) <= 0) SPF_error("Failed to allocate enough memory " "to expand string."); memcpy(p, SPF_data_str(d), d->ds.len); p += d->ds.len; continue; } /* Otherwise, it's a variable. */ var = NULL; switch (d->dv.parm_type) { case PARM_LP_FROM: /* local-part of envelope-sender */ var = spf_request->env_from_lp; break; case PARM_ENV_FROM: /* envelope-sender */ var = spf_request->env_from; break; case PARM_DP_FROM: /* envelope-domain */ var = spf_request->env_from_dp; break; case PARM_CUR_DOM: /* current-domain */ var = spf_request->cur_dom; break; case PARM_CLIENT_IP: /* SMTP client IP */ if (compute_length) { len = sizeof(ip6_buf); if (d->dv.url_encode) len *= 3; buflen += len; continue; } if (spf_request->client_ver == AF_INET) { p_err = inet_ntop(AF_INET, &spf_request->ipv4, ip4_buf, sizeof(ip4_buf)); var = ip4_buf; } else if (spf_request->client_ver == AF_INET6) { p2 = ip6_rbuf; p2_end = p2 + sizeof(ip6_rbuf); for (i = 0; i < array_elem(spf_request->ipv6.s6_addr); i++) { p2 += snprintf(p2, p2_end - p2, "%.1x.%.1x.", spf_request->ipv6.s6_addr[i] >> 4, spf_request->ipv6.s6_addr[i] & 0xf); } /* squash the final '.' */ ip6_rbuf[sizeof(struct in6_addr) * 4 - 1] = '\0'; var = ip6_rbuf; } break; case PARM_CLIENT_IP_P: /* SMTP client IP (pretty) */ if (compute_length) { len = sizeof(ip6_buf); if (d->dv.url_encode) len *= 3; buflen += len; continue; } if (spf_request->client_ver == AF_INET) { p_err = inet_ntop(AF_INET, &spf_request->ipv4, ip4_buf, sizeof(ip4_buf)); var = ip4_buf; } else if (spf_request->client_ver == AF_INET6) { p_err = inet_ntop(AF_INET6, &spf_request->ipv6, ip6_buf, sizeof(ip6_buf)); var = ip6_buf; } break; case PARM_TIME: /* time in UTC epoch secs */ if (compute_length) { len = sizeof(time_buf); /* This never gets bigger using URL encoding. */ buflen += len; continue; } snprintf(time_buf, sizeof(time_buf), "%ld", (long)time(NULL)); var = time_buf; break; case PARM_CLIENT_DOM: /* SMTP client domain name */ var = SPF_request_get_client_dom(spf_request); if (! var) return SPF_E_NO_MEMORY; break; case PARM_CLIENT_VER: /* IP ver str - in-addr/ip6 */ if (spf_request->client_ver == AF_INET) var = client_ver_ipv4; else if (spf_request->client_ver == AF_INET6) var = client_ver_ipv6; break; case PARM_HELO_DOM: /* HELO/EHLO domain */ var = spf_request->helo_dom; break; case PARM_REC_DOM: /* receiving domain */ var = SPF_request_get_rec_dom(spf_request); break; default: #ifdef DEBUG fprintf(stderr, "Invalid variable %d\n", d->dv.parm_type); #endif return SPF_E_INVALID_VAR; break; } if (var == NULL) return SPF_E_UNINIT_VAR; len = strlen(var); if (compute_length) { if (d->dv.url_encode) len *= 3; buflen += len; continue; } /* Now we put 'var' through the munging procedure. */ munged_var = (char *)malloc(len + 1); if (munged_var == NULL) return SPF_E_NO_MEMORY; memset(munged_var, 0, len + 1); p_read_end = var + len; p_write = munged_var; /* reverse */ /* The following code confuses both me and Coverity. Shevek. */ if (d->dv.rev) { p_read = p_read_end - 1; while ( p_read >= var ) { if ( SPF_delim_valid(d, *p_read) ) { /* Subtract 1 because p_read points to delim, and * p_read_end points to the following delim. */ len = p_read_end - p_read - 1; memcpy( p_write, p_read + 1, len ); p_write += len; *p_write++ = '.'; p_read_end = p_read; } p_read--; } /* Now p_read_end should point one before the start of the * string. p_read_end might also point there if the string * starts with a delimiter. */ if (p_read_end >= p_read) { len = p_read_end - p_read - 1; memcpy( p_write, p_read + 1, len ); p_write += len; *p_write++ = '.'; } /* p_write always points to the 'next' character. */ p_write--; *p_write = '\0'; } else { p_read = var; while (p_read < p_read_end) { if (SPF_delim_valid(d, *p_read)) *p_write++ = '.'; else *p_write++ = *p_read; p_read++; } *p_write = '\0'; } /* Now munged_var is a copy of var, possibly reversed, and * thus len == strlen(munged_var). However, we continue to * manipulate the underlying munged_var since var is const. */ /* truncate, from the right hand side. */ if (d->dv.num_rhs > 0) { p_read_end = munged_var + len; /* const, at '\0' */ p_write = munged_var + len - 1; num_found = 0; while (p_write > munged_var) { if (*p_write == '.') num_found++; if (num_found == d->dv.num_rhs) break; p_write--; } p_write++; /* Move to just after the '.' */ /* This moves the '\0' as well. */ len = p_read_end - p_write; memmove(munged_var, p_write, len + 1); } var = munged_var; /* Now, we have 'var', of length 'len' */ /* URL encode */ if (d->dv.url_encode) { url_var = malloc(len * 3 + 1); if (url_var == NULL) { if (munged_var) free(munged_var); return SPF_E_NO_MEMORY; } p_read = var; p_write = url_var; /* escape non-uric characters (rfc2396) */ while ( *p_read != '\0' ) { if ( isalnum( (unsigned char)( *p_read ) ) ) *p_write++ = *p_read++; else { switch( *p_read ) { case '-': case '_': case '.': case '!': case '~': case '*': case '\'': case '(': case ')': *p_write++ = *p_read++; break; default: /* No point doing snprintf with a const '4' * because we know we're going to get 4 * characters anyway. */ sprintf( p_write, "%%%02x", *p_read ); p_write += 3; p_read++; break; } } } *p_write = '\0'; var = url_var; len = p_write - url_var; /* Not actually used. */ } /* finish up */ len = snprintf(p, p_end - p, "%s", var); p += len; if (p_end - p <= 0) { if (munged_var) free(munged_var); if (url_var) free(url_var); return SPF_E_INTERNAL_ERROR; } if (munged_var) free(munged_var); munged_var = NULL; if (url_var) free(url_var); url_var = NULL; }
/** * Parses an SPF macro string. * * Note that we cannot write data_avail bytes from data, since we * might be called with a modified data pointer. We MUST compare * data_used with data_avail. * * @param spf_server The SPF server on whose behalf the record is being compiled. * @param spf_response The SPF response in which to store errors. * @param data Output buffer pointer. * @param data_used Output parameter for amount of data written to output buffer. * @param data_avail Input parameter for size of output buffer. * @param src Input buffer pointer. * @param src_len Input buffer length. * @param big_err The error code to return on an over-length condition. * @param is_mod True if this is a modifier. */ static SPF_errcode_t SPF_c_parse_macro(SPF_server_t *spf_server, SPF_response_t *spf_response, SPF_data_t *data, size_t *data_used, size_t data_avail, const char *src, size_t src_len, SPF_errcode_t big_err, int is_mod) { SPF_errcode_t err; /* Generic parsing iterators and boundaries */ size_t idx; size_t len; /* For parsing strings. */ char *dst; size_t ds_avail; size_t ds_len; if (spf_server->debug) SPF_debugf("Parsing macro starting at %s", src); #if 0 if ((void *)data != _align_ptr((void *)data)) SPF_errorf("Data pointer %p is not aligned: Cannot compile.", data); #endif /* * Create the data blocks */ idx = 0; /* Initialise the block as a string. If ds_len == 0 later, we * will just clobber it. */ SPF_INIT_STRING_LITERAL(data_avail - *data_used); // while ( p != end ) { while (idx < src_len) { if (spf_server->debug > 3) SPF_debugf("Current data is at %p", data); /* Either the unit is terminated by a space, or we hit a %. * We should only hit a space if we run past src_len. */ len = strcspn(&src[idx], " %"); // XXX Also tab? if (len > 0) { /* An optimisation */ /* Don't over-run into the CIDR. */ if (idx + len > src_len) len = src_len - idx; if (spf_server->debug > 3) SPF_debugf("Adding string literal (%lu): '%*.*s'", (unsigned long)len, (int)len, (int)len, &src[idx]); /* XXX Bounds-check here. */ SPF_ENSURE_STRING_AVAIL(len); memcpy(dst, &src[idx], len); ds_len += len; dst += len; idx += len; /* If len == 0 then we never entered the while(). Thus * if idx == src_len, then len != 0 and we reach this test. */ } /* However, this logic is overcomplex and I am a simpleton, * so I have moved it out of the condition above. */ if (idx == src_len) break; /* Now, we must have a %-escape code, since if we hit a * space, then we are at the end. * Incrementing idx consumes the % we hit first, and then * we switch on the following character, which also * increments idx. */ idx++; switch (src[idx]) { case '%': if (spf_server->debug > 3) SPF_debugf("Adding literal %%"); SPF_ENSURE_STRING_AVAIL(1); *dst++ = '%'; ds_len++; idx++; break; case '_': if (spf_server->debug > 3) SPF_debugf("Adding literal space"); SPF_ENSURE_STRING_AVAIL(1); *dst++ = ' '; ds_len++; idx++; break; case '-': if (spf_server->debug > 3) SPF_debugf("Adding escaped space"); SPF_ENSURE_STRING_AVAIL(3); *dst++ = '%'; *dst++ = '2'; *dst++ = '0'; ds_len += 3; idx++; break; default: if (spf_server->debug > 3) SPF_debugf("Adding illegal %%-follower '%c' at %d", src[idx], idx); /* SPF spec says to treat it as a literal, not * SPF_E_INVALID_ESC */ /* FIXME issue a warning? */ SPF_ENSURE_STRING_AVAIL(1); *dst++ = '%'; ds_len++; break; case '{': /*vi:}*/ SPF_FINI_STRING_LITERAL(); if (spf_server->debug > 3) SPF_debugf("Adding macro, data is at %p", data); /* this must be a variable */ idx++; err = SPF_c_parse_var(spf_response, &data->dv, &src[idx], is_mod); if (err != SPF_E_SUCCESS) return err; idx += strcspn(&src[idx], "} "); if (src[idx] == '}') idx++; else if (src[idx] == ' ') return SPF_response_add_error_ptr(spf_response, SPF_E_INVALID_VAR, src, &src[idx], "Unterminated variable?"); len = SPF_data_len(data); SPF_ADD_LEN_TO(*data_used, len, data_avail); data = SPF_data_next( data ); if (spf_server->debug > 3) SPF_debugf("Next data is at %p", data); SPF_INIT_STRING_LITERAL(data_avail - *data_used); break; } } SPF_FINI_STRING_LITERAL(); return SPF_E_SUCCESS; }