From: Jeremy Harris Date: Wed, 4 Nov 2015 18:20:04 +0000 (+0000) Subject: Expansions: New ${ipv6denorm:} and ${ipv6norm:} operators. Bug 1650 X-Git-Tag: exim-4_87_RC1~52 X-Git-Url: https://git.exim.org/exim.git/commitdiff_plain/fc4a7f705d4c5911220cf9dbb0995d9f5dbabf02?hp=6636495c105ec7c4954585c1b317e049c6cac002 Expansions: New ${ipv6denorm:} and ${ipv6norm:} operators. Bug 1650 --- diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 101df6b90..54480ee8d 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -10107,6 +10107,27 @@ as is, and other byte values are converted to &`\xNN`&, for example a byte value 127 is converted to &`\x7f`&. +.new +.vitem &*${ipv6denorm:*&<&'string'&>&*}*& +.cindex "&%ipv6denorm%& expansion item" +.cindex "IP address" normalisation +This expands an IPv6 address to a full eight-element colon-separated set +of hex digits including leading zeroes. +A trailing ipv4-style dotted-decimal set is converted to hex. +Pure IPv4 addresses are converted to IPv4-mapped IPv6. + +.vitem &*${ipv6norm:*&<&'string'&>&*}*& +.cindex "&%ipv6norm%& expansion item" +.cindex "IP address" normalisation +.cindex "IP address" "canonical form" +This converts an IPv6 address to canonical form. +Leading zeroes of groups are omitted, and the longest +set of zero-valued groups is replaced with a double colon. +A trailing ipv4-style dotted-decimal set is converted to hex. +Pure IPv4 addresses are converted to IPv4-mapped IPv6. +.wen + + .vitem &*${lc:*&<&'string'&>&*}*& .cindex "case forcing in strings" .cindex "string" "case forcing" diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index a7185a4e2..ca65c98ea 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -17,6 +17,13 @@ Version 4.87 3. Transports now take a "max_parallel" option, to limit concurrency. + 4. Expansion operators ${ipv6norm:} and ${ipv6denorm:}. + The latter expands to a 8-element colon-sep set of hex digits including + leading zeroes. A trailing ipv4-style dotted-decimal set is converted + to hex. Pure ipv4 addresses are converted to IPv4-mapped IPv6. + The former operator strips leading zeroes and collapses the longest + set of 0-groups to a double-colon. + Version 4.86 ------------ diff --git a/src/src/expand.c b/src/src/expand.c index fbbc68154..90ffe78c0 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -210,6 +210,8 @@ static uschar *op_table_main[] = { US"hash", US"hex2b64", US"hexquote", + US"ipv6denorm", + US"ipv6norm", US"l", US"lc", US"length", @@ -248,6 +250,8 @@ enum { EOP_HASH, EOP_HEX2B64, EOP_HEXQUOTE, + EOP_IPV6DENORM, + EOP_IPV6NORM, EOP_L, EOP_LC, EOP_LENGTH, @@ -6406,6 +6410,39 @@ while (*s != 0) continue; } + case EOP_IPV6NORM: + case EOP_IPV6DENORM: + { + int type = string_is_ip_address(sub, NULL); + int binary[4]; + uschar buffer[44]; + + switch (type) + { + case 6: + (void) host_aton(sub, binary); + break; + + case 4: /* convert to IPv4-mapped IPv6 */ + binary[0] = binary[1] = 0; + binary[2] = 0x0000ffff; + (void) host_aton(sub, binary+3); + break; + + case 0: + expand_string_message = + string_sprintf("\"%s\" is not an IP address", sub); + goto EXPAND_FAILED; + } + + yield = string_cat(yield, &size, &ptr, buffer, + c == EOP_IPV6NORM + ? ipv6_nmtoa(binary, buffer) + : host_nmtoa(4, binary, -1, buffer, ':') + ); + continue; + } + case EOP_ADDRESS: case EOP_LOCAL_PART: case EOP_DOMAIN: diff --git a/src/src/functions.h b/src/src/functions.h index 2fe681394..9df8030f5 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -233,6 +233,8 @@ extern int ip_tcpsocket(const uschar *, uschar **, int); extern int ip_unixsocket(const uschar *, uschar **); extern int ip_streamsocket(const uschar *, uschar **, int); +extern int ipv6_nmtoa(int *, uschar *); + extern uschar *local_part_quote(uschar *); extern int log_create(uschar *); extern int log_create_as_exim(uschar *); diff --git a/src/src/host.c b/src/src/host.c index 429b6352c..67d33a9b8 100644 --- a/src/src/host.c +++ b/src/src/host.c @@ -1146,20 +1146,14 @@ if (count == 1) { j = binary[0]; for (i = 24; i >= 0; i -= 8) - { - sprintf(CS tt, "%d.", (j >> i) & 255); - while (*tt) tt++; - } + tt += sprintf(CS tt, "%d.", (j >> i) & 255); } else - { for (i = 0; i < 4; i++) { j = binary[i]; - sprintf(CS tt, "%04x%c%04x%c", (j >> 16) & 0xffff, sep, j & 0xffff, sep); - while (*tt) tt++; + tt += sprintf(CS tt, "%04x%c%04x%c", (j >> 16) & 0xffff, sep, j & 0xffff, sep); } - } tt--; /* lose final separator */ @@ -1175,6 +1169,63 @@ return tt - buffer; } +/* Like host_nmtoa() but: ipv6-only, canonical output, no mask + +Arguments: + binary points to the ints + buffer big enough to hold the result + +Returns: the number of characters placed in buffer, not counting + the final nul. +*/ + +int +ipv6_nmtoa(int * binary, uschar * buffer) +{ +int i, j, k; +uschar * c = buffer; +uschar * d; + +for (i = 0; i < 4; i++) + { /* expand to text */ + j = binary[i]; + c += sprintf(CS c, "%x:%x:", (j >> 16) & 0xffff, j & 0xffff); + } + +for (c = buffer, k = -1, i = 0; i < 8; i++) + { /* find longest 0-group sequence */ + if (*c == '0') /* must be "0:" */ + { + uschar * s = c; + j = i; + while (c[2] == '0') i++, c += 2; + if (i-j > k) + { + k = i-j; /* length of sequence */ + d = s; /* start of sequence */ + } + } + while (*++c != ':') ; + c++; + } + +c[-1] = '\0'; /* drop trailing colon */ + +/* debug_printf("%s: D k %d <%s> <%s>\n", __FUNCTION__, k, d, d + 2*(k+1)); */ +if (k >= 0) + { /* collapse */ + c = d + 2*(k+1); + if (d == buffer) c--; /* need extra colon */ + *d++ = ':'; /* 1st 0 */ + while (*d++ = *c++) ; + } +else + d = c; + +return d - buffer; +} + + /************************************************* * Check port for tls_on_connect * diff --git a/test/scripts/0000-Basic/0002 b/test/scripts/0000-Basic/0002 index 9afb556a3..bf2c1068c 100644 --- a/test/scripts/0000-Basic/0002 +++ b/test/scripts/0000-Basic/0002 @@ -208,6 +208,17 @@ mask: ${mask:192.168.10.206/33} mask: ${mask:192.168.10.206/0} mask: ${mask:192.168.10.206} mask: ${mask:a.b.c.d} +ipv6denorm: ${ipv6denorm:::1} +ipv6denorm: ${ipv6denorm:fe00::1} +ipv6denorm: ${ipv6denorm:192.168.0.1} +ipv6denorm: ${ipv6denorm:fe80::192.168.0.1} +ipv6norm: ${ipv6norm:0:0:0::1} +ipv6norm: ${ipv6norm:2a00::0} +ipv6norm: ${ipv6norm:2a00::1} +ipv6norm: ${ipv6norm:2a00:eadf:0000:0000:0000:0000:0001:0000} +ipv6norm: ${ipv6norm:2a00:eadf:0000:0001:0000:0000:0000:0000} +ipv6norm: ${ipv6norm:2a00:0:0:0::} +ipv6norm: ${ipv6norm:2a00:2:3:4:5:6:7:8} nhash: ${nhash_24:monty} ${nhash_8_63:monty python} lc/uc: ${lc:The Quick} ${uc: Brown Fox} length: ${length_10:The quick brown fox} ${l_10:abc} diff --git a/test/stdout/0002 b/test/stdout/0002 index 9a3219d59..3018dce1f 100644 --- a/test/stdout/0002 +++ b/test/stdout/0002 @@ -189,6 +189,17 @@ > mask: 0.0.0.0/0 > Failed: missing mask value in "192.168.10.206" > Failed: "a.b.c.d" is not an IP address +> ipv6denorm: 0000:0000:0000:0000:0000:0000:0000:0001 +> ipv6denorm: fe00:0000:0000:0000:0000:0000:0000:0001 +> ipv6denorm: 0000:0000:0000:0000:0000:ffff:c0a8:0001 +> ipv6denorm: fe80:0000:0000:0000:0000:0000:c0a8:0001 +> ipv6norm: ::1 +> ipv6norm: 2a00:: +> ipv6norm: 2a00::1 +> ipv6norm: 2a00:eadf::1:0 +> ipv6norm: 2a00:eadf:0:1:: +> ipv6norm: 2a00:: +> ipv6norm: 2a00:2:3:4:5:6:7:8 > nhash: 19 0/61 > lc/uc: the quick BROWN FOX > length: The quick abc