mysql_table(5), pgsql_table(5) and sqlite_table(5),
virtual_alias_domains, virtual_mailbox_domains.
-20140111
+20150111
Cleanup: simplified the interposition layer that adds UTF-8
support to Postfix lookup tables. Files: util/dict_utf8.c.
smtpd_tls_session_cache_database, transport_maps,
virtual_alias_maps, virtual_gid_maps, virtual_mailbox_maps,
virtual_uid_maps.
+
+20150112
+
+ Support for UTF-8 casefolding in match_lists. Instead of
+ using strcasecmp(), casefold all string constant patterns
+ during initialization, and casefold a search string at the
+ beginning of the search, and use strcmp() for comparison.
+ Files: util/casefold.c util/dict.h, util/dict_utf8.c,
+ util/match_list.c, util/match_list.h, util/match_ops.c,
+ util/stringops.h, global/addr_match_list.c, global/domain_list.c,
+ global/namadr_list.c, global/string_list.c.
transport_maps, etc., and when maintaining tables with the postmap(1) and
postalias(1) commands.
-N\bNo\bo c\bca\bas\bse\be-\b-i\bin\bns\bse\ben\bns\bsi\bit\bti\biv\bve\be m\bma\bat\btc\bch\bhi\bin\bng\bg o\bof\bf n\bno\bon\bn-\b-A\bAS\bSC\bCI\bII\bI s\bst\btr\bri\bin\bng\bg p\bpa\bat\btt\bte\ber\brn\bns\bs i\bin\bn m\bma\bat\btc\bch\bhl\bli\bis\bst\bts\bs.\b.
-
-Postfix currently does not yet implement case-insensitive string comparison for
-non-ASCII string patterns in list features such as mydestination,
-relay_domains, etc. For now, use "inline:{string}" instead of "string". This
-limitation will be removed before the stable release.
-
C\bCo\bom\bmp\bpa\bat\bti\bib\bbi\bil\bli\bit\bty\by w\bwi\bit\bth\bh p\bpr\bre\be-\b-S\bSM\bMT\bTP\bPU\bUT\bTF\bF8\b8 e\ben\bnv\bvi\bir\bro\bon\bnm\bme\ben\bnt\bts\bs
M\bMa\bai\bil\bli\bin\bng\bg l\bli\bis\bst\bts\bs w\bwi\bit\bth\bh U\bUT\bTF\bF-\b-8\b8 a\ban\bnd\bd n\bno\bon\bn-\b-U\bUT\bTF\bF-\b-8\b8 s\bsu\bub\bbs\bsc\bcr\bri\bib\bbe\ber\brs\bs
Things to do after the stable release:
+ Add context argument for better match_list diagnostics.
+
Expose UTF8 flag in server_acl API. Some applications such
as postscreen don't need UTF8 support.
maps, <a href="postconf.5.html#transport_maps">transport_maps</a>, etc., and when maintaining tables with the
<a href="postmap.1.html">postmap(1)</a> and <a href="postalias.1.html">postalias(1)</a> commands. </p>
-<h3> No case-insensitive matching of non-ASCII string patterns in matchlists. </h3>
-
-<p> Postfix currently does not yet implement case-insensitive string
-comparison for non-ASCII string patterns in list features such as
-<a href="postconf.5.html#mydestination">mydestination</a>, <a href="postconf.5.html#relay_domains">relay_domains</a>, etc. For now, use "<a href="DATABASE_README.html#types">inline</a>:{string}"
-instead of "string". This limitation will be removed before the
-stable release. </p>
-
<h2> <a name="compatibility">Compatibility with pre-SMTPUTF8
environments</a> </h2>
maps, transport_maps, etc., and when maintaining tables with the
postmap(1) and postalias(1) commands. </p>
-<h3> No case-insensitive matching of non-ASCII string patterns in matchlists. </h3>
-
-<p> Postfix currently does not yet implement case-insensitive string
-comparison for non-ASCII string patterns in list features such as
-mydestination, relay_domains, etc. For now, use "inline:{string}"
-instead of "string". This limitation will be removed before the
-stable release. </p>
-
<h2> <a name="compatibility">Compatibility with pre-SMTPUTF8
environments</a> </h2>
abounce.o: msg_stats.h
abounce.o: recipient_list.h
addr_match_list.o: ../../include/argv.h
+addr_match_list.o: ../../include/check_arg.h
addr_match_list.o: ../../include/match_list.h
addr_match_list.o: ../../include/sys_defs.h
+addr_match_list.o: ../../include/vbuf.h
+addr_match_list.o: ../../include/vstring.h
addr_match_list.o: addr_match_list.c
addr_match_list.o: addr_match_list.h
anvil_clnt.o: ../../include/attr.h
db_common.o: db_common.h
db_common.o: string_list.h
debug_peer.o: ../../include/argv.h
+debug_peer.o: ../../include/check_arg.h
debug_peer.o: ../../include/match_list.h
debug_peer.o: ../../include/msg.h
debug_peer.o: ../../include/sys_defs.h
+debug_peer.o: ../../include/vbuf.h
+debug_peer.o: ../../include/vstring.h
debug_peer.o: debug_peer.c
debug_peer.o: debug_peer.h
debug_peer.o: mail_params.h
dict_sqlite.o: dict_sqlite.h
dict_sqlite.o: string_list.h
domain_list.o: ../../include/argv.h
+domain_list.o: ../../include/check_arg.h
domain_list.o: ../../include/match_list.h
domain_list.o: ../../include/sys_defs.h
+domain_list.o: ../../include/vbuf.h
+domain_list.o: ../../include/vstring.h
domain_list.o: domain_list.c
domain_list.o: domain_list.h
dot_lockfile.o: ../../include/check_arg.h
mark_corrupt.o: msg_stats.h
mark_corrupt.o: recipient_list.h
match_parent_style.o: ../../include/argv.h
+match_parent_style.o: ../../include/check_arg.h
match_parent_style.o: ../../include/match_list.h
match_parent_style.o: ../../include/sys_defs.h
+match_parent_style.o: ../../include/vbuf.h
+match_parent_style.o: ../../include/vstring.h
match_parent_style.o: mail_params.h
match_parent_style.o: match_parent_style.c
match_parent_style.o: match_parent_style.h
mypwd.o: mypwd.c
mypwd.o: mypwd.h
namadr_list.o: ../../include/argv.h
+namadr_list.o: ../../include/check_arg.h
namadr_list.o: ../../include/match_list.h
namadr_list.o: ../../include/sys_defs.h
+namadr_list.o: ../../include/vbuf.h
+namadr_list.o: ../../include/vstring.h
namadr_list.o: namadr_list.c
namadr_list.o: namadr_list.h
off_cvt.o: ../../include/check_arg.h
stream2rec.o: record.h
stream2rec.o: stream2rec.c
string_list.o: ../../include/argv.h
+string_list.o: ../../include/check_arg.h
string_list.o: ../../include/match_list.h
string_list.o: ../../include/sys_defs.h
+string_list.o: ../../include/vbuf.h
+string_list.o: ../../include/vstring.h
string_list.o: string_list.c
string_list.o: string_list.h
strip_addr.o: ../../include/mymalloc.h
#include <vstream.h>
#include <vstring_vstream.h>
#include <msg_vstream.h>
+#include <stringops.h> /* util_utf8_enable */
static void usage(char *progname)
{
}
if (argc != optind + 2)
usage(argv[0]);
+ dict_allow_surrogate = 1;
+ util_utf8_enable = 1;
list = addr_match_list_init(MATCH_FLAG_PARENT | MATCH_FLAG_RETURN, argv[optind]);
addr = argv[optind + 1];
if (strcmp(addr, "-") == 0) {
#include <unistd.h>
#include <vstream.h>
#include <msg_vstream.h>
+#include <stringops.h> /* util_utf8_enable */
static void usage(char *progname)
{
}
if (argc != optind + 2)
usage(argv[0]);
+ dict_allow_surrogate = 1;
+ util_utf8_enable = 1;
list = domain_list_init(MATCH_FLAG_PARENT | MATCH_FLAG_RETURN, argv[optind]);
host = argv[optind + 1];
vstream_printf("%s: %s\n", host, domain_list_match(list, host) ?
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20150111"
+#define MAIL_RELEASE_DATE "20150112"
#define MAIL_VERSION_NUMBER "2.12"
#ifdef SNAPSHOT
#include <vstream.h>
#include <msg_vstream.h>
#include <dict.h>
+#include <stringops.h> /* util_utf8_enable */
static void usage(char *progname)
{
if (argc != optind + 3)
usage(argv[0]);
dict_allow_surrogate = 1;
+ util_utf8_enable = 1;
list = namadr_list_init(MATCH_FLAG_PARENT | MATCH_FLAG_RETURN, argv[optind]);
host = argv[optind + 1];
addr = argv[optind + 2];
#include <vstream.h>
#include <vstring.h>
#include <msg_vstream.h>
+#include <stringops.h> /* util_utf8_enable */
static void usage(char *progname)
{
}
if (argc != optind + 2)
usage(argv[0]);
+ dict_allow_surrogate = 1;
+ util_utf8_enable = 1;
list = string_list_init(MATCH_FLAG_RETURN, argv[optind]);
string = argv[optind + 1];
vstream_printf("%s: %s\n", string, string_list_match(list, string) ?
/* #include <stringops.h>
/*
/* char *casefold(
+/* int utf8_request,
/* VSTRING *src,
/* const char *src,
/* CONST_CHAR_STAR *err)
/* range), not even when running inside an empty chroot jail.
/*
/* Arguments:
+/* .IP utf8_request
+/* Perform UTF-8 case folding.
/* .IP src
/* Null-terminated input string.
/* .IP dest
/* casefold - casefold an UTF-8 string */
-char *casefold(VSTRING *dest, const char *src, CONST_CHAR_STAR *err)
+char *casefold(int utf8_req, VSTRING *dest, const char *src,
+ CONST_CHAR_STAR *err)
{
#ifdef NO_EAI
/*
* All-ASCII input.
*/
- if (allascii(src)) {
+ if (utf8_req == 0 || allascii(src)) {
vstring_strcpy(dest, src);
return (lowercase(STR(dest)));
}
char *conv_res;
const char *fold_err;
char *cmd;
- int codepoint, first, last;
+ int codepoint, first, last, utf8_req;
if (setlocale(LC_ALL, "C") == 0)
msg_fatal("setlocale(LC_ALL, C) failed: %m");
msg_vstream_init(argv[0], VSTREAM_ERR);
- util_utf8_enable = 1;
+ utf8_req = util_utf8_enable = 1;
VSTRING_SPACE(buffer, 256); /* chroot pathname */
* Null-terminated string.
*/
if (strcmp(cmd, "fold") == 0) {
- if ((conv_res = casefold(dest, bp, &fold_err)) != 0)
+ if ((conv_res = casefold(utf8_req, dest, bp, &fold_err)) != 0)
msg_info("\"%s\" ->fold \"%s\"", bp, conv_res);
else
msg_warn("cannot casefold \"%s\": %s", bp, fold_err);
msg_info("U+%X -> %s", codepoint, STR(buffer));
if (valid_utf8_string(STR(buffer), LEN(buffer)) == 0)
msg_fatal("bad utf-8 encoding for U+%X", codepoint);
- if (casefold(dest, STR(buffer), &fold_err) == 0)
+ if (casefold(utf8_req, dest, STR(buffer), &fold_err) == 0)
msg_warn("casefold error for U+%X: %s",
codepoint, fold_err);
}
* This name is reserved for matchlist error handling.
*/
#define DICT_TYPE_NOFILE "non-existent"
+#define DICT_TYPE_NOUTF8 "non-UTF-8"
/*
* Duplicated from vstream(3). This should probably be abstracted out.
/* configuration error.
/*
/* The dict_utf8_check* functions may be invoked to perform
-/* UTF-8 validity checks when util_utf8_enable is non-zero and
-/* DICT_FLAG_UTF8_ENABLE is set. Otherwise both functions
-/* always report success.
+/* UTF-8 validity checks when util_utf8_enable is non-zero.
/*
/* dict_utf8_check_fold() optionally folds a string, and checks
/* it for UTF-8 validity. The result is the possibly-folded
DICT_FLAG_FOLD_FIX : DICT_FLAG_FOLD_MUL)) {
if (dict->fold_buf == 0)
dict->fold_buf = vstring_alloc(10);
- return (casefold(dict->fold_buf, string, err));
+ return (casefold(dict->flags & DICT_FLAG_UTF8_ACTIVE,
+ dict->fold_buf, string, err));
}
/*
DICT *dict_utf8_activate(DICT *dict)
{
+ const char myname[] = "dict_utf8_activate";
DICT_UTF8_BACKUP *backup;
/*
* Sanity check.
*/
+ if (util_utf8_enable == 0)
+ msg_panic("%s: Unicode support is not available", myname);
+ if ((dict->flags & DICT_FLAG_UTF8_REQUEST) == 0)
+ msg_panic("%s: %s:%s does not request Unicode support",
+ myname, dict->type, dict->name);
if (dict->flags & DICT_FLAG_UTF8_ACTIVE)
- msg_panic("dict_utf8_activate: %s:%s is already encapsulated",
- dict->type, dict->name);
+ msg_panic("%s: %s:%s Unicode support is already activated",
+ myname, dict->type, dict->name);
/*
* Unlike dict_debug(3) we do not put a proxy dict object in front of the
/* MATCH_LIST *list;
/* DESCRIPTION
/* This module implements a framework for tests for list membership.
-/* The actual tests are done by user-supplied functions.
+/* The actual tests are done by user-supplied functions. With
+/* util_utf8_enable non-zero, string comparison supports UTF-8.
/*
/* Patterns are separated by whitespace and/or commas. A pattern
/* is either a string, a file name (in which case the contents
/* match_list_parse - parse buffer, destroy buffer */
-static ARGV *match_list_parse(ARGV *list, char *string, int init_match)
+static ARGV *match_list_parse(MATCH_LIST *match_list, ARGV *pat_list,
+ char *string, int init_match)
{
const char *myname = "match_list_parse";
VSTRING *buf = vstring_alloc(10);
char *item;
char *map_type_name_flags;
int match;
+ const char *utf8_err;
+ /*
+ * No DICT_FLAG_FOLD_FIX here, because we casefold the search string at
+ * the beginning of a search. String constant patterns are casefolded
+ * during match_list initialization.
+ */
#define OPEN_FLAGS O_RDONLY
-#define DICT_FLAGS (DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX \
- | DICT_FLAG_UTF8_REQUEST)
+#define DICT_FLAGS (DICT_FLAG_LOCK | DICT_FLAG_UTF8_REQUEST)
#define STR(x) vstring_str(x)
/*
* /filename contents are expanded in-line. To support !/filename we
* prepend the negation operator to each item from the file.
+ *
+ * If there is an error, implement graceful degradation by inserting a
+ * pseudo table whose lookups fail with a warning message.
*/
while ((start = mystrtokq(&bp, delim, CHARS_BRACE)) != 0) {
if (*start == '#') {
if (*item == '/') { /* /file/name */
if ((fp = vstream_fopen(item, O_RDONLY, 0)) == 0) {
vstring_sprintf(buf, "%s:%s", DICT_TYPE_NOFILE, item);
- /* XXX Should increment existing map refcount. */
if (dict_handle(STR(buf)) == 0)
dict_register(STR(buf),
dict_surrogate(DICT_TYPE_NOFILE, item,
OPEN_FLAGS, DICT_FLAGS,
"open file %s: %m", item));
- argv_add(list, STR(buf), (char *) 0);
+ argv_add(pat_list, STR(buf), (char *) 0);
} else {
while (vstring_fgets(buf, fp))
if (vstring_str(buf)[0] != '#')
- list = match_list_parse(list, vstring_str(buf), match);
+ pat_list = match_list_parse(match_list, pat_list,
+ vstring_str(buf), match);
if (vstream_fclose(fp))
msg_fatal("%s: read file %s: %m", myname, item);
}
vstring_sprintf(buf, "%s%s(%o,%s)", match ? "" : "!",
item, OPEN_FLAGS, dict_flags_str(DICT_FLAGS));
map_type_name_flags = STR(buf) + (match == 0);
- /* XXX Should increment existing map refcount. */
if (dict_handle(map_type_name_flags) == 0)
dict_register(map_type_name_flags,
dict_open(item, OPEN_FLAGS, DICT_FLAGS));
- argv_add(list, STR(buf), (char *) 0);
+ argv_add(pat_list, STR(buf), (char *) 0);
} else { /* other pattern */
- argv_add(list, match ? item :
- STR(vstring_sprintf(buf, "!%s", item)), (char *) 0);
+ if (casefold(util_utf8_enable, match_list->fold_buf, match ?
+ item : STR(vstring_sprintf(buf, "!%s", item)),
+ &utf8_err) == 0) {
+ vstring_sprintf(match_list->fold_buf, "%s:%s",
+ DICT_TYPE_NOUTF8, item);
+ if (dict_handle(STR(match_list->fold_buf)) == 0)
+ dict_register(STR(match_list->fold_buf),
+ dict_surrogate(DICT_TYPE_NOFILE, item,
+ OPEN_FLAGS, DICT_FLAGS,
+ "casefold error: %s",
+ utf8_err));
+ }
+ argv_add(pat_list, STR(match_list->fold_buf), (char *) 0);
}
}
vstring_free(buf);
- return (list);
+ return (pat_list);
}
/* match_list_init - initialize pattern list */
list->match_func[i] = va_arg(ap, MATCH_LIST_FN);
va_end(ap);
list->error = 0;
+ list->fold_buf = vstring_alloc(20);
#define DO_MATCH 1
saved_patterns = mystrdup(patterns);
- list->patterns = match_list_parse(argv_alloc(1), saved_patterns, DO_MATCH);
+ list->patterns = match_list_parse(list, argv_alloc(1), saved_patterns,
+ DO_MATCH);
argv_terminate(list->patterns);
myfree(saved_patterns);
return (list);
int match;
int i;
va_list ap;
+ const char *utf8_err;
/*
* Iterate over all patterns in the list, stop at the first match.
for (cpp = list->patterns->argv; (pat = *cpp) != 0; cpp++) {
for (match = 1; *pat == '!'; pat++)
match = !match;
- for (i = 0; i < list->match_count; i++)
- if (list->match_func[i] (list, list->match_args[i], pat))
+ for (i = 0; i < list->match_count; i++) {
+ if (casefold(util_utf8_enable, list->fold_buf,
+ list->match_args[i], &utf8_err) == 0) {
+ msg_warn("%s: casefold error for \"%s\": %s",
+ myname, list->match_args[i], utf8_err);
+ continue;
+ }
+ if (list->match_func[i] (list, STR(list->fold_buf), pat))
return (match);
else if (list->error != 0)
return (0);
+ }
}
if (msg_verbose)
for (i = 0; i < list->match_count; i++)
argv_free(list->patterns);
myfree((void *) list->match_func);
myfree((void *) list->match_args);
+ vstring_free(list->fold_buf);
myfree((void *) list);
}
* Utility library.
*/
#include <argv.h>
+#include <vstring.h>
/*
* External interface.
int match_count; /* match function/argument count */
MATCH_LIST_FN *match_func; /* match functions */
const char **match_args; /* match arguments */
+ VSTRING *fold_buf; /* case-folded pattern string */
int error; /* last operation */
};
}
/*
- * Try an exact string match.
+ * Try an exact string match. Note that the string and pattern are
+ * already casefolded.
*/
- if (strcasecmp(string, pattern) == 0) {
+ if (strcmp(string, pattern) == 0) {
return (1);
}
}
/*
- * Try an exact match with the host name.
+ * Try an exact match with the host name. Note that the name and the
+ * pattern are already casefolded.
*/
- if (strcasecmp(name, pattern) == 0) {
+ if (strcmp(name, pattern) == 0) {
return (1);
}
/*
- * See if the pattern is a parent domain of the hostname.
+ * See if the pattern is a parent domain of the hostname. Note that the
+ * name and the pattern are already casefolded.
*/
else {
if (list->flags & MATCH_FLAG_PARENT) {
pd = name + strlen(name) - strlen(pattern);
- if (pd > name && pd[-1] == '.' && strcasecmp(pd, pattern) == 0)
+ if (pd > name && pd[-1] == '.' && strcmp(pd, pattern) == 0)
return (1);
} else if (pattern[0] == '.') {
pd = name + strlen(name) - strlen(pattern);
- if (pd > name && strcasecmp(pd, pattern) == 0)
+ if (pd > name && strcmp(pd, pattern) == 0)
return (1);
}
}
}
/*
- * Try an exact match with the host address.
+ * Try an exact match with the host address. Note that the address and
+ * pattern are already casefolded.
*/
if (pattern[0] != '[') {
- if (strcasecmp(addr, pattern) == 0)
+ if (strcmp(addr, pattern) == 0)
return (1);
} else {
size_t addr_len = strlen(addr);
- if (strncasecmp(addr, pattern + 1, addr_len) == 0
+ if (strncmp(addr, pattern + 1, addr_len) == 0
&& strcmp(pattern + 1 + addr_len, "]") == 0)
return (1);
}
* - Don't bother unless the pattern is either an IPv6 address or net/mask.
*
* We can safely skip IPv4 address patterns because their form is
- * unambiguous and they did not match in the strcasecmp() calls above.
+ * unambiguous and they did not match in the strcmp() calls above.
*
* XXX We MUST skip (parent) domain names, which may appear in NAMADR_LIST
* input, to avoid triggering false cidr_match_parse() errors.
extern char *printable(char *, int);
extern char *neuter(char *, const char *, int);
extern char *lowercase(char *);
-extern char *casefold(VSTRING *, const char *, CONST_CHAR_STAR *);
+extern char *casefold(int, VSTRING *, const char *, CONST_CHAR_STAR *);
extern char *uppercase(char *);
extern char *skipblanks(const char *);
extern char *trimblanks(char *, ssize_t);