diff --git a/postfix/HISTORY b/postfix/HISTORY index f31d85e1a..ae7f7ee64 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -6926,6 +6926,25 @@ Apologies for any names omitted. Bugfix: the LDAP client dumped core in verbose mode. Reported by Will Day and others. File: util/dict_ldap.c. +20020906 + + Cleanup: dict_regexp module speedups by avoiding unnecessary + work while matching strings. File: util/dict_regexp.c. + +20020907 + + Feature: IF..ENDIF support based on code by Bert Driehuis. + File: util/dict_regexp.c. + + +200209010 + + Bugfix: the SMTP client produced unnecessary warnings about + trouble with the fallback_relay hosts. File: smtp/smtp_connect.c. + + Robustness: don't wait with detecting broken SMTP connections + until reading input. Leandro Santi. File: smtpd/smtpd_chat.c. + Open problems: Low: smtpd should log queue ID with reject/warn/hold/discard diff --git a/postfix/README_FILES/VIRTUAL_README b/postfix/README_FILES/VIRTUAL_README index 6ad5fe7bd..2f8439dd7 100644 --- a/postfix/README_FILES/VIRTUAL_README +++ b/postfix/README_FILES/VIRTUAL_README @@ -138,7 +138,7 @@ virtual_mailbox_lock Use the "postconf -m" command to find out what locking methods Postfix supports on your system. -virtual_mailbox_size +virtual_mailbox_limit An upper limit on the size of a mailbox file or maildir file. diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index c5df4ce84..1168c9d78 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -12,6 +12,12 @@ snapshot release). Patches change the patchlevel and the release date. Snapshots change only the release date, unless they include the same bugfixes as a patch release. +Incompatible changes with Postfix snapshot 1.1.11-20020910 +========================================================== + +The relayhost setting now behaves as documented, i.e. you can no +longer specify multiple destinations. + Incompatible changes with Postfix snapshot 1.1.11-20020906 ========================================================== diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 68abbfa22..76b4acfed 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * Patches change the patchlevel and the release date. Snapshots change the * release date only, unless they include the same bugfix as a patch release. */ -#define MAIL_RELEASE_DATE "20020906" +#define MAIL_RELEASE_DATE "20020910" #define VAR_MAIL_VERSION "mail_version" #define DEF_MAIL_VERSION "1.1.11-" MAIL_RELEASE_DATE diff --git a/postfix/src/smtp/smtp_chat.c b/postfix/src/smtp/smtp_chat.c index cbd81a265..d33a90e4e 100644 --- a/postfix/src/smtp/smtp_chat.c +++ b/postfix/src/smtp/smtp_chat.c @@ -149,14 +149,6 @@ void smtp_chat_cmd(SMTP_STATE *state, char *fmt,...) * Send the command to the SMTP server. */ smtp_fputs(STR(state->buffer), LEN(state->buffer), session->stream); - - /* - * Flush unsent output if no I/O happened for a while. This avoids - * timeouts with pipelined SMTP sessions that have lots of delays - * (typically, DNS lookups for sender/recipient unaliasing). - */ - if (time((time_t *) 0) - vstream_ftime(session->stream) > 10) - vstream_fflush(session->stream); } /* smtp_chat_resp - read and process SMTP server response */ diff --git a/postfix/src/smtp/smtp_connect.c b/postfix/src/smtp/smtp_connect.c index c6ea16c43..38d769a7b 100644 --- a/postfix/src/smtp/smtp_connect.c +++ b/postfix/src/smtp/smtp_connect.c @@ -391,9 +391,9 @@ SMTP_SESSION *smtp_connect(char *destination, VSTRING *why) char *host; unsigned port; char *def_service = "smtp"; /* XXX configurable? */ - char *save; + ARGV *sites; char *dest; - char *cp; + char **cpp; int found_myself = 0; /* @@ -401,9 +401,11 @@ SMTP_SESSION *smtp_connect(char *destination, VSTRING *why) * to the optional fall-back relays. Each can be a list of destinations * by itself, with domain, host, [], numerical address, and port. */ - cp = save = concatenate(destination, " ", var_fallback_relay, (char *) 0); + sites = argv_alloc(1); + argv_add(sites, destination, (char *) 0); + argv_split_append(sites, var_fallback_relay, ", \t\r\n"); - while ((dest = mystrtok(&cp, ", \t\r\n")) != 0) { + for (cpp = sites->argv; (dest = *cpp) != 0; cpp++) { /* * Parse the destination. Default is to use the SMTP port. @@ -448,8 +450,8 @@ SMTP_SESSION *smtp_connect(char *destination, VSTRING *why) VAR_RELAYHOST, var_relayhost); smtp_errno = SMTP_RETRY; } - if (*var_fallback_relay) { - msg_warn("%s configuration problem: %s", + if (cpp > sites->argv && sites->argc > 1) { + msg_warn("%s problem: %s", VAR_FALLBACK_RELAY, var_fallback_relay); smtp_errno = SMTP_RETRY; } @@ -458,6 +460,6 @@ SMTP_SESSION *smtp_connect(char *destination, VSTRING *why) /* * Cleanup. */ - myfree(save); + argv_free(sites); return (session); } diff --git a/postfix/src/smtpd/smtpd_chat.c b/postfix/src/smtpd/smtpd_chat.c index b9be60015..ae2cf344b 100644 --- a/postfix/src/smtpd/smtpd_chat.c +++ b/postfix/src/smtpd/smtpd_chat.c @@ -139,6 +139,7 @@ void smtpd_chat_query(SMTPD_STATE *state) void smtpd_chat_reply(SMTPD_STATE *state, char *format,...) { va_list ap; + int delay = 0; va_start(ap, format); vstring_vsprintf(state->buffer, format, ap); @@ -157,9 +158,10 @@ void smtpd_chat_reply(SMTPD_STATE *state, char *format,...) * errors within a session. */ if (state->error_count > var_smtpd_soft_erlim) - sleep(state->error_count); + sleep(delay = (state->error_count > var_smtpd_err_sleep ? + state->error_count : var_smtpd_err_sleep)); else if (STR(state->buffer)[0] == '4' || STR(state->buffer)[0] == '5') - sleep(var_smtpd_err_sleep); + sleep(delay = var_smtpd_err_sleep); smtp_fputs(STR(state->buffer), LEN(state->buffer), state->client); @@ -168,8 +170,14 @@ void smtpd_chat_reply(SMTPD_STATE *state, char *format,...) * timeouts with pipelined SMTP sessions that have lots of server-side * delays (tarpit delays or DNS lookups for UCE restrictions). */ - if (time((time_t *) 0) - vstream_ftime(state->client) > 10) + if (delay || time((time_t *) 0) - vstream_ftime(state->client) > 10) vstream_fflush(state->client); + + /* + * Abort immediately if the connection is broken. + */ + if (vstream_ferror(state->client)) + vstream_longjmp(state->client, SMTP_ERR_EOF); } /* print_line - line_wrap callback */ diff --git a/postfix/src/util/dict_ldap.c b/postfix/src/util/dict_ldap.c index 7b2333b6b..e8e7683ea 100644 --- a/postfix/src/util/dict_ldap.c +++ b/postfix/src/util/dict_ldap.c @@ -308,7 +308,7 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) &dict_ldap->version) != LDAP_OPT_SUCCESS) msg_warn("%s: Unable to get LDAP protocol version", myname); else - msg_warn("%s: Actual Protocol version used was %d.", + msg_warn("%s: Actual Protocol version used is %d.", myname, dict_ldap->version); } #endif diff --git a/postfix/src/util/dict_regexp.c b/postfix/src/util/dict_regexp.c index 1a55680b6..df1e3abd6 100644 --- a/postfix/src/util/dict_regexp.c +++ b/postfix/src/util/dict_regexp.c @@ -16,6 +16,7 @@ /* against the table. /* SEE ALSO /* dict(3) generic dictionary manager +/* regexp_table(5) format of Postfix regular expression tables /* AUTHOR(S) /* LaMont Jones /* lamont@hp.com @@ -43,6 +44,9 @@ #include #include #include +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif /* Utility library. */ @@ -57,6 +61,13 @@ #include "dict_regexp.h" #include "mac_parse.h" + /* + * Support for IF/ENDIF based on code by Bert Driehuis. + */ +#define REGEXP_OP_MATCH 1 /* Match this regexp */ +#define REGEXP_OP_IF 2 /* Increase if/endif nesting on match */ +#define REGEXP_OP_ENDIF 3 /* Decrease if/endif nesting */ + /* * Regular expression before compiling. */ @@ -75,6 +86,8 @@ typedef struct dict_regexp_list { char *replacement; /* replacement text */ size_t max_nsub; /* largest replacement $number */ int lineno; /* source file line number */ + int nesting; /* Level of search nesting */ + int op; /* REGEXP_OP_MATCH, OP_IF, OP_ENDIF */ } DICT_REGEXP_RULE; /* @@ -168,6 +181,7 @@ static const char *dict_regexp_lookup(DICT *dict, const char *name) DICT_REGEXP_EXPAND_CONTEXT ctxt; static VSTRING *buf; int error; + int nesting = 0; dict_errno = 0; @@ -179,6 +193,12 @@ static const char *dict_regexp_lookup(DICT *dict, const char *name) * for substring substitution to the bare minimum. */ for (rule = dict_regexp->head; rule; rule = rule->next) { + if (nesting < rule->nesting) /* inside false IF/ENDIF */ + continue; + if (rule->op == REGEXP_OP_ENDIF) { + nesting--; + continue; + } error = regexec(rule->primary_exp, name, rule->max_nsub + 1, rule->max_nsub ? dict_regexp->pmatch : (regmatch_t *) 0, 0); @@ -213,8 +233,16 @@ static const char *dict_regexp_lookup(DICT *dict, const char *name) } /* - * Match found. Skip $number substitutions when the replacement text - * contains no $number strings (as learned during the pre-scan). + * Match found. + */ + if (rule->op == REGEXP_OP_IF) { + nesting++; + continue; + } + + /* + * Skip $number substitutions when the replacement text contains no + * $number strings (as learned during the pre-scan). */ if (rule->max_nsub == 0) return (rule->replacement); @@ -364,10 +392,32 @@ static int dict_regexp_prescan(int type, VSTRING *buf, char *context) return (MAC_PARSE_OK); } +/* dict_regexp_patterns - get the primary and negated patterns and flags */ + +static int dict_regexp_patterns(const char *map, int lineno, char **p, + DICT_REGEXP_PATTERN *primary_pat, + DICT_REGEXP_PATTERN *negated_pat) +{ + + /* + * Get the primary and optional negated patterns and their flags. + */ + if (dict_regexp_get_pattern(map, lineno, p, primary_pat) == 0) + return (0); + if (*(*p) == '!' && (*p)[1] && !ISSPACE((*p)[1])) { + (*p)++; + if (dict_regexp_get_pattern(map, lineno, p, negated_pat) == 0) + return (0); + } else { + negated_pat->regexp = 0; + } + return (1); +} + /* dict_regexp_parseline - parse one rule */ static DICT_REGEXP_RULE *dict_regexp_parseline(const char *map, int lineno, - char *line) + char *line, int nesting) { DICT_REGEXP_RULE *rule; char *p; @@ -376,79 +426,126 @@ static DICT_REGEXP_RULE *dict_regexp_parseline(const char *map, int lineno, DICT_REGEXP_PATTERN primary_pat; DICT_REGEXP_PATTERN negated_pat; DICT_REGEXP_PRESCAN_CONTEXT ctxt; + int op = REGEXP_OP_MATCH; p = line; /* - * Get the primary and optional negated patterns and their flags. + * The MATCH operator takes both patterns and replacement text. */ - if (dict_regexp_get_pattern(map, lineno, &p, &primary_pat) == 0) - return (0); - if (*p == '!' && p[1] && !ISSPACE(p[1])) { - p++; - if (dict_regexp_get_pattern(map, lineno, &p, &negated_pat) == 0) + if (!ISALNUM(*p)) { + op = REGEXP_OP_MATCH; + if (!dict_regexp_patterns(map, lineno, &p, &primary_pat, &negated_pat)) return (0); - } else { - negated_pat.regexp = 0; + + /* + * Get the replacement text. + */ + if (!ISSPACE(*p)) { + msg_warn("regexp map %s, line %d: invalid expression: " + "skipping this rule", map, lineno); + return (0); + } + while (*p && ISSPACE(*p)) + ++p; + if (!*p) { + msg_warn("regexp map %s, line %d: using empty replacement string", + map, lineno); + } } /* - * Get the replacement text. + * The IF operator takes patterns but no replacement text. */ - if (!ISSPACE(*p)) { - msg_warn("regexp map %s, line %d: invalid expression: " - "skipping this rule", map, lineno); - return (0); + else if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) { + op = REGEXP_OP_IF; + p += 2; + while (*p && ISSPACE(*p)) + p++; + if (!dict_regexp_patterns(map, lineno, &p, &primary_pat, &negated_pat)) + return (0); + if (*p) + msg_warn("%s, line %d: ignoring extra text after IF", map, lineno); } - while (*p && ISSPACE(*p)) - ++p; - if (!*p) { - msg_warn("regexp map %s, line %d: using empty replacement string", + + /* + * The ENDIF operator takes no patterns and no replacement text. + */ + else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) { + op = REGEXP_OP_ENDIF; + p += 5; + if (*p) + msg_warn("%s, line %d: ignoring extra text after ENDIF", + map, lineno); + if (nesting == 0) { + msg_warn("%s, line %d: ignoring ENDIF without matching IF", + map, lineno); + return (0); + } + primary_pat.regexp = negated_pat.regexp = 0; + } + + /* + * Unrecognized request. + */ + else { + msg_warn("regexp map %s, line %d: ignoring unrecognized request", map, lineno); - } - - /* - * Find the highest-numbered $number substitution string. We can speed up - * processing 1) by passing hints to the regexp compiler, setting the - * REG_NOSUB flag when the replacement text contains no $number string; - * 2) by passing hints to the regexp execution code, limiting the amount - * of text that is made available for substitution. - */ - ctxt.map = map; - ctxt.lineno = lineno; - ctxt.max_nsub = 0; - if (mac_parse(p, dict_regexp_prescan, (char *) &ctxt) & MAC_PARSE_ERROR) { - msg_warn("regexp map %s, line %d: bad replacement syntax: " - "skipping this rule", map, lineno); return (0); } /* - * Compile the primary and the optional negated pattern. Speed up - * execution when no matched text needs to be substituted into the result - * string, or when the highest numbered substring is less than the total - * number of () subpatterns. + * Do some compile-time optimizations to speed up pattern matches. */ - if (ctxt.max_nsub == 0) - primary_pat.options |= REG_NOSUB; - if ((primary_exp = dict_regexp_compile(map, lineno, &primary_pat)) == 0) - return (0); - if (ctxt.max_nsub > primary_exp->re_nsub) { - msg_warn("regexp map %s, line %d: out of range replacement index \"%d\": " - "skipping this rule", map, lineno, ctxt.max_nsub); - regfree(primary_exp); - myfree((char *) primary_exp); - return (0); - } - if (negated_pat.regexp != 0) { - negated_pat.options |= REG_NOSUB; - if ((negated_exp = dict_regexp_compile(map, lineno, &negated_pat)) == 0) { + if (primary_pat.regexp) { + ctxt.map = map; + ctxt.lineno = lineno; + ctxt.max_nsub = 0; + + /* + * Find the highest-numbered $number substitution string. We can + * speed up processing 1) by passing hints to the regexp compiler, + * setting the REG_NOSUB flag when the replacement text contains no + * $number string; 2) by passing hints to the regexp execution code, + * limiting the amount of text that is made available for + * substitution. + */ + if (mac_parse(p, dict_regexp_prescan, (char *) &ctxt) & MAC_PARSE_ERROR) { + msg_warn("regexp map %s, line %d: bad replacement syntax: " + "skipping this rule", map, lineno); + return (0); + } + + /* + * Compile the primary and the optional negated pattern. Speed up + * execution when no matched text needs to be substituted into the + * result string, or when the highest numbered substring is less than + * the total number of () subpatterns. + */ + if (ctxt.max_nsub == 0) + primary_pat.options |= REG_NOSUB; + if ((primary_exp = dict_regexp_compile(map, lineno, &primary_pat)) == 0) + return (0); + if (ctxt.max_nsub > primary_exp->re_nsub) { + msg_warn("regexp map %s, line %d: out of range replacement index \"%d\": " + "skipping this rule", map, lineno, ctxt.max_nsub); regfree(primary_exp); myfree((char *) primary_exp); return (0); } - } else - negated_exp = 0; + if (negated_pat.regexp != 0) { + negated_pat.options |= REG_NOSUB; + if ((negated_exp = dict_regexp_compile(map, lineno, &negated_pat)) == 0) { + regfree(primary_exp); + myfree((char *) primary_exp); + return (0); + } + } else + negated_exp = 0; + } else { + primary_exp = negated_exp = 0; + ctxt.max_nsub = 0; + } /* * Package up the result. @@ -459,6 +556,8 @@ static DICT_REGEXP_RULE *dict_regexp_parseline(const char *map, int lineno, rule->replacement = mystrdup(p); rule->max_nsub = ctxt.max_nsub; rule->lineno = lineno; + rule->op = op; + rule->nesting = nesting; rule->next = 0; return (rule); } @@ -474,6 +573,7 @@ DICT *dict_regexp_open(const char *map, int unused_flags, int dict_flags) DICT_REGEXP_RULE *last_rule = 0; int lineno = 0; size_t max_nsub = 0; + int nesting = 0; char *p; line_buffer = vstring_alloc(100); @@ -495,11 +595,16 @@ DICT *dict_regexp_open(const char *map, int unused_flags, int dict_flags) while (readlline(line_buffer, map_fp, &lineno)) { p = vstring_str(line_buffer); trimblanks(p, 0)[0] = 0; - rule = dict_regexp_parseline(map, lineno, p); + if (*p == 0) + continue; + rule = dict_regexp_parseline(map, lineno, p, nesting); if (rule) { if (rule->max_nsub > max_nsub) max_nsub = rule->max_nsub; - + if (rule->op == REGEXP_OP_IF) + nesting++; + if (rule->op == REGEXP_OP_ENDIF) + nesting--; if (last_rule == 0) dict_regexp->head = rule; else @@ -508,6 +613,9 @@ DICT *dict_regexp_open(const char *map, int unused_flags, int dict_flags) } } + if (nesting) + msg_warn("%s, line %d: more IFs than ENDIFs", map, lineno); + /* * Allocate space for only as many matched substrings as used in the * replacement text. diff --git a/postfix/src/util/dict_static.c b/postfix/src/util/dict_static.c index 1b06a89ab..346e166b9 100644 --- a/postfix/src/util/dict_static.c +++ b/postfix/src/util/dict_static.c @@ -44,7 +44,7 @@ /* dict_static_lookup - access static value*/ -static const char *dict_static_lookup(DICT *dict, const char *name) +static const char *dict_static_lookup(DICT *dict, const char *unused_name) { dict_errno = 0;