Home | History | Annotate | Line # | Download | only in global
      1 /*	$NetBSD: normalize_mailhost_addr.c,v 1.4 2025/02/25 19:15:45 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	normalize_mailhost_addr 3
      6 /* SUMMARY
      7 /*	normalize mailhost address string representation
      8 /* SYNOPSIS
      9 /*	#include <normalize_mailhost_addr.h>
     10 /*
     11 /*	int	normalize_mailhost_addr(
     12 /*	const char *string,
     13 /*	char	**mailhost_addr,
     14 /*	char	**bare_addr,
     15 /*	int	*addr_family)
     16 /* DESCRIPTION
     17 /*	normalize_mailhost_addr() takes the RFC 2821 string
     18 /*	representation of an IPv4 or IPv6 network address, and
     19 /*	normalizes the "IPv6:" prefix and numeric form. An IPv6 or
     20 /*	IPv4 form is rejected if support for that protocol is
     21 /*	disabled or non-existent. If both IPv6 and IPv4 support are
     22 /*	enabled, a V4-in-V6 address is replaced with the IPv4 form.
     23 /*
     24 /*	Arguments:
     25 /* .IP string
     26 /*	Null-terminated string with the RFC 2821 string representation
     27 /*	of an IPv4 or IPv6 network address.
     28 /* .IP mailhost_addr
     29 /*	Null pointer, or pointer to null-terminated string with the
     30 /*	normalized RFC 2821 string representation of an IPv4 or
     31 /*	IPv6 network address. Storage must be freed with myfree().
     32 /* .IP bare_addr
     33 /*	Null pointer, or pointer to null-terminated string with the
     34 /*	numeric address without prefix, such as "IPv6:". Storage
     35 /*	must be freed with myfree().
     36 /* .IP addr_family
     37 /*	Null pointer, or pointer to integer for storing the address
     38 /*	family.
     39 /* DIAGNOSTICS
     40 /*	normalize_mailhost_addr() returns -1 if the input is malformed,
     41 /*	zero otherwise.
     42 /* LICENSE
     43 /* .ad
     44 /* .fi
     45 /*	The Secure Mailer license must be distributed with this software.
     46 /* AUTHOR(S)
     47 /*	Wietse Venema
     48 /*	Google, Inc.
     49 /*	111 8th Avenue
     50 /*	New York, NY 10011, USA
     51 /*--*/
     52 
     53  /*
     54   * System library.
     55   */
     56 #include <sys_defs.h>
     57 #include <string.h>
     58 
     59 #ifdef STRCASECMP_IN_STRINGS_H
     60 #include <strings.h>
     61 #endif
     62 
     63  /*
     64   * Utility library.
     65   */
     66 #include <inet_proto.h>
     67 #include <msg.h>
     68 #include <myaddrinfo.h>
     69 #include <mymalloc.h>
     70 #include <stringops.h>
     71 
     72  /*
     73   * Global library.
     74   */
     75 #include <normalize_mailhost_addr.h>
     76 #include <valid_mailhost_addr.h>
     77 
     78 /* normalize_mailhost_addr - parse and normalize mailhost IP address */
     79 
     80 int     normalize_mailhost_addr(const char *string, char **mailhost_addr,
     81 				        char **bare_addr, int *addr_family)
     82 {
     83     const char myname[] = "normalize_mailhost_addr";
     84     const INET_PROTO_INFO *proto_info = inet_proto_info();
     85     struct addrinfo *res = 0;
     86     MAI_HOSTADDR_STR hostaddr;
     87     const char *valid_addr;		/* IPv6:fc00::1 */
     88     const char *normal_addr;		/* 192.168.0.1 */
     89     int     normal_family;
     90 
     91 #define UPDATE_BARE_ADDR(s, v) do { \
     92 	if (s) myfree(s); \
     93 	(s) = mystrdup(v); \
     94     } while(0)
     95 #define UPDATE_MAILHOST_ADDR(s, prefix, addr) do { \
     96 	if (s) myfree(s); \
     97 	(s) = concatenate((prefix), (addr), (char *) 0); \
     98     } while (0)
     99 
    100     /*
    101      * Parse and normalize the input.
    102      */
    103     if ((valid_addr = valid_mailhost_addr(string, DONT_GRIPE)) == 0
    104 	|| hostaddr_to_sockaddr(valid_addr, (char *) 0, 0, &res) != 0
    105 	|| sockaddr_to_hostaddr(res->ai_addr, res->ai_addrlen,
    106 				&hostaddr, (MAI_SERVPORT_STR *) 0, 0) != 0) {
    107 	normal_addr = 0;
    108 #ifdef HAS_IPV6
    109     } else if (res->ai_family == AF_INET6
    110 	       && strncasecmp("::ffff:", hostaddr.buf, 7) == 0
    111 	       && strchr((char *) proto_info->sa_family_list, AF_INET)) {
    112 	normal_addr = hostaddr.buf + 7;
    113 	normal_family = AF_INET;
    114 #endif
    115     } else if (strchr((char *) proto_info->sa_family_list, res->ai_family)) {
    116 	normal_addr = hostaddr.buf;
    117 	normal_family = res->ai_family;
    118     } else {
    119 	normal_addr = 0;
    120     }
    121     if (res)
    122 	freeaddrinfo(res);
    123     if (normal_addr == 0)
    124 	return (-1);
    125 
    126     /*
    127      * Write the applicable outputs.
    128      */
    129     if (bare_addr) {
    130 	UPDATE_BARE_ADDR(*bare_addr, normal_addr);
    131 	if (msg_verbose)
    132 	    msg_info("%s: bare_addr=%s", myname, *bare_addr);
    133     }
    134     if (mailhost_addr) {
    135 #ifdef HAS_IPV6
    136 	if (normal_family == AF_INET6)
    137 	    UPDATE_MAILHOST_ADDR(*mailhost_addr, IPV6_COL, normal_addr);
    138 	else
    139 #endif
    140 	    UPDATE_BARE_ADDR(*mailhost_addr, normal_addr);
    141 	if (msg_verbose)
    142 	    msg_info("%s: mailhost_addr=%s", myname, *mailhost_addr);
    143     }
    144     if (addr_family) {
    145 	*addr_family = normal_family;
    146 	if (msg_verbose)
    147 	    msg_info("%s: addr_family=%s", myname,
    148 		     *addr_family == AF_INET6 ? "AF_INET6"
    149 		     : *addr_family == AF_INET ? "AF_INET"
    150 		     : "unknown");
    151     }
    152     return (0);
    153 }
    154 
    155  /*
    156   * Test program.
    157   */
    158 #ifdef TEST
    159 #include <stdlib.h>
    160 #include <mymalloc.h>
    161 #include <msg.h>
    162 
    163  /*
    164   * Main test program.
    165   */
    166 int     main(int argc, char **argv)
    167 {
    168     /* Test cases with inputs and expected outputs. */
    169     typedef struct TEST_CASE {
    170 	const char *inet_protocols;
    171 	const char *mailhost_addr;
    172 	int     exp_return;
    173 	const char *exp_mailhost_addr;
    174 	char   *exp_bare_addr;
    175 	int     exp_addr_family;
    176     } TEST_CASE;
    177     static TEST_CASE test_cases[] = {
    178 	/* IPv4 in IPv6. */
    179 	{"ipv4, ipv6", "ipv6:::ffff:1.2.3.4", 0, "1.2.3.4", "1.2.3.4", AF_INET},
    180 	{"ipv6", "ipv6:::ffff:1.2.3.4", 0, "IPv6:::ffff:1.2.3.4", "::ffff:1.2.3.4", AF_INET6},
    181 	/* Pass IPv4 or IPV6. */
    182 	{"ipv4, ipv6", "ipv6:fc00::1", 0, "IPv6:fc00::1", "fc00::1", AF_INET6},
    183 	{"ipv4, ipv6", "1.2.3.4", 0, "1.2.3.4", "1.2.3.4", AF_INET},
    184 	/* Normalize IPv4 or IPV6. */
    185 	{"ipv4, ipv6", "ipv6:fc00::0", 0, "IPv6:fc00::", "fc00::", AF_INET6},
    186 	{"ipv4, ipv6", "01.02.03.04", 0, "1.2.3.4", "1.2.3.4", AF_INET},
    187 	/* Suppress specific outputs. */
    188 	{"ipv4, ipv6", "ipv6:fc00::1", 0, 0, "fc00::1", AF_INET6},
    189 	{"ipv4, ipv6", "ipv6:fc00::1", 0, "IPv6:fc00::1", 0, AF_INET6},
    190 	{"ipv4, ipv6", "ipv6:fc00::1", 0, "IPv6:fc00::1", "fc00::1", -1},
    191 	/* Address type mismatch. */
    192 	{"ipv4, ipv6", "::ffff:1.2.3.4", -1},
    193 	{"ipv4", "ipv6:fc00::1", -1},
    194 	{"ipv6", "1.2.3.4", -1},
    195 	0,
    196     };
    197     TEST_CASE *test_case;
    198 
    199     /* Actual results. */
    200     int     act_return;
    201     char   *act_mailhost_addr = mystrdup("initial_mailhost_addr");
    202     char   *act_bare_addr = mystrdup("initial_bare_addr");
    203     int     act_addr_family = 0xdeadbeef;
    204 
    205     /* Findings. */
    206     int     tests_failed = 0;
    207     int     test_failed;
    208 
    209     for (tests_failed = 0, test_case = test_cases; test_case->inet_protocols;
    210 	 tests_failed += test_failed, test_case++) {
    211 	test_failed = 0;
    212 	inet_proto_init(argv[0], test_case->inet_protocols);
    213 	act_return =
    214 	    normalize_mailhost_addr(test_case->mailhost_addr,
    215 				    test_case->exp_mailhost_addr ?
    216 				    &act_mailhost_addr : (char **) 0,
    217 				    test_case->exp_bare_addr ?
    218 				    &act_bare_addr : (char **) 0,
    219 				    test_case->exp_addr_family >= 0 ?
    220 				    &act_addr_family : (int *) 0);
    221 	if (act_return != test_case->exp_return) {
    222 	    msg_warn("test case %d return expected=%d actual=%d",
    223 		     (int) (test_case - test_cases),
    224 		     test_case->exp_return, act_return);
    225 	    test_failed = 1;
    226 	    continue;
    227 	}
    228 	if (test_case->exp_return != 0)
    229 	    continue;
    230 	if (test_case->exp_mailhost_addr
    231 	    && strcmp(test_case->exp_mailhost_addr, act_mailhost_addr)) {
    232 	    msg_warn("test case %d mailhost_addr expected=%s actual=%s",
    233 		     (int) (test_case - test_cases),
    234 		     test_case->exp_mailhost_addr, act_mailhost_addr);
    235 	    test_failed = 1;
    236 	}
    237 	if (test_case->exp_bare_addr
    238 	    && strcmp(test_case->exp_bare_addr, act_bare_addr)) {
    239 	    msg_warn("test case %d bare_addr expected=%s actual=%s",
    240 		     (int) (test_case - test_cases),
    241 		     test_case->exp_bare_addr, act_bare_addr);
    242 	    test_failed = 1;
    243 	}
    244 	if (test_case->exp_addr_family >= 0
    245 	    && test_case->exp_addr_family != act_addr_family) {
    246 	    msg_warn("test case %d addr_family expected=0x%x actual=0x%x",
    247 		     (int) (test_case - test_cases),
    248 		     test_case->exp_addr_family, act_addr_family);
    249 	    test_failed = 1;
    250 	}
    251     }
    252     if (act_mailhost_addr)
    253 	myfree(act_mailhost_addr);
    254     if (act_bare_addr)
    255 	myfree(act_bare_addr);
    256     if (tests_failed)
    257 	msg_info("tests failed: %d", tests_failed);
    258     exit(tests_failed != 0);
    259 }
    260 
    261 #endif
    262