Expansions: mask_n operator
authorJeremy Harris <jgh146exb@wizmail.org>
Sun, 8 Aug 2021 16:34:49 +0000 (17:34 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Sun, 8 Aug 2021 16:34:49 +0000 (17:34 +0100)
doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
src/src/expand.c
test/scripts/0000-Basic/0002
test/stdout/0002

index 0385de6dbc5b96b8c907f366ce99c3b5c27604e9..e766b69e2d1e193a87f4d1b0bcc09c43e7210098 100644 (file)
@@ -11228,7 +11228,8 @@ empty.
 The parsing correctly handles SMTPUTF8 Unicode in the string.
 
 
-.vitem &*${mask:*&<&'IP&~address'&>&*/*&<&'bit&~count'&>&*}*&
+.vitem &*${mask:*&<&'IP&~address'&>&*/*&<&'bit&~count'&>&*}*& &&&
+       &*${mask_n:*&<&'IP&~address'&>&*/*&<&'bit&~count'&>&*}*&
 .cindex "masked IP address"
 .cindex "IP address" "masking"
 .cindex "CIDR notation"
@@ -11242,8 +11243,14 @@ the result back to text, with mask appended. For example,
 .code
 ${mask:10.111.131.206/28}
 .endd
-returns the string &"10.111.131.192/28"&. Since this operation is expected to
-be mostly used for looking up masked addresses in files, the result for an IPv6
+returns the string &"10.111.131.192/28"&.
+
+Since this operation is expected to
+be mostly used for looking up masked addresses in files, the
+.new
+normal
+.wen
+result for an IPv6
 address uses dots to separate components instead of colons, because colon
 terminates a key string in lsearch files. So, for example,
 .code
@@ -11253,6 +11260,10 @@ returns the string
 .code
 3ffe.ffff.836f.0a00.000a.0800.2000.0000/99
 .endd
+.new
+If the optional form &*mask_n*& is used, IPv6 address result are instead
+returned in normailsed form, using colons and with zero-compression.
+.wen
 Letters in IPv6 addresses are always output in lower case.
 
 
index 478446b41f1c0ee859cf65af717ca6def5afb8c6..1d6190b294e512a516f05d3d1236c598d77fe9c8 100644 (file)
@@ -11,6 +11,8 @@ Version 4.96
 
  1. A new ACL condition: seen. Records/tests a timestamp against a key.
 
+ 2. A variant of the "mask" expansion operator to give normalised IPv6.
+
 
 Version 4.95
 ------------
index 4fb935528e49b1c6fe9ab3a959d0aa113bbc8336..83c0ad051f4daef1ea36e08d8db500922ed91bf7 100644 (file)
@@ -7333,11 +7333,11 @@ while (*s)
         int count;
         uschar *endptr;
         int binary[4];
-        int mask, maskoffset;
-        int type = string_is_ip_address(sub, &maskoffset);
+        int type, mask, maskoffset;
+       BOOL normalised;
         uschar buffer[64];
 
-        if (type == 0)
+        if ((type = string_is_ip_address(sub, &maskoffset)) == 0)
           {
           expand_string_message = string_sprintf("\"%s\" is not an IP address",
            sub);
@@ -7353,13 +7353,18 @@ while (*s)
 
         mask = Ustrtol(sub + maskoffset + 1, &endptr, 10);
 
-        if (*endptr != 0 || mask < 0 || mask > ((type == 4)? 32 : 128))
+        if (*endptr || mask < 0 || mask > (type == 4 ? 32 : 128))
           {
           expand_string_message = string_sprintf("mask value too big in \"%s\"",
             sub);
           goto EXPAND_FAILED;
           }
 
+       /* If an optional 'n' was given, ipv6 gets normalised output:
+       colons rather than dots, and zero-compressed. */
+
+       normalised = arg && *arg == 'n';
+
         /* Convert the address to binary integer(s) and apply the mask */
 
         sub[maskoffset] = 0;
@@ -7368,8 +7373,14 @@ while (*s)
 
         /* Convert to masked textual format and add to output. */
 
-        yield = string_catn(yield, buffer,
-          host_nmtoa(count, binary, mask, buffer, '.'));
+       if (type == 4 || !normalised)
+         yield = string_catn(yield, buffer,
+           host_nmtoa(count, binary, mask, buffer, '.'));
+       else
+         {
+         ipv6_nmtoa(binary, buffer);
+         yield = string_fmt_append(yield, "%s/%d", buffer, mask);
+         }
         continue;
         }
 
index cc289e04eeb9618b960cbb84bf97cec9f9899e5c..db3eae6be4f80dc967f81a2bf50ef0fdfb03cacc 100644 (file)
@@ -248,6 +248,8 @@ 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}
+mask:   ${mask:2a00:2:3:4:5:6:7:8/79}
+mask_n: ${mask_n:2a00:2:3:4:5:6:7:8/79}
 ipv6denorm: ${ipv6denorm:::1}
 ipv6denorm: ${ipv6denorm:fe00::1}
 ipv6denorm: ${ipv6denorm:192.168.0.1}
index 0b9a95cd5fa566b4a6982a2d83449efb68a20175..5c8c2520c3f4eb298df4fa2b13756e400fd31733 100644 (file)
@@ -230,6 +230,8 @@ newline     tab\134backslash ~tilde\177DEL\200\201.
 > 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
+> mask:   2a00.0002.0003.0004.0004.0000.0000.0000/79
+> mask_n: 2a00:2:3:4:4::/79
 > 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