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