1 /* libspf - Sender Policy Framework library
2 *
3 * ANSI C implementation of spf-draft-200405.txt
4 *
5 * Author: James Couzens <jcouzens@codeshare.ca>
6 * Author: Sean Comeau <scomeau@obscurity.org>
7 *
8 * File: util.c
9 * Desc: Utility functions
10 *
11 * License:
12 *
13 * The libspf Software License, Version 1.0
14 *
15 * Copyright (c) 2004 James Couzens & Sean Comeau All rights
16 * reserved.
17 *
18 * Redistribution and use in source and binary forms, with or without
19 * modification, are permitted provided that the following conditions
20 * are met:
21 *
22 * 1. Redistributions of source code must retain the above copyright
23 * notice, this list of conditions and the following disclaimer.
24 *
25 * 2. Redistributions in binary form must reproduce the above copyright
26 * notice, this list of conditions and the following disclaimer in
27 * the documentation and/or other materials provided with the
28 * distribution.
29 *
30 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
31 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
33 * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS MAKING USE OF THIS LICESEN
34 * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
37 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
38 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
39 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
40 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
41 * SUCH DAMAGE.
42 *
43 */
44
45 #include "../../config.h" /* autoconf */
46
47 #include "spf.h" /* spf API */
48 #include "dns.h" /* dns functions */
49 #include "macro.h" /* our header */
50 #include "util.h" /* utility functions */
51
52
53 /* MACRO_expand
54 *
55 * Author: Sean Comeau <scomeau@obscurity.org>
56 * Author: James Couzens <jcouzens@codeshare.ca>
57 *
58 * Date: 01/18/04
59 * Updated: 06/29/04 Roger Moser <Roger.Moser@pamho.net>
60 *
61 * Desc:
62 * Walks a string of macros tokenizing and expanding them along the
63 * way, each token being inserted as a node into a linked list. Once the
64 * walk is complete the list is walked and the nodes are copied back out
65 * in the order they were added as expanded macros into a string which is
66 * then passed back to the calling function.
67 *
68 * MACRO_freebuf is removed and I moved the code inside of this function since
69 * before calling it, it was being walked anyway to copy out each string into
70 * the return buffer, so now it just free's directly after and then destructs.
71 *
72 */
MACRO_expand(peer_info_t * p,const char * s)73 char *MACRO_expand(peer_info_t *p, const char *s)
74 {
75 const char * const macro_p = "%"; /* % (literal) */
76 const char * const macro_d = "%20"; /* "URL" space */
77 const char * const macro_u = " "; /* whitespace */
78
79 char *buf = NULL; /* return buffer */
80 char *ptr = NULL; /* working pointer */
81 char *cp = NULL; /* working pointer */
82 char *macro = NULL;
83 char *s_macro = NULL; /* single char macro */
84
85 strbuf_t *master = NULL; /* list pointers */
86 strbuf_node_t *c_node = NULL; /* c_node node */
87 strbuf_node_t *kill_node = NULL; /* temp node used in destruct */
88
89 size_t len = 0; /* leng of passed string */
90 size_t i = 0; /* index on a str */
91 size_t length = 0; /* overall len of expanded str */
92
93
94 if (s == NULL)
95 {
96 xepprintf("Passed a NULL string. Abort!\n");
97 return(NULL);
98 }
99
100 len = strlen(s);
101 ptr = cp = xstrndup(s, (len + 1));
102
103 master = xmalloc(SIZEOF(strbuf_t));
104 master->head = NULL;
105 master->elements = 0;
106
107 while (*ptr)
108 {
109 /*
110 * This works by moving through the string and replacing non-essential
111 * elements with NULL chars until the character designating an expansion
112 * mechanism is found. The character is then sent off to MACRO_process
113 * for expansion
114 */
115 if (*ptr == '%') /* start of macro */
116 {
117 switch (*(ptr + 1))
118 {
119 case '%':
120 {
121 /* convert %% into % */
122 if (MACRO_addbuf(master, (char *)macro_p, 1) == SPF_FALSE)
123 {
124 xvprintf("Unable to allocate list node with (%c)!\n", macro_p);
125
126 return(NULL);
127 }
128
129 ptr += 2;
130 length++;
131
132 break;
133 } /* % */
134
135 case '_':
136 {
137 /* convert %_ to a white space */
138 if (MACRO_addbuf(master, (char *)macro_u, 1) == SPF_FALSE)
139 {
140 xvprintf("Unable to allocate list node with (%c)!\n", macro_u);
141
142 return(NULL);
143 }
144
145 ptr += 2;
146 length++;
147
148 break;
149 } /* - */
150
151 case '-':
152 {
153 /* convert %- into URL encoded '%20' */
154 if (MACRO_addbuf(master, (char *)macro_d, 3) == SPF_FALSE)
155 {
156 xvprintf("Unable to allocate list node with [%s]!\n", macro_d);
157
158 return(NULL);
159 }
160
161 ptr += 2;
162 length += 3;
163
164 break;
165 } /* _ */
166
167 case '{':
168 {
169 *ptr++ = '\0'; /* % */
170 *ptr++ = '\0'; /* { */
171
172 if ((i = UTIL_index(ptr, '}')) == 0)
173 {
174 xvprintf("'}' Invalid Macro (%c)\n", *(s + 1));
175
176 return(NULL); /* not closed, invalid macro */
177 }
178
179 *(ptr + i) = '\0'; /* } */
180
181 xvprintf("Actual macro [%s]\n", ptr);
182 if ((macro = MACRO_process(p, ptr, (i + 1))) == NULL)
183 {
184 xepprintf("macro process returned null!\n");
185 }
186 else
187 {
188 length += strlen(macro);
189 xvprintf("Macro expanded to: [%s] %i bytes\n", macro,
190 strlen(macro));
191
192 if (MACRO_addbuf(master, macro, strlen(macro)) == SPF_FALSE)
193 {
194 xvprintf("Unable to allocate list node with [%s]!\n", macro);
195 xfree(macro);
196
197 return(NULL); /* not closed, invalid macro */
198 }
199 xfree(macro);
200 }
201 ptr += i;
202
203 break;
204 } /* { */
205
206 default:
207 {
208 xvprintf("ERROR: Invalid macro. [%s] Abort!\n", *(ptr + 1));
209
210 /* need cleanup function call perhaps */
211 return(NULL);
212 } /* default */
213
214 } /* switch */
215 } /* if */
216 else
217 {
218 if ((i = UTIL_index(ptr, '%')) == 0)
219 {
220 while (*(ptr + i))
221 {
222 i++;
223 }
224 s_macro = xmalloc(i + 1);
225 memcpy(s_macro, ptr, (i + 1));
226 }
227 else
228 {
229 s_macro = xmalloc(i + 1);
230 memcpy(s_macro, ptr, i);
231 }
232
233 length += i;
234
235 if (MACRO_addbuf(master, s_macro, (i + 1)) == SPF_FALSE)
236 {
237 xvprintf("Unable to allocate list node with [%s]!\n", s_macro);
238
239 return(NULL);
240 }
241
242 ptr += (i - 1);
243 xvprintf("Freeing s_macro temp buf [%s]\n", s_macro);
244 xfree(s_macro);
245 }
246 ptr++;
247 xvprintf("Remaining buffer [%s]\n", ptr);
248 } /* while */
249
250 xprintf("Allocated %i bytes for return buf\n", length);
251 buf = xmalloc(length + 1);
252
253 c_node = master->head;
254 while (c_node != NULL)
255 {
256 kill_node = c_node;
257
258 if (kill_node->len > 1)
259 {
260 xvprintf("NODE: [%s] LEN: %i\n", kill_node->s, kill_node->len);
261 }
262 else
263 {
264 xvprintf("NODE: (%c) LEN: %i\n", kill_node->s, kill_node->len);
265 }
266
267 strncat(buf, kill_node->s, kill_node->len);
268 xfree(kill_node->s);
269 c_node = c_node->next;
270 xfree(kill_node);
271 }
272
273 xfree(cp);
274 xfree(master);
275
276 xvprintf("Returning expanded macro: [%s]\n", buf);
277
278 return(buf);
279 }
280
281
282 /* MACRO_process
283 *
284 * Author: James Couzens <jcouzens@codeshare.ca>
285 *
286 * Date: 01/21/04
287 *
288 * Desc:
289 * This function takes a NULL terminated string containing only one
290 * macro. It returns a new string containing whatever that macro expanded to.
291 * Returns NULL if the macro can not be expanded.
292 *
293 * exists:%{ir}.%{l1r+-}._spf.%{d}
294 *
295 * Date: 08/??/04 - Travis Anderson <tanderson@codeshare.ca>
296 *
297 * Desc:
298 * Fixed problematic 'p' macro with the addition of new DNS function
299 * DNS_check_client_reverse.
300 *
301 * Date: 09/11/04 - James Couzens <jcouzens@codeshare.ca>
302 *
303 * Desc:
304 * Removed strlen calls originally intended for replacement
305 * with either a generic limit (SPF_MAX_STR), or a more stringent limit safely
306 * imposed upon the malloc.
307 *
308 *
309 */
MACRO_process(peer_info_t * p,char * macro,const size_t size)310 char *MACRO_process(peer_info_t *p, char *macro, const size_t size)
311 {
312 int c = 0; /* stores a lower case version of the macro if necessary */
313
314 size_t i = 0; /* utility for string lengths */
315
316 char *rev_addr = NULL; /* used by p macro */
317
318
319 if (macro == NULL)
320 {
321 xepprintf("Passed a NULL string. Abort!\n");
322
323 return(SPF_FALSE);
324 }
325
326 xprintf("called with [%s] and len: %i\n", macro, size);
327
328 rev_addr = NULL;
329 i = 0;
330
331 if (isupper(*macro))
332 {
333 c = tolower(*macro);
334 }
335 else
336 {
337 c = *macro;
338 }
339
340 switch (c)
341 {
342 /* current-domain */
343 case 'd':
344 {
345 if (*(macro + 1))
346 {
347 return(MACRO_eatmore(macro, p->current_domain));
348 }
349 else
350 {
351 xvprintf("macro 'd' expands to: [%s]\n", p->current_domain);
352
353 return(xstrndup(p->current_domain, SPF_MAX_STR));
354 }
355 } /* 'd' */
356
357 /* HELO/EHLO domain */
358 case 'h':
359 {
360 if (*(macro + 1))
361 {
362 return(MACRO_eatmore(macro, p->helo));
363 }
364 else
365 {
366 xvprintf("macro 'h' expands to: [%s]\n", p->helo);
367
368 if (p->helo != NULL)
369 {
370 return(xstrndup(p->helo, SPF_MAX_ENV_HELO));
371 }
372 else
373 {
374 return(xstrndup(p->ehlo, SPF_MAX_ENV_HELO));
375 }
376 }
377 } /* 'h' */
378
379 /* SMTP client IP (nibble format when an IPv6 address) */
380 case 'i':
381 {
382 if (*(macro + 1))
383 {
384 return(MACRO_eatmore(macro, p->r_ip));
385 }
386 else
387 {
388 xvprintf("macro 'i' expands to: [%s]\n", p->r_ip);
389
390 return(xstrndup(p->r_ip, SPF_MAX_IP_ADDR));
391 }
392 } /* 'i' */
393
394 /* local-part of responsible-sender */
395 case 'l':
396 {
397 if (*(macro + 1))
398 {
399 return(MACRO_eatmore(macro, p->local_part));
400 }
401 else
402 {
403 xvprintf("macro 'l' expands to: [%s]\n", p->local_part);
404
405 return(xstrndup(p->local_part, SPF_MAX_LOCAL_PART));
406 }
407 } /* 'l' */
408
409 /* responsible domain */
410 case 'o':
411 /*
412 * Comment by: James Couzens <jcouzens@codeshare.ca>
413 * Date: 01/02/05
414 *
415 * Michael Elliott <elliott@rod.msen.com> correctly pointed out to me that
416 * this is failing because during recursion it points to the current
417 * domain instead of as per RFC requirements, that it do not change during
418 * any form of recursion. p->original_domain was added to deal with this
419 * properly.
420 */
421 {
422 if (*(macro + 1))
423 {
424 return(MACRO_eatmore(macro, p->original_domain));
425 }
426 else
427 {
428 xvprintf("macro 'o' expands to: [%s]\n", p->original_domain);
429
430 return(xstrndup(p->original_domain, SPF_MAX_STR));
431 }
432 } /* 'o' */
433
434 /* SMTP client domain name */
435 case 'p':
436 {
437 if (DNS_check_client_reverse(p) == SPF_FALSE)
438 {
439 p->r_vhname = xmalloc(8);
440 snprintf(p->r_vhname, 8, "unknown");
441 }
442
443 if (*(macro + 1))
444 {
445 xvprintf("macro '%c' expands to: [%s]\n", c, p->r_vhname);
446
447 return(MACRO_eatmore(macro, p->r_vhname));
448 }
449 else
450 {
451 xvprintf("macro '%c' expands to: [%s]\n", c, p->r_vhname);
452
453 return(xstrndup(p->r_vhname, SPF_MAX_STR));
454 }
455 } /* 'p' */
456
457 /* responsible sender*/
458 case 's':
459 {
460 if ((p->cur_eaddr != NULL) || p->cur_eaddr)
461 {
462 xfree(p->cur_eaddr);
463 }
464
465 xprintf("local-part: [%s]; current domain: [%s]\n",
466 p->local_part, p->original_domain);
467
468 i = ((strlen(p->local_part) + strlen(p->original_domain) + 2));
469 p->cur_eaddr = xmalloc(i);
470
471 snprintf(p->cur_eaddr, i, "%s@%s", p->local_part, p->original_domain);
472
473 if (*(macro + 1))
474 {
475 return(MACRO_eatmore(macro, p->cur_eaddr));
476 }
477 else
478 {
479 xvprintf("macro 's' expands to: [%s]\n", p->cur_eaddr);
480
481 return(xstrndup(p->cur_eaddr, SPF_MAX_STR));
482 }
483 } /* 's' */
484
485 /* current timestamp in UTC epoch seconds notation */
486 case 't':
487 {
488 if (*(macro + 1))
489 {
490 return(MACRO_eatmore(macro, p->utc_time));
491 }
492 else
493 {
494 xvprintf("macro 't' expands to: [%s]\n", p->utc_time);
495
496 return(xstrndup(p->utc_time, SPF_MAX_UTC_TIME));
497 }
498 } /* 't' */
499
500 /* client IP version string: "in-addr" for ipv4 or "ip6" for ipv6 */
501 case 'v':
502 {
503 if (*(macro + 1))
504 {
505 return(MACRO_eatmore(macro, p->ip_ver));
506 }
507 else
508 {
509 xvprintf("macro 'v' expands to: [%s]\n", p->ip_ver);
510 return(xstrndup(p->ip_ver, SPF_MAX_IP_ADDR));
511 }
512 } /* 'v' */
513
514 /* sekret */
515 case 'x':
516 {
517 if (size > 1)
518 {
519 if (*(macro + 1) == 'R' || *(macro + 1) == 'r')
520 {
521 return(xstrndup(p->mta_hname, SPF_MAX_HNAME));
522 }
523 }
524 break;
525 } /* 'x' */
526
527 default:
528 {
529 return(xstrndup(macro, SPF_MAX_STR));
530 } /* default */
531
532 } /* switch */
533
534 return(NULL);
535 }
536
537
538 /* MACRO_eatmore
539 *
540 * Author: James Couzens <jcouzens@codeshare.ca>
541 *
542 * Date: 01/22/04
543 * Date: 07/02/04 James Couzens <jcouzens@codeshare.ca> (compiler warnings)
544 *
545 * Desc:
546 * This function is called whenever a macro is more than one character
547 * long and so we have to 'eatmore' ;-) macro is the unexpanded macro and
548 * s is the expanded string of the original macro would have been returned as
549 * had it been single. Returns the expanded macro using dynamically allocated
550 * memory upon success and in the process will FREE s after allocating more
551 * memory (if necessary) and copying it over. Returns NULL upon failure.
552 *
553 */
MACRO_eatmore(char * macro,char * s)554 char *MACRO_eatmore(char *macro, char *s)
555 {
556 size_t i = 0; /* how much of the buffer we are using */
557
558 char delim = '\0'; /* marco is a delimiter modifier */
559
560 char *cp = NULL; /* working pointer */
561 char *buf = NULL; /* return buffer */
562 char *rev_str = NULL; /* temporary buffer */
563 char *d_buf = NULL; /* temporary buffer */
564
565 u_int8_t n_dot = 0; /* number of 'delimiter' items in a string */
566 u_int8_t rev = 0; /* string to be reversed */
567 u_int8_t digit = 0; /* macro is a digit modifier */
568
569
570 if (macro == NULL)
571 {
572 xepprintf("Passed a NULL string. Abort!\n");
573
574 return(NULL);
575 }
576
577 xprintf("Called with macro [%s] and string [%s]\n", macro, s);
578
579 /* need a proper value for this not just SPF_MAX_MACRO need to know the MAXIMUM
580 * expandable length of a macro. */
581
582 cp = macro;
583 delim = '.';
584
585 while (*cp)
586 {
587 if (isdigit(*cp))
588 {
589 digit = atoi(cp);
590 }
591 else if (UTIL_is_spf_delim(*cp) == SPF_TRUE)
592 {
593 delim = *cp;
594 }
595 else if ((*cp == 'r') || (*cp == 'R'))
596 {
597 rev = 1;
598 }
599 cp++;
600 }
601
602 xvprintf("mac:[%s] r:(%i) dig:(%i) dlm: (%c)\n",
603 macro, rev, digit, delim);
604
605 i = 0;
606 /* reverse the string */
607 if (rev == 1)
608 {
609 /*delim = '.';*/
610 rev_str = UTIL_reverse(s, delim);
611 s = NULL;
612 }
613
614 if (s == NULL)
615 {
616 cp = rev_str;
617 }
618 else
619 {
620 cp = s;
621 }
622
623 /* exercise digit modifier on string */
624 if (digit > 0)
625 {
626 n_dot = UTIL_count_delim(cp, delim);
627
628 if (digit > n_dot)
629 {
630 digit = n_dot;
631 }
632
633 if ((d_buf = UTIL_split_strr(cp, delim, digit)) != NULL)
634 {
635 i = strlen(d_buf);
636 }
637 else
638 {
639 d_buf = cp;
640 i = strlen(d_buf);
641 }
642
643 buf = xmalloc(i + 1);
644 memcpy(buf, d_buf, (i + 1));
645
646 if (d_buf != cp)
647 {
648 xfree(d_buf);
649 }
650 }
651 else if (rev == 1)
652 {
653 buf = xstrndup(rev_str, SPF_MAX_MACRO);
654 }
655
656 xvprintf("Returning [%s] (%i bytes)\n", buf, strlen(buf));
657
658 if (rev == 1)
659 {
660 xfree(rev_str);
661 }
662
663 return(buf);
664 }
665
666
667 /* MACRO_addbuf
668 *
669 * Author: Sean Comeau <scomeau@obscurity.org>
670 * Author: James Couznes <jcouzens@codeshare.ca>
671 *
672 * Date: 01/18/04
673 * Updated: 01/24/04
674 *
675 *
676 * Desc:
677 * Appends nodes to a master list which is passed of type
678 * strbuf_t. The nodes are of type strbuf_node_t and appended on
679 * the end and the list is reordered to reflect this. Returns
680 * SPF_TRUE upon success and SPF_FALSE
681 * upon failure.
682 *
683 */
MACRO_addbuf(strbuf_t * master,char * s,size_t size)684 SPF_BOOL MACRO_addbuf(strbuf_t *master, char *s, size_t size)
685 {
686 strbuf_node_t *c_node = NULL; /* c_node working node */
687 strbuf_node_t *new_node = NULL; /* newly allocated node */
688 strbuf_node_t *prev_node = NULL; /* previous working node */
689
690
691 if (s == NULL)
692 {
693 xepprintf("Passed a NULL string. Abort!\n");
694
695 return(SPF_FALSE);
696 }
697
698 xvprintf("Called with [%s] %i (%i) bytes.\n", s, size, strlen(s));
699
700 new_node = xmalloc(SIZEOF(strbuf_node_t));
701 new_node->s = xmalloc(size + 1);
702
703 strncpy(new_node->s, s, size);
704 new_node->len = size;
705 new_node->next = NULL;
706
707 xvprintf("Added [%s] to node of len: %i)\n", new_node->s,
708 new_node->len);
709
710 prev_node = NULL;
711 c_node = master->head;
712
713 /* reorder the list with the NEW element on the end */
714 while (c_node != NULL)
715 {
716 prev_node = c_node;
717 c_node = c_node->next;
718 }
719
720 if (prev_node != NULL)
721 {
722 new_node->next = prev_node->next;
723 prev_node->next = new_node;
724 }
725 else
726 {
727 master->head = new_node;
728 }
729
730 master->elements++;
731
732 return(SPF_TRUE);
733 }
734
735 /* end macro.c */
736
737