libspf2  1.2.11
spf_expand.c
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or modify
3  * it under the terms of either:
4  *
5  * a) The GNU Lesser General Public License as published by the Free
6  * Software Foundation; either version 2.1, or (at your option) any
7  * later version,
8  *
9  * OR
10  *
11  * b) The two-clause BSD license.
12  *
13  * These licenses can be found with the distribution in the file LICENSES
14  */
15 
21 #include "spf_sys_config.h"
22 
23 
24 #ifdef STDC_HEADERS
25 # include <stdio.h> /* stdin / stdout */
26 # include <stdlib.h> /* malloc / free */
27 # include <ctype.h> /* isupper / tolower */
28 #endif
29 
30 #ifdef HAVE_STRING_H
31 # include <string.h> /* strstr / strdup */
32 #else
33 # ifdef HAVE_STRINGS_H
34 # include <strings.h> /* strstr / strdup */
35 # endif
36 #endif
37 
38 #if TIME_WITH_SYS_TIME
39 # include <sys/time.h>
40 # include <time.h>
41 #else
42 # if HAVE_SYS_TIME_H
43 # include <sys/time.h>
44 # else
45 # include <time.h>
46 # endif
47 #endif
48 #ifdef HAVE_STRING_H
49 #include <string.h>
50 #endif
51 
52 
53 #include "spf.h"
54 #include "spf_internal.h"
55 #include "spf_record.h"
56 
57 
58 // #define DEBUG
59 
60 static const char client_ver_ipv4[] = "in-addr";
61 static const char client_ver_ipv6[] = "ip6";
62 
63 
64 static inline int
65 SPF_delim_valid(SPF_data_t *d, char c)
66 {
67  return ( ( d->dv.delim_dot && c == '.' )
68  || ( d->dv.delim_dash && c == '-' )
69  || ( d->dv.delim_plus && c == '+' )
70  || ( d->dv.delim_equal && c == '=' )
71  || ( d->dv.delim_bar && c == '|' )
72  || ( d->dv.delim_under && c == '_' ) );
73 }
74 
81 SPF_record_expand_data(SPF_server_t *spf_server,
82  SPF_request_t *spf_request,
83  SPF_response_t *spf_response,
84  SPF_data_t *data, size_t data_len,
85  char **bufp, size_t *buflenp)
86 {
87  SPF_data_t *d, *data_end;
88 
89  size_t len, label_len;
90  const char *p_err; // XXX Check this value, when returned.
91  char *p, *p_end;
92  const char *p_read;
93  const char *p_read_end;
94  char *p_write;
95  char *p2, *p2_end;
96 
97 
98  const char *var;
99  char *munged_var = NULL;
100  char *url_var = NULL;
101 
102  /* Pretty-printing buffers. */
103  char ip4_buf[ INET_ADDRSTRLEN ];
104  char ip6_buf[ INET6_ADDRSTRLEN ];
105  /* Hex buffer for ipv6 (size in nibbles) */
106  char ip6_rbuf[ sizeof( struct in6_addr ) * 4 + 1 ];
107 
108  char time_buf[ sizeof( "4294967296" ) ]; /* 2^32 seconds max */
109 
110  int num_found;
111  int i;
112  size_t buflen;
113  int compute_length;
114  SPF_errcode_t err;
115 
116 
117  /*
118  * make sure we were passed valid data to work with
119  */
120  SPF_ASSERT_NOTNULL(spf_server);
121  SPF_ASSERT_NOTNULL(data);
122  SPF_ASSERT_NOTNULL(bufp);
123  SPF_ASSERT_NOTNULL(buflenp);
124 
125  buflen = 1; /* For the terminating '\0' */
126  compute_length = 1;
127  p = NULL;
128  p_end = NULL;
129 
130  /* data_end = SPF_mech_end_data( mech ); */ /* doesn't work for mods */
131  data_end = (SPF_data_t *)((char *)data + data_len);
132 
133 top:
134 #ifdef DEBUG
135  fprintf(stderr, "Pass start compute_length=%d\n", compute_length);
136 #endif
137  /*
138  * expand the data
139  */
140  for (d = data; d < data_end; d = SPF_data_next(d)) {
141 #ifdef DEBUG
142  fprintf(stderr, " Item type=%d at %p\n", d->dc.parm_type, d);
143 #endif
144  if (d->dc.parm_type == PARM_CIDR)
145  continue;
146 
147  if (d->ds.parm_type == PARM_STRING) {
148  if (compute_length) {
149  buflen += d->ds.len;
150  continue;
151  }
152  /* This should NEVER happen now. */
153  if (p_end - (p + d->ds.len) <= 0)
154  SPF_error("Failed to allocate enough memory "
155  "to expand string.");
156  memcpy(p, SPF_data_str(d), d->ds.len);
157  p += d->ds.len;
158  continue;
159  }
160 
161  /* Otherwise, it's a variable. */
162 
163  var = NULL;
164  switch (d->dv.parm_type) {
165  case PARM_LP_FROM: /* local-part of envelope-sender */
166  var = spf_request->env_from_lp;
167  break;
168 
169  case PARM_ENV_FROM: /* envelope-sender */
170  var = spf_request->env_from;
171  break;
172 
173  case PARM_DP_FROM: /* envelope-domain */
174  var = spf_request->env_from_dp;
175  break;
176 
177  case PARM_CUR_DOM: /* current-domain */
178  var = spf_request->cur_dom;
179  break;
180 
181  case PARM_CLIENT_IP: /* SMTP client IP */
182  if (compute_length) {
183  len = sizeof(ip6_rbuf);
184  if (d->dv.url_encode)
185  len *= 3;
186  buflen += len;
187  continue;
188  }
189  if (spf_request->client_ver == AF_INET) {
190  p_err = inet_ntop(AF_INET, &spf_request->ipv4,
191  ip4_buf, sizeof(ip4_buf));
192  var = ip4_buf;
193  }
194  else if (spf_request->client_ver == AF_INET6) {
195  p2 = ip6_rbuf;
196  p2_end = p2 + sizeof(ip6_rbuf);
197 
198  for (i = 0; i < array_elem(spf_request->ipv6.s6_addr); i++) {
199  p2 += snprintf(p2, p2_end - p2, "%.1x.%.1x.",
200  spf_request->ipv6.s6_addr[i] >> 4,
201  spf_request->ipv6.s6_addr[i] & 0xf);
202  }
203 
204  /* squash the final '.' */
205  ip6_rbuf[sizeof(struct in6_addr) * 4 - 1] = '\0';
206 
207  var = ip6_rbuf;
208  }
209  break;
210 
211  case PARM_CLIENT_IP_P: /* SMTP client IP (pretty) */
212  if (compute_length) {
213  len = sizeof(ip6_rbuf);
214  if (d->dv.url_encode)
215  len *= 3;
216  buflen += len;
217  continue;
218  }
219  if (spf_request->client_ver == AF_INET) {
220  p_err = inet_ntop(AF_INET, &spf_request->ipv4,
221  ip4_buf, sizeof(ip4_buf));
222  var = ip4_buf;
223  }
224  else if (spf_request->client_ver == AF_INET6) {
225  p_err = inet_ntop(AF_INET6, &spf_request->ipv6,
226  ip6_buf, sizeof(ip6_buf));
227  var = ip6_buf;
228  }
229  break;
230 
231  case PARM_TIME: /* time in UTC epoch secs */
232  if (compute_length) {
233  len = sizeof(time_buf);
234  /* This never gets bigger using URL encoding. */
235  buflen += len;
236  continue;
237  }
238  snprintf(time_buf, sizeof(time_buf), "%ld",
239  (long)time(NULL));
240  var = time_buf;
241  break;
242 
243  case PARM_CLIENT_DOM: /* SMTP client domain name */
244  var = SPF_request_get_client_dom(spf_request);
245  if (! var)
246  return SPF_E_NO_MEMORY;
247  break;
248 
249  case PARM_CLIENT_VER: /* IP ver str - in-addr/ip6 */
250  if (spf_request->client_ver == AF_INET)
251  var = client_ver_ipv4;
252  else if (spf_request->client_ver == AF_INET6)
253  var = client_ver_ipv6;
254  break;
255 
256  case PARM_HELO_DOM: /* HELO/EHLO domain */
257  var = spf_request->helo_dom;
258  break;
259 
260  case PARM_REC_DOM: /* receiving domain */
261  var = SPF_request_get_rec_dom(spf_request);
262  break;
263 
264  default:
265 #ifdef DEBUG
266  fprintf(stderr, "Invalid variable %d\n", d->dv.parm_type);
267 #endif
268  return SPF_E_INVALID_VAR;
269  break;
270  }
271 
272  if (var == NULL)
273  return SPF_E_UNINIT_VAR;
274 
275  len = strlen(var);
276  if (compute_length) {
277  if (d->dv.url_encode)
278  len *= 3;
279  buflen += len;
280  continue;
281  }
282 
283  /* Now we put 'var' through the munging procedure. */
284  munged_var = (char *)malloc(len + 1);
285  if (munged_var == NULL)
286  return SPF_E_NO_MEMORY;
287  memset(munged_var, 0, len + 1);
288 
289  p_read_end = var + len;
290  p_write = munged_var;
291 
292  /* reverse */
293 
294 /* The following code confuses both me and Coverity. Shevek. */
295 
296  if (d->dv.rev) {
297  p_read = p_read_end - 1;
298 
299  while ( p_read >= var ) {
300  if ( SPF_delim_valid(d, *p_read) ) {
301  /* Subtract 1 because p_read points to delim, and
302  * p_read_end points to the following delim. */
303  label_len = p_read_end - p_read - 1;
304  memcpy( p_write, p_read + 1, label_len );
305  p_write += label_len;
306  *p_write++ = '.';
307 
308  p_read_end = p_read;
309  }
310  p_read--;
311  }
312 
313  /* Now p_read_end should point one before the start of the
314  * string. p_read_end might also point there if the string
315  * starts with a delimiter. */
316  if (p_read_end >= p_read) {
317  label_len = p_read_end - p_read - 1;
318  memcpy( p_write, p_read + 1, label_len );
319  p_write += label_len;
320  *p_write++ = '.';
321  }
322 
323  /* p_write always points to the 'next' character. */
324  p_write--;
325  *p_write = '\0';
326  }
327  else {
328  p_read = var;
329 
330  while (p_read < p_read_end) {
331  if (SPF_delim_valid(d, *p_read))
332  *p_write++ = '.';
333  else
334  *p_write++ = *p_read;
335  p_read++;
336  }
337 
338  *p_write = '\0';
339  }
340 
341  /* Now munged_var is a copy of var, possibly reversed, and
342  * thus len == strlen(munged_var). However, we continue to
343  * manipulate the underlying munged_var since var is const. */
344 
345  /* truncate, from the right hand side. */
346  if (d->dv.num_rhs > 0) {
347  p_read_end = munged_var + len; /* const, at '\0' */
348  p_write = munged_var + len - 1;
349  num_found = 0;
350  while (p_write > munged_var) {
351  if (*p_write == '.')
352  num_found++;
353  if (num_found == d->dv.num_rhs)
354  break;
355  p_write--;
356  }
357  p_write++; /* Move to just after the '.' */
358  /* This moves the '\0' as well. */
359  len = p_read_end - p_write;
360  memmove(munged_var, p_write, len + 1);
361  }
362 
363  var = munged_var;
364  /* Now, we have 'var', of length 'len' */
365 
366  /* URL encode */
367 
368  if (d->dv.url_encode) {
369  url_var = malloc(len * 3 + 1);
370  if (url_var == NULL) {
371  if (munged_var)
372  free(munged_var);
373  return SPF_E_NO_MEMORY;
374  }
375 
376  p_read = var;
377  p_write = url_var;
378 
379  /* escape non-uric characters (rfc2396) */
380  while ( *p_read != '\0' )
381  {
382  if ( isalnum( (unsigned char)( *p_read ) ) )
383  *p_write++ = *p_read++;
384  else
385  {
386  switch( *p_read )
387  {
388  case '-':
389  case '_':
390  case '.':
391  case '!':
392  case '~':
393  case '*':
394  case '\'':
395  case '(':
396  case ')':
397  *p_write++ = *p_read++;
398  break;
399 
400  default:
401  snprintf( p_write, 4, "%%%02x", (unsigned char) *p_read );
402  p_write += 3;
403  p_read++;
404  break;
405  }
406  }
407  }
408  *p_write = '\0';
409 
410  var = url_var;
411  len = p_write - url_var; /* Not actually used. */
412  }
413 
414 
415  /* finish up */
416  len = snprintf(p, p_end - p, "%s", var);
417  p += len;
418  if (p_end - p <= 0) {
419  if (munged_var)
420  free(munged_var);
421  if (url_var)
422  free(url_var);
423  return SPF_E_INTERNAL_ERROR;
424  }
425 
426  if (munged_var)
427  free(munged_var);
428  munged_var = NULL;
429  if (url_var)
430  free(url_var);
431  url_var = NULL;
432  }
433 #ifdef DEBUG
434  fprintf(stderr, "Pass end compute_length=%d\n", compute_length);
435 #endif
436 
437  if (compute_length) {
438  compute_length = 0;
439  /* Do something about (re-)allocating the buffer. */
440  err = SPF_recalloc(bufp, buflenp, buflen);
441  if (err != SPF_E_SUCCESS)
442  return err;
443  p = *bufp;
444  p_end = *bufp + *buflenp;
445  goto top;
446  }
447 
448  *p++ = '\0';
449 
450  return SPF_E_SUCCESS;
451 }
unsigned short delim_bar
Definition: spf_record.h:125
#define SPF_ASSERT_NOTNULL(x)
Definition: spf_log.h:118
SPF_data_cidr_t dc
Definition: spf_record.h:145
#define PARM_CLIENT_IP
Definition: spf_record.h:92
unsigned char len
Definition: spf_record.h:107
unsigned short delim_under
Definition: spf_record.h:126
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)
Definition: spf_expand.c:81
#define PARM_REC_DOM
Definition: spf_record.h:98
#define PARM_ENV_FROM
Definition: spf_record.h:89
#define PARM_CLIENT_IP_P
Definition: spf_record.h:93
unsigned char parm_type
Definition: spf_record.h:132
unsigned short delim_dash
Definition: spf_record.h:122
unsigned char parm_type
Definition: spf_record.h:106
SPF_data_var_t dv
Definition: spf_record.h:143
unsigned char parm_type
Definition: spf_record.h:117
size_t len
Definition: memcmp.c:10
unsigned short delim_dot
Definition: spf_record.h:121
#define array_elem(x)
Definition: spf_internal.h:31
#define PARM_CLIENT_DOM
Definition: spf_record.h:95
#define SPF_error(errmsg)
Definition: spf_log.h:40
SPF_errcode_t
Definition: spf_response.h:118
unsigned short delim_equal
Definition: spf_record.h:124
const char * SPF_request_get_client_dom(SPF_request_t *sr)
Definition: spf_request.c:196
unsigned short rev
Definition: spf_record.h:119
unsigned char num_rhs
Definition: spf_record.h:118
#define NULL
Definition: spf_internal.h:28
#define PARM_CLIENT_VER
Definition: spf_record.h:96
#define PARM_CUR_DOM
Definition: spf_record.h:91
const char * SPF_request_get_rec_dom(SPF_request_t *sr)
Definition: spf_request.c:131
#define PARM_DP_FROM
Definition: spf_record.h:90
SPF_data_str_t ds
Definition: spf_record.h:144
#define PARM_HELO_DOM
Definition: spf_record.h:97
unsigned short delim_plus
Definition: spf_record.h:123
#define PARM_CIDR
Definition: spf_record.h:99
int top
#define PARM_TIME
Definition: spf_record.h:94
#define PARM_STRING
Definition: spf_record.h:100
unsigned short url_encode
Definition: spf_record.h:120
#define PARM_LP_FROM
Definition: spf_record.h:88
SPF_errcode_t SPF_recalloc(char **bufp, size_t *buflenp, size_t buflen) __attribute__((warn_unused_result))
Definition: spf_utils.c:188