From: Wietse Venema Date: Thu, 9 Dec 1999 05:00:00 +0000 (-0500) Subject: snapshot-19991209 X-Git-Tag: v20010228~83 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5587a4cbad93e827c4fcc2f74aba76df5c2f5243;p=thirdparty%2Fpostfix.git snapshot-19991209 --- diff --git a/postfix/DEBUG_README b/postfix/DEBUG_README index 89ca99136..78d0b6dd5 100644 --- a/postfix/DEBUG_README +++ b/postfix/DEBUG_README @@ -25,8 +25,8 @@ Append one or more -v options to selected daemon definitions in /etc/postfix/master.cf and type "postfix reload". This will cause a lot of activity to be logged to the syslog daemon. -4 - Tracing a Postfix daemon process -==================================== +4 - Manually tracing a Postfix daemon process +============================================= Some systems allow you to inspect a running process with a system call tracer. For example: @@ -41,12 +41,30 @@ See your system documentation for details. Tracing a running process can give valuable information about what a process is attempting to do. This is as much information as you can get without running an interactive debugger program, as described -in the next section. +in a later section. -See the next section on how to automatically attach a program to -a Postfix daemon. +5 - Automatically tracing a Postfix daemon process +================================================== -5 - Running daemon programs under an interactive debugger +Postfix can attach a call tracer whenever a daemon process starts. + +Append a -D option to the suspect command in /etc/postfix/master.cf, +for example: + + smtp inet n - n - - smtpd -D + +Edit the debugger_command definition in /etc/postfix/main.cf so +that it invokes the call tracer of your choice, for example: + + debugger_command = + PATH=/bin:/usr/bin:/usr/local/bin + (truss -p $process_id 2>&1 | logger -p mail.info) & sleep 5 + +Instead of truss use trace or strace. + +Type "postfix reload" and watch the logfile. + +6 - Running daemon programs under an interactive debugger ========================================================= Append a -D option to the suspect command in /etc/postfix/master.cf, @@ -72,7 +90,7 @@ Stop and start the Postfix system. Whenever the suspect daemon process is started, a debugger window pops up and you can watch in detail what happens. -6 - Unreasonable behavior +7 - Unreasonable behavior ========================= Sometimes the behavior exhibit by Postfix just does not match the diff --git a/postfix/HISTORY b/postfix/HISTORY index d6b13adaa..67a397a0a 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -3317,15 +3317,43 @@ Apologies for any names omitted. 19991207 - Performance: the queue manager now frees in-memory recipients - as soon as a message is delivered to one destination, rather - than waiting until all in-memory recipients of that message - have been tried. This means that one message with many - recipients no longer stops other mail from being delivered. + Performance: one message with many recipients no longer + stops other mail from being delivered. The queue manager + now frees in-memory recipients as soon as a message is + delivered to one destination, rather than waiting until + all in-memory destinations of that message have been tried. Patch by Patrik Rak @ ein.cz. Files: qmgr/qmgr_entry.c, qmgr/qmgr_message.c. - Performance: when delivering a huge list of recipients, - the queue manager now reads new recipients from queue file - before delivery concurrency starts dropping. Files: - qmgr/qmgr_entry.c, qmgr/qmgr_message.c. + Performance: when delivering mail to a huge list of + recipients, the queue manager now reads more recipients + from the queue file before delivery concurrency starts + to drop. Files: qmgr/qmgr_entry.c, qmgr/qmgr_message.c. + +19991208 + + Performance: improved worst-case behavior. A fully loaded + Postfix inflicts the same delay to messages with any number + of recipients (up to qmgr_message_recipient_limit.) Inspired + by discussions with Patrik Rak (although he disagrees with + the strategy). File: qmgr/qmgr_message.c. + + Updated LDAP client code by John Hensley with escape + sequences as per RFC 2254. File: util/dict_ldap.c. + + Updated MYSQL client code by Scott Cotton. File: dict_mysql.c. + + Feature: added -N/-n options to include/exclude terminating + nulls in keys and values in postmap/postalias DB or DBM + files. Normally, Postfix uses whatever is appropriate for + the host system. A non-default setting can be necessary + for inter-operability with third-party software. + + Bugfix: the local delivery agent would deliver to the user + instead of the .forward file when the .forward file was + already visited via some non-recursive path. Patch by Patrik + Rak @ ein.cz. Files: global/been_here.c, local/dotforward.c. + + Robustness: attempt to deliver all addresses in the expansion + of an alias or .forward file, even when some addresses must + be deferred. File: local/token.c. diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index e4449762c..4e5024b0c 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -1,4 +1,4 @@ -Incompatible changes with snapshot 19991127 +Incompatible changes with snapshot 19991209 =========================================== - In an SMTPD access map, an all-numeric right-hand side now means @@ -18,7 +18,7 @@ $mydestination domains matches a transport specification, you also need to add a "domain.name local:" entry in your transport_maps. See the html/faq.html sections for firewalls and intranets. -Major changes with snapshot 19991127 +Major changes with snapshot 19991209 ==================================== - It is now relatively safe to configure 550 status codes for the diff --git a/postfix/conf/main.cf b/postfix/conf/main.cf index 869b2bbcb..6fbc69cb0 100644 --- a/postfix/conf/main.cf +++ b/postfix/conf/main.cf @@ -222,9 +222,9 @@ mail_owner = postfix # DELIVERY TO MAILBOX # # The home_mailbox parameter specifies the optional pathname of a -# mailbox relative to a user's home directory. The default is to -# deliver to the UNIX-style /var/spool/mail/user or /var/mail/user. -# Specify "Maildir/" for qmail-style delivery (the / is required). +# mailbox file relative to a user's home directory. The default +# mailbox file is /var/spool/mail/user or /var/mail/user. Specify +# "Maildir/" for qmail-style delivery (the / is required). # #home_mailbox = Mailbox #home_mailbox = Maildir/ @@ -298,12 +298,14 @@ mail_owner = postfix #header_checks = regexp:/etc/postfix/filename #header_checks = pcre:/etc/postfix/filename -# The relay_domains parameter restricts what domains (and subdomains -# thereof) this mail system will relay mail from or to. See the -# smtpd_recipient_restrictions restriction in the file sample-smtpd.cf. +# The relay_domains parameter restricts what client hostname domains +# (and subdomains thereof) this mail system will relay mail from, +# and restricts what destination domains (and subdomains thereof) +# this system will relay mail to. See the smtpd_recipient_restrictions +# restriction in the file sample-smtpd.cf. # -# By default, Postfix relays mail only from or to sites in or below -# $mydestination, or in the optional virtual domain list. +# By default, Postfix relays mail only from clients or to destinations +# in or below $mydestination, or in the optional virtual domain list. # # Specify a list of hosts or domains, /file/name patterns or type:name # lookup tables, separated by commas and/or whitespace. Continue diff --git a/postfix/conf/sample-local.cf b/postfix/conf/sample-local.cf index 2cb6ff6d4..f2c3d4ebe 100644 --- a/postfix/conf/sample-local.cf +++ b/postfix/conf/sample-local.cf @@ -66,9 +66,9 @@ default_privs = nobody # # The home_mailbox parameter specifies the optional pathname of a -# mailbox relative to a user's home directory. The default is to -# deliver to the UNIX-style /var/spool/mail/user or /var/mail/user. -# Specify "Maildir/" for qmail-style delivery (the / is required). +# mailbox file relative to a user's home directory. The default +# mailbox file is /var/spool/mail/user or /var/mail/user. Specify +# "Maildir/" for qmail-style delivery (the / is required). # # home_mailbox = Mailbox # home_mailbox = Maildir/ diff --git a/postfix/conf/sample-smtpd.cf b/postfix/conf/sample-smtpd.cf index db6f35f69..9c37a82c3 100644 --- a/postfix/conf/sample-smtpd.cf +++ b/postfix/conf/sample-smtpd.cf @@ -181,7 +181,7 @@ smtpd_sender_restrictions = # # The default is to permit any destination from clients that match # $mynetworks, and to otherwise permit only mail from clients or to -# domains that match $relay_domains or a subdomain thereof. +# destinations that match $relay_domains or a subdomain thereof. # # The following restrictions are available: # @@ -191,8 +191,8 @@ smtpd_sender_restrictions = # reject_invalid_hostname: reject HELO hostname with bad syntax. # reject_unknown_hostname: reject HELO hostname without DNS A or MX record. # reject_unknown_sender_domain: reject sender domain without A or MX record. -# check_relay_domains: permit only mail from/to domains in $relay_domains - or to the local machine. +# check_relay_domains: permit only mail from clients/to domains matching +# $relay_domains, or to the local machine. # permit_auth_destination: permit mail to self or to $relay_domains. # reject_unauth_destination: reject mail not to self or to $relay_domains. # reject_unauth_pipelining: reject mail from improperly pipelining spamware @@ -238,11 +238,13 @@ smtpd_recipient_restrictions = permit_mynetworks,check_relay_domains # maps_rbl_domains = rbl.maps.vix.com -# The relay_domains parameter restricts what domains (and subdomains -# thereof) this mail system will relay mail from or to. +# The relay_domains parameter restricts what client hostname domains +# (and subdomains thereof) this mail system will relay mail from, +# and restricts what destination domains (and subdomains thereof) +# this system will relay mail to. # -# By default, Postfix relays mail only from or to sites in or below -# $mydestination, or in the optional virtual domain list. +# By default, Postfix relays mail only from clients or to destinations +# in or below $mydestination, or in the optional virtual domain list. # # Specify a list of hosts or domains, /file/name patterns or type:name # lookup tables, separated by commas and/or whitespace. Continue diff --git a/postfix/global/been_here.c b/postfix/global/been_here.c index 51e8e8715..a95c91aca 100644 --- a/postfix/global/been_here.c +++ b/postfix/global/been_here.c @@ -17,6 +17,14 @@ /* BH_TABLE *dup_filter; /* char *format; /* +/* int been_here_check_fixed(dup_filter, string) +/* BH_TABLE *dup_filter; +/* char *string; +/* +/* int been_here_check(dup_filter, format, ...) +/* BH_TABLE *dup_filter; +/* char *format; +/* /* void been_here_free(dup_filter) /* BH_TABLE *dup_filter; /* DESCRIPTION @@ -34,6 +42,9 @@ /* not found. The result is non-zero (true) if the formatted result was /* found, zero (false) otherwise. /* +/* been_here_check_fixed() and been_here_check() are similar +/* but do not update the duplicate filter. +/* /* been_here_free() releases storage for a duplicate filter. /* /* Arguments: @@ -173,3 +184,65 @@ int been_here_fixed(BH_TABLE *dup_filter, const char *string) return (status); } + +/* been_here_check - query duplicate detector with finer control */ + +int been_here_check(BH_TABLE *dup_filter, const char *fmt,...) +{ + VSTRING *buf = vstring_alloc(100); + int status; + va_list ap; + + /* + * Construct the string to be checked. + */ + va_start(ap, fmt); + vstring_vsprintf(buf, fmt, ap); + va_end(ap); + + /* + * Do the duplicate check. + */ + status = been_here_check_fixed(dup_filter, vstring_str(buf)); + + /* + * Cleanup. + */ + vstring_free(buf); + return (status); +} + +/* been_here_check_fixed - query duplicate detector */ + +int been_here_check_fixed(BH_TABLE *dup_filter, const char *string) +{ + char *folded_string; + const char *lookup_key; + int status; + + /* + * Special processing: case insensitive lookup. + */ + if (dup_filter->flags & BH_FLAG_FOLD) { + folded_string = mystrdup(string); + lookup_key = lowercase(folded_string); + } else { + folded_string = 0; + lookup_key = string; + } + + /* + * Do the duplicate check. + */ + status = (htable_locate(dup_filter->table, lookup_key) != 0); + if (msg_verbose) + msg_info("been_here_check: %s: %d", string, status); + + /* + * Cleanup. + */ + if (folded_string) + myfree(folded_string); + + return (status); +} diff --git a/postfix/global/been_here.h b/postfix/global/been_here.h index 953889784..12d878487 100644 --- a/postfix/global/been_here.h +++ b/postfix/global/been_here.h @@ -32,6 +32,8 @@ extern BH_TABLE *been_here_init(int, int); extern void been_here_free(BH_TABLE *); extern int been_here_fixed(BH_TABLE *, const char *); extern int been_here(BH_TABLE *, const char *,...); +extern int been_here_check_fixed(BH_TABLE *, const char *); +extern int been_here_check(BH_TABLE *, const char *,...); /* LICENSE /* .ad diff --git a/postfix/global/mail_version.h b/postfix/global/mail_version.h index 5c873b645..f963aed29 100644 --- a/postfix/global/mail_version.h +++ b/postfix/global/mail_version.h @@ -15,7 +15,7 @@ * Version of this program. */ #define VAR_MAIL_VERSION "mail_version" -#define DEF_MAIL_VERSION "Snapshot-19991207" +#define DEF_MAIL_VERSION "Snapshot-19991209" extern char *var_mail_version; /* LICENSE diff --git a/postfix/html/postalias.1.html b/postfix/html/postalias.1.html index 4e25fad1f..4d95379f7 100644 --- a/postfix/html/postalias.1.html +++ b/postfix/html/postalias.1.html @@ -9,7 +9,7 @@ POSTALIAS(1) POSTALIAS(1) postalias - Postfix alias database maintenance SYNOPSIS - postalias [-ivw] [-c config_dir] [-q key] + postalias [-Ninvw] [-c config_dir] [-q key] [file_type:]file_name ... DESCRIPTION @@ -26,15 +26,25 @@ POSTALIAS(1) POSTALIAS(1) Options: + -N Include the terminating null character that termi- + nates lookup keys and values. By default, Postfix + does whatever is the default for the host operating + system. + -c config_dir - Read the main.cf configuration file in the named + Read the main.cf configuration file in the named directory. - -i Incremental mode. Read entries from standard input + -i Incremental mode. Read entries from standard input and do not truncate an existing database. By - default, postalias creates a new database from the + default, postalias creates a new database from the entries in file_name. + -n Don't include the terminating null character that + terminates lookup keys and values. By default, + Postfix does whatever is the default for the host + operating system. + -q key Search the specified maps for key and print the first value found on the standard output stream. The exit status is non-zero if the requested infor- @@ -49,16 +59,6 @@ POSTALIAS(1) POSTALIAS(1) Arguments: - file_type - The type of database to be produced. - - btree The output is a btree file, named - file_name.db. This is available only on - systems with support for db databases. - - dbm The output consists of two files, named - file_name.pag and file_name.dir. This is - available only on systems with support for @@ -71,6 +71,16 @@ POSTALIAS(1) POSTALIAS(1) POSTALIAS(1) POSTALIAS(1) + file_type + The type of database to be produced. + + btree The output is a btree file, named + file_name.db. This is available only on + systems with support for db databases. + + dbm The output consists of two files, named + file_name.pag and file_name.dir. This is + available only on systems with support for dbm databases. hash The output is a hashed file, named @@ -114,6 +124,19 @@ POSTALIAS(1) POSTALIAS(1) aliases(5) format of alias database input file. sendmail(1) mail posting and compatibility interface. + + + + + 2 + + + + + +POSTALIAS(1) POSTALIAS(1) + + LICENSE The Secure Mailer license must be distributed with this software. @@ -128,7 +151,50 @@ POSTALIAS(1) POSTALIAS(1) - 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3 diff --git a/postfix/html/postmap.1.html b/postfix/html/postmap.1.html index 263a52fbd..c818e43e0 100644 --- a/postfix/html/postmap.1.html +++ b/postfix/html/postmap.1.html @@ -9,7 +9,7 @@ POSTMAP(1) POSTMAP(1) postmap - Postfix lookup table management SYNOPSIS - postmap [-ivw] [-c config_dir] [-q key] + postmap [-Ninvw] [-c config_dir] [-q key] [file_type:]file_name ... DESCRIPTION @@ -45,20 +45,20 @@ POSTMAP(1) POSTMAP(1) Options: + -N Include the terminating null character that termi- + nates lookup keys and values. By default, Postfix + does whatever is the default for the host operating + system. + -c config_dir - Read the main.cf configuration file in the named + Read the main.cf configuration file in the named directory. - -i Incremental mode. Read entries from standard input + -i Incremental mode. Read entries from standard input and do not truncate an existing database. By - default, postmap creates a new database from the + default, postmap creates a new database from the entries in file_name. - -q key Search the specified maps for key and print the - first value found on the standard output stream. - The exit status is non-zero if the requested infor- - mation was not found. - @@ -71,6 +71,16 @@ POSTMAP(1) POSTMAP(1) POSTMAP(1) POSTMAP(1) + -n Don't include the terminating null character that + terminates lookup keys and values. By default, + Postfix does whatever is the default for the host + operating system. + + -q key Search the specified maps for key and print the + first value found on the standard output stream. + The exit status is non-zero if the requested infor- + mation was not found. + -v Enable verbose logging for debugging purposes. Mul- tiple -v options make the software increasingly verbose. @@ -116,16 +126,6 @@ POSTMAP(1) POSTMAP(1) MAIL_VERBOSE Enable verbose logging for debugging purposes. -CONFIGURATION PARAMETERS - database_type - Default output database type. On many UNIX sys- - tems, the default database type is either hash or - dbm. - -LICENSE - The Secure Mailer license must be distributed with this - software. - 2 @@ -137,6 +137,16 @@ POSTMAP(1) POSTMAP(1) POSTMAP(1) POSTMAP(1) +CONFIGURATION PARAMETERS + database_type + Default output database type. On many UNIX sys- + tems, the default database type is either hash or + dbm. + +LICENSE + The Secure Mailer license must be distributed with this + software. + AUTHOR(S) Wietse Venema IBM T.J. Watson Research @@ -173,16 +183,6 @@ POSTMAP(1) POSTMAP(1) - - - - - - - - - - diff --git a/postfix/html/uce.html b/postfix/html/uce.html index 58db0ae5f..9b518b2a4 100644 --- a/postfix/html/uce.html +++ b/postfix/html/uce.html @@ -550,7 +550,7 @@ specifies the response code for rejected requests (default:
permit_auth_destination
Ignore the client hostname. Permit the request when the resolved destination address matches -the $mydestination, the +$mydestination, the machine IP addresses, or $relay_domains.

diff --git a/postfix/local/dotforward.c b/postfix/local/dotforward.c index b65877f86..e3131d3f8 100644 --- a/postfix/local/dotforward.c +++ b/postfix/local/dotforward.c @@ -213,27 +213,31 @@ int deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) * If this user includes (an alias of) herself in her own .forward file, * deliver to the user instead. */ - if (lookup_status >= 0 - && been_here(state.dup_filter, "forward %s", STR(path)) == 0) { - state.msg_attr.exp_from = state.msg_attr.local; - if (S_ISREG(st.st_mode) == 0) { - msg_warn("file %s is not a regular file", STR(path)); - } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) { - msg_warn("file %s has bad owner uid %d", STR(path), st.st_uid); - } else if (st.st_mode & 002) { - msg_warn("file %s is world writable", STR(path)); - } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) { - msg_warn("cannot open file %s: %m", STR(path)); - } else { - close_on_exec(fd, CLOSE_ON_EXEC); - addr_count = 0; - fp = vstream_fdopen(fd, O_RDONLY); - status = deliver_token_stream(state, usr_attr, fp, &addr_count); - if (vstream_fclose(fp)) - msg_warn("close file %s: %m", STR(path)); - if (addr_count > 0) - forward_found = YES; - } + if (lookup_status >= 0) { + if (been_here(state.dup_filter, "forward %s", STR(path)) == 0) { + state.msg_attr.exp_from = state.msg_attr.local; + if (S_ISREG(st.st_mode) == 0) { + msg_warn("file %s is not a regular file", STR(path)); + } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) { + msg_warn("file %s has bad owner uid %d", STR(path), st.st_uid); + } else if (st.st_mode & 002) { + msg_warn("file %s is world writable", STR(path)); + } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) { + msg_warn("cannot open file %s: %m", STR(path)); + } else { + close_on_exec(fd, CLOSE_ON_EXEC); + addr_count = 0; + fp = vstream_fdopen(fd, O_RDONLY); + status = deliver_token_stream(state, usr_attr, fp, &addr_count); + if (vstream_fclose(fp)) + msg_warn("close file %s: %m", STR(path)); + if (addr_count > 0) { + forward_found = YES; + been_here(state.dup_filter, "forward-done %s", STR(path)); + } + } + } else if (been_here_check(state.dup_filter, "forward-done %s", STR(path)) != 0) + forward_found = YES; /* else we're recursive */ } /* diff --git a/postfix/local/token.c b/postfix/local/token.c index d9ca86d4c..9f6f8ac14 100644 --- a/postfix/local/token.c +++ b/postfix/local/token.c @@ -177,9 +177,7 @@ int deliver_token_string(LOCAL_STATE state, USER_ATTR usr_attr, if (addr->type == TOK822_ADDR) { if (addr_count) (*addr_count)++; - status = deliver_token(state, usr_attr, addr); - if (status != 0) - break; + status |= deliver_token(state, usr_attr, addr); } } tok822_free_tree(tree); diff --git a/postfix/man/man1/postalias.1 b/postfix/man/man1/postalias.1 index 5315b5d22..8c3880a4a 100644 --- a/postfix/man/man1/postalias.1 +++ b/postfix/man/man1/postalias.1 @@ -9,8 +9,8 @@ Postfix alias database maintenance .na .nf .fi -\fBpostalias\fR [\fB-ivw\fR] [\fB-c \fIconfig_dir\fR] [\fB-q \fIkey\fR] -[\fIfile_type\fR:]\fIfile_name\fR ... +\fBpostalias\fR [\fB-Ninvw\fR] [\fB-c \fIconfig_dir\fR] +[\fB-q \fIkey\fR] [\fIfile_type\fR:]\fIfile_name\fR ... .SH DESCRIPTION .ad .fi @@ -25,12 +25,20 @@ entire database, in order to avoid surprises in spectator programs. Options: +.IP \fB-N\fR +Include the terminating null character that terminates lookup keys +and values. By default, Postfix does whatever is the default for +the host operating system. .IP "\fB-c \fIconfig_dir\fR" Read the \fBmain.cf\fR configuration file in the named directory. .IP \fB-i\fR Incremental mode. Read entries from standard input and do not truncate an existing database. By default, \fBpostalias\fR creates a new database from the entries in \fBfile_name\fR. +.IP \fB-n\fR +Don't include the terminating null character that terminates lookup +keys and values. By default, Postfix does whatever is the default for +the host operating system. .IP "\fB-q \fIkey\fR" Search the specified maps for \fIkey\fR and print the first value found on the standard output stream. The exit status is non-zero diff --git a/postfix/man/man1/postmap.1 b/postfix/man/man1/postmap.1 index f23c330d1..0b8b593ee 100644 --- a/postfix/man/man1/postmap.1 +++ b/postfix/man/man1/postmap.1 @@ -9,7 +9,7 @@ Postfix lookup table management .na .nf .fi -\fBpostmap\fR [\fB-ivw\fR] [\fB-c \fIconfig_dir\fR] [\fB-q \fIkey\fR] +\fBpostmap\fR [\fB-Ninvw\fR] [\fB-c \fIconfig_dir\fR] [\fB-q \fIkey\fR] [\fIfile_type\fR:]\fIfile_name\fR ... .SH DESCRIPTION .ad @@ -44,12 +44,20 @@ special characters such as `#' or whitespace. The \fIkey\fR is mapped to lowercase to make mapping lookups case insensitive. Options: +.IP \fB-N\fR +Include the terminating null character that terminates lookup keys +and values. By default, Postfix does whatever is the default for +the host operating system. .IP "\fB-c \fIconfig_dir\fR" Read the \fBmain.cf\fR configuration file in the named directory. .IP \fB-i\fR Incremental mode. Read entries from standard input and do not truncate an existing database. By default, \fBpostmap\fR creates a new database from the entries in \fBfile_name\fR. +.IP \fB-n\fR +Don't include the terminating null character that terminates lookup +keys and values. By default, Postfix does whatever is the default for +the host operating system. .IP "\fB-q \fIkey\fR" Search the specified maps for \fIkey\fR and print the first value found on the standard output stream. The exit status is non-zero diff --git a/postfix/postalias/postalias.c b/postfix/postalias/postalias.c index 79f7f40ea..2b002e88a 100644 --- a/postfix/postalias/postalias.c +++ b/postfix/postalias/postalias.c @@ -5,8 +5,8 @@ /* Postfix alias database maintenance /* SYNOPSIS /* .fi -/* \fBpostalias\fR [\fB-ivw\fR] [\fB-c \fIconfig_dir\fR] [\fB-q \fIkey\fR] -/* [\fIfile_type\fR:]\fIfile_name\fR ... +/* \fBpostalias\fR [\fB-Ninvw\fR] [\fB-c \fIconfig_dir\fR] +/* [\fB-q \fIkey\fR] [\fIfile_type\fR:]\fIfile_name\fR ... /* DESCRIPTION /* The \fBpostalias\fR command creates or queries one or more Postfix /* alias databases, or updates an existing one. The input and output @@ -19,12 +19,20 @@ /* programs. /* /* Options: +/* .IP \fB-N\fR +/* Include the terminating null character that terminates lookup keys +/* and values. By default, Postfix does whatever is the default for +/* the host operating system. /* .IP "\fB-c \fIconfig_dir\fR" /* Read the \fBmain.cf\fR configuration file in the named directory. /* .IP \fB-i\fR /* Incremental mode. Read entries from standard input and do not /* truncate an existing database. By default, \fBpostalias\fR creates /* a new database from the entries in \fBfile_name\fR. +/* .IP \fB-n\fR +/* Don't include the terminating null character that terminates lookup +/* keys and values. By default, Postfix does whatever is the default for +/* the host operating system. /* .IP "\fB-q \fIkey\fR" /* Search the specified maps for \fIkey\fR and print the first value /* found on the standard output stream. The exit status is non-zero @@ -297,7 +305,7 @@ static int postalias_query(const char *map_type, const char *map_name, static NORETURN usage(char *myname) { - msg_fatal("usage: %s [-ivw] [-c config_dir] [-q key] [map_type:]file...", + msg_fatal("usage: %s [-Ninvw] [-c config_dir] [-q key] [map_type:]file...", myname); } @@ -346,11 +354,15 @@ int main(int argc, char **argv) /* * Parse JCL. */ - while ((ch = GETOPT(argc, argv, "c:iq:vw")) > 0) { + while ((ch = GETOPT(argc, argv, "Nc:inq:vw")) > 0) { switch (ch) { default: usage(argv[0]); break; + case 'N': + dict_flags |= DICT_FLAG_TRY1NULL; + dict_flags &= ~DICT_FLAG_TRY0NULL; + break; case 'c': if (setenv(CONF_ENV_PATH, optarg, 1) < 0) msg_fatal("out of memory"); @@ -358,6 +370,10 @@ int main(int argc, char **argv) case 'i': open_flags &= ~O_TRUNC; break; + case 'n': + dict_flags |= DICT_FLAG_TRY0NULL; + dict_flags &= ~DICT_FLAG_TRY1NULL; + break; case 'q': query = optarg; break; diff --git a/postfix/postconf/postconf.c b/postfix/postconf/postconf.c index 4c5f2fbc0..f4695a44d 100644 --- a/postfix/postconf/postconf.c +++ b/postfix/postconf/postconf.c @@ -638,6 +638,9 @@ int main(int argc, char **argv) struct stat st; int junk; + /* + * Be consistent with file permissions. + */ umask(022); /* diff --git a/postfix/postmap/postmap.c b/postfix/postmap/postmap.c index 702119aee..d42909c13 100644 --- a/postfix/postmap/postmap.c +++ b/postfix/postmap/postmap.c @@ -5,7 +5,7 @@ /* Postfix lookup table management /* SYNOPSIS /* .fi -/* \fBpostmap\fR [\fB-ivw\fR] [\fB-c \fIconfig_dir\fR] [\fB-q \fIkey\fR] +/* \fBpostmap\fR [\fB-Ninvw\fR] [\fB-c \fIconfig_dir\fR] [\fB-q \fIkey\fR] /* [\fIfile_type\fR:]\fIfile_name\fR ... /* DESCRIPTION /* The \fBpostmap\fR command creates or queries one or more Postfix @@ -38,12 +38,20 @@ /* to lowercase to make mapping lookups case insensitive. /* /* Options: +/* .IP \fB-N\fR +/* Include the terminating null character that terminates lookup keys +/* and values. By default, Postfix does whatever is the default for +/* the host operating system. /* .IP "\fB-c \fIconfig_dir\fR" /* Read the \fBmain.cf\fR configuration file in the named directory. /* .IP \fB-i\fR /* Incremental mode. Read entries from standard input and do not /* truncate an existing database. By default, \fBpostmap\fR creates /* a new database from the entries in \fBfile_name\fR. +/* .IP \fB-n\fR +/* Don't include the terminating null character that terminates lookup +/* keys and values. By default, Postfix does whatever is the default for +/* the host operating system. /* .IP "\fB-q \fIkey\fR" /* Search the specified maps for \fIkey\fR and print the first value /* found on the standard output stream. The exit status is non-zero @@ -251,7 +259,7 @@ static int postmap_query(const char *map_type, const char *map_name, static NORETURN usage(char *myname) { - msg_fatal("usage: %s [-ivw] [-c config_dir] [-q key] [map_type:]file...", + msg_fatal("usage: %s [-Ninvw] [-c config_dir] [-q key] [map_type:]file...", myname); } @@ -300,11 +308,15 @@ int main(int argc, char **argv) /* * Parse JCL. */ - while ((ch = GETOPT(argc, argv, "c:iq:vw")) > 0) { + while ((ch = GETOPT(argc, argv, "Nc:inq:vw")) > 0) { switch (ch) { default: usage(argv[0]); break; + case 'N': + dict_flags |= DICT_FLAG_TRY1NULL; + dict_flags &= ~DICT_FLAG_TRY0NULL; + break; case 'c': if (setenv(CONF_ENV_PATH, optarg, 1) < 0) msg_fatal("out of memory"); @@ -312,6 +324,10 @@ int main(int argc, char **argv) case 'i': open_flags &= ~O_TRUNC; break; + case 'n': + dict_flags |= DICT_FLAG_TRY0NULL; + dict_flags &= ~DICT_FLAG_TRY1NULL; + break; case 'q': query = optarg; break; diff --git a/postfix/qmgr/qmgr_entry.c b/postfix/qmgr/qmgr_entry.c index f471e51fd..1d290a0ea 100644 --- a/postfix/qmgr/qmgr_entry.c +++ b/postfix/qmgr/qmgr_entry.c @@ -137,7 +137,7 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which) } /* - * Free the recipient list and decrease in-core recipient count + * Free the recipient list and decrease the in-core recipient count * accordingly. */ qmgr_recipient_count -= entry->rcpt_list.len; @@ -160,10 +160,11 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which) /* * Update the in-core message reference count. When the in-core message - * structure has no more references, dispose of the message. When the - * in-core recipient count falls below some threshold and this message - * has more recipients, read them from disk before concurrency starts to - * drop. + * structure has no more references, dispose of the message. + * + * When the in-core recipient count falls below some threshold and this + * message has more recipients, read more recipients before concurrency + * starts to drop. */ message->refcount--; if (message->refcount == 0) diff --git a/postfix/qmgr/qmgr_message.c b/postfix/qmgr/qmgr_message.c index 0703913f9..a3363ee69 100644 --- a/postfix/qmgr/qmgr_message.c +++ b/postfix/qmgr/qmgr_message.c @@ -222,6 +222,17 @@ static int qmgr_message_read(QMGR_MESSAGE *message) * may appear before or after the message content, so we keep reading * from the queue file until we have enough recipients (rcpt_offset != 0) * and until we know where the message content starts (data_offset != 0). + * + * When reading recipients from queue file, stop reading when we reach a + * per-message in-core recipient limit rather than a global in-core + * recipient limit. Use the global recipient limit only in order to stop + * opening queue files. The purpose is to achieve equal delay for + * messages with recipient counts up to var_qmgr_rcpt_limit recipients. + * + * If we would read recipients up to a global recipient limit, the average + * number of in-core recipients per message would asymptotically approach + * (global recipient limit)/(active queue size limit), which gives equal + * delay per recipient rather than equal delay per message. */ do { if ((curr_offset = vstream_ftell(message->fp)) < 0) @@ -241,10 +252,9 @@ static int qmgr_message_read(QMGR_MESSAGE *message) message->data_size, "queue %s", message->queue_name); } } else if (rec_type == REC_TYPE_RCPT) { -#define TOTAL_RECIPIENT_COUNT (qmgr_recipient_count + message->rcpt_list.len) - if (TOTAL_RECIPIENT_COUNT < var_qmgr_rcpt_limit) { + if (message->rcpt_list.len < var_qmgr_rcpt_limit) { qmgr_rcpt_list_add(&message->rcpt_list, curr_offset, start); - if (TOTAL_RECIPIENT_COUNT >= var_qmgr_rcpt_limit) { + if (message->rcpt_list.len >= var_qmgr_rcpt_limit) { if ((message->rcpt_offset = vstream_ftell(message->fp)) < 0) msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); diff --git a/postfix/util/dict_ldap.c b/postfix/util/dict_ldap.c index e28725ae6..f644f0e18 100644 --- a/postfix/util/dict_ldap.c +++ b/postfix/util/dict_ldap.c @@ -224,20 +224,39 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) escaped_name = vstring_alloc(20); filter_buf = vstring_alloc(30); - /* Any wildcards and escapes in the supplied address should be escaped. */ - if (strchr(name, '*') || strchr(name, '\\')) { + /* + * If any characters in the supplied address should be escaped per RFC + * 2254, do so. + */ + + end = (char *) name + strlen((char *) name); + sub = (char *) strpbrk((char *) name, "*()\\\0"); + if (sub && sub != end) { if (msg_verbose) - msg_info("%s: found wildcard in %s", myname, name); - for (sub = (char *) name; *sub != '\0'; sub++) { - if (*sub == '*' || *sub == '\\') { - vstring_strncat(escaped_name, "\\", 1); - vstring_strncat(escaped_name, sub, 1); - } else { + msg_info("%s: found character(s) in %s that must be escaped", myname, name); + for (sub = (char *) name; sub != end; sub++) { + switch (*sub) { + case '*': + vstring_strcat(escaped_name, "\\2a"); + break; + case '(': + vstring_strcat(escaped_name, "\\28"); + break; + case ')': + vstring_strcat(escaped_name, "\\29"); + break; + case '\\': + vstring_strcat(escaped_name, "\\5c"); + break; + case '\0': + vstring_strcat(escaped_name, "\\00"); + break; + default: vstring_strncat(escaped_name, sub, 1); } } if (msg_verbose) - msg_info("%s: with wildcards escaped, it's %s", myname, vstring_str(escaped_name)); + msg_info("%s: after escaping, it's %s", myname, vstring_str(escaped_name)); } else vstring_strcpy(escaped_name, (char *) name); diff --git a/postfix/util/dict_mysql.c b/postfix/util/dict_mysql.c index 0da7e4880..8f3263779 100644 --- a/postfix/util/dict_mysql.c +++ b/postfix/util/dict_mysql.c @@ -12,11 +12,19 @@ /* int dummy; /* int unused_dict_flags; /* DESCRIPTION -/* dict_mysql_open() opens the mysql databases with name dbname on -/* each host in hostlist and registers under the given name with the -/* dictionary manager. The result is a pointer to the installed dictionary, +/* dict_mysql_open() creates a dictionary of type 'mysql'. This +/* dictionary is an interface for the postfix key->value mappings +/* to mysql. The result is a pointer to the installed dictionary, /* or a null pointer in case of problems. /* +/* The mysql dictionary can manage multiple connections to different +/* sql servers on different hosts. It assumes that the underlying data +/* on each host is identical (mirrored) and maintains one connection +/* at any given time. If any connection fails, any other available +/* ones will be opened and used. The intent of this feature is to eliminate +/* a single point of failure for mail systems that would otherwise rely +/* on a single mysql server. +/* /* Arguments: /* .IP name /* The path of the MySQL configuration file. The file encodes a number of @@ -70,8 +78,24 @@ #include "argv.h" #include "vstring.h" +/* external declarations */ extern int dict_errno; +/* need some structs to help organize things */ +typedef struct { + MYSQL db; + char *hostname; + int stat; /* STATUNTRIED | STATFAIL | STATCUR */ + time_t ts; /* used for attempting reconnection + * every so often if a host is down */ +} HOST; + +typedef struct { + int len_hosts; /* number of hosts */ + HOST *db_hosts; /* the hosts on which the databases + * reside */ +} PLMYSQL; + typedef struct { char *username; char *password; @@ -90,8 +114,248 @@ typedef struct { MYSQL_NAME *name; } DICT_MYSQL; -/* mysqlname_parse - parse mysql configuration file */ +/* internal function declarations */ +static PLMYSQL *plmysql_init(char *hostnames[], int); +static MYSQL_RES *plmysql_query(PLMYSQL *, const char *, char *, char *, char *); +static void plmysql_dealloc(PLMYSQL *); +static void plmysql_down_host(HOST *); +static void plmysql_connect_single(HOST *, char *, char *, char *); +static int plmysql_ready_reconn(HOST); +static void dict_mysql_update(DICT *, const char *, const char *); +static const char *dict_mysql_lookup(DICT *, const char *); +DICT *dict_mysql_open(const char *, int, int); +static void dict_mysql_close(DICT *); +static MYSQL_NAME *mysqlname_parse(const char *); +static HOST host_init(char *); + + + +/********************************************************************** + * public interface dict_mysql_lookup + * find database entry return 0 if no alias found, set dict_errno + * on errors to DICT_ERRBO_RETRY and set dict_errno to 0 on success + *********************************************************************/ +static const char *dict_mysql_lookup(DICT *dict, const char *name) +{ + MYSQL_RES *query_res; + MYSQL_ROW row; + DICT_MYSQL *dict_mysql; + PLMYSQL *pldb; + static VSTRING *result; + static VSTRING *query = 0; + int i, + numrows; + char *name_escaped = 0; + dict_mysql = (DICT_MYSQL *) dict; + pldb = dict_mysql->pldb; + /* initialization for query */ + query = vstring_alloc(24); + vstring_strcpy(query, ""); + if ((name_escaped = (char *) mymalloc((sizeof(char) * (strlen(name) * 2) +1))) == NULL) { + msg_fatal("dict_mysql_lookup: out of memory."); + } + /* prepare the query */ + mysql_escape_string(name_escaped, name, (unsigned int) strlen(name)); + vstring_sprintf(query, "select %s from %s where %s = '%s' %s", dict_mysql->name->select_field, + dict_mysql->name->table, dict_mysql->name->where_field, name_escaped, + dict_mysql->name->additional_conditions); + if (msg_verbose) + msg_info("dict_mysql_lookup using sql query: %s", vstring_str(query)); + /* free mem associated with preparing the query */ + myfree(name_escaped); + /* do the query - set dict_errno & cleanup if there's an error */ + if ((query_res = plmysql_query(pldb, + vstring_str(query), + dict_mysql->name->dbname, + dict_mysql->name->username, + dict_mysql->name->password)) == 0) { + dict_errno = DICT_ERR_RETRY; + vstring_free(query); + return 0; + } + dict_errno = 0; + /* free the vstring query */ + vstring_free(query); + numrows = mysql_num_rows(query_res); + if (msg_verbose) + msg_info("dict_mysql_lookup: retrieved %d rows", numrows); + if (numrows == 0) { + mysql_free_result(query_res); + return 0; + } + if (result == 0) + result = vstring_alloc(10); + vstring_strcpy(result, ""); + for (i = 0; i < numrows; i++) { + row = mysql_fetch_row(query_res); + if (msg_verbose > 1) + msg_info("dict_mysql_lookup: retrieved row: %d: %s", i, row[0]); + if (i > 0) + vstring_strcat(result, ","); + vstring_strcat(result, row[0]); + } + mysql_free_result(query_res); + return vstring_str(result); +} + +/* + * plmysql_query - process a MySQL query. Return MYSQL_RES* on success. + * On failure, log failure and try other db instances. + * on failure of all db instances, return 0; + * close unnecessary active connections + */ + +static MYSQL_RES *plmysql_query(PLMYSQL *PLDB, + const char *query, + char *dbname, + char *username, + char *password) +{ + int i; + HOST *host; + MYSQL_RES *res = 0; + + for (i = 0; i < PLDB->len_hosts; i++) { + /* can't deal with typing or reading PLDB->db_hosts[i] over & over */ + host = &(PLDB->db_hosts[i]); + if (msg_verbose > 1) + msg_info("dict_mysql: trying host %s stat %d, last res %p", host->hostname, host->stat, res); + + /* answer already found */ + if (res != 0 && host->stat == STATACTIVE) { + msg_info("dict_mysql: closing unnessary connection to %s", host->hostname); + mysql_close(&(host->db)); /* also frees memory, have to + * reallocate it */ + host->db = *((MYSQL *) mymalloc(sizeof(MYSQL))); + plmysql_down_host(host); + } + /* try to connect for the first time if we don't have a result yet */ + if (res == 0 && host->stat == STATUNTRIED) { + msg_info("dict_mysql: attempting to connect to host %s", host->hostname); + plmysql_connect_single(host, dbname, username, password); + } + + /* + * try to reconnect if we don't have an answer and the host had a + * prob in the past and it's time for it to reconnect + */ + if (res == 0 && host->stat == STATFAIL && (plmysql_ready_reconn(*host))) { + msg_warn("dict_mysql: attempting to reconnect to host %s", host->hostname); + plmysql_connect_single(host, dbname, username, password); + } + + /* + * if we don't have a result and the current host is marked active, + * try the query. If the query fails, mark the host STATFAIL + */ + if (res == 0 && host->stat == STATACTIVE) { + if (!(mysql_query(&(host->db), query))) { + if ((res = mysql_store_result(&(host->db))) == 0) { + msg_warn("%s", mysql_error(&(host->db))); + plmysql_down_host(host); + } else { + if (msg_verbose) + msg_info("dict_mysql: successful query from host %s", host->hostname); + } + } else { + msg_warn("%s", mysql_error(&(host->db))); + plmysql_down_host(host); + } + } + } + return res; +} + +/* + * plmysql_connect_single - + * used to reconnect to a single database when one is down or none is + * connected yet. Log all errors and set the stat field of host accordingly + */ +static void plmysql_connect_single(HOST *host, char *dbname, char *username, char *password) +{ + if (mysql_connect(&(host->db), host->hostname, username, password)) { + if (mysql_select_db(&(host->db), dbname) == 0) { + msg_info("dict_mysql: successful connection to host %s", host->hostname); + host->stat = STATACTIVE; + } else { + plmysql_down_host(host); + msg_warn("%s", mysql_error(&(host->db))); + } + } else { + plmysql_down_host(host); + msg_warn("%s", mysql_error(&(host->db))); + } +} + +/* + * plmysql_down_host - mark a HOST down update ts if marked down + * for the first time so that we'll know when to retry the connection + */ +static void plmysql_down_host(HOST *host) +{ + if (host->stat != STATFAIL) { + host->ts = time(&(host->ts)); + host->stat = STATFAIL; + } +} + +/* + * plmysql_ready_reconn - + * given a downed HOST, return whether or not it should retry connection + */ +static int plmysql_ready_reconn(HOST host) +{ + time_t t; + long now; + + now = (long) time(&t); + if (msg_verbose > 1) { + msg_info("dict_mysql: plmysql_ready_reconn(): now is %d", now); + msg_info("dict_mysql: plmysql_ready_reconn(): ts is %d", (long) host.ts); + msg_info("dict_mysql: plmysql_ready_reconn(): RETRY_CONN_INTV is %d", RETRY_CONN_INTV); + if ((now - ((long) host.ts)) >= RETRY_CONN_INTV) { + msg_info("dict_mysql: plymsql_ready_reconn(): returning TRUE"); + return 1; + } else { + msg_info("dict_mysql: plymsql_ready_reconn(): returning FALSE"); + return 0; + } + } else { + if ((now - ((long) host.ts)) >= RETRY_CONN_INTV) + return 1; + return 0; + } +} + +/********************************************************************** + * public interface dict_mysql_open + * create association with database with appropriate values + * parse the map's config file + * allocate memory + **********************************************************************/ +DICT *dict_mysql_open(const char *name, int unused_flags, int unused_dict_flags) +{ + DICT_MYSQL *dict_mysql; + int connections; + + dict_mysql = (DICT_MYSQL *) mymalloc(sizeof(DICT_MYSQL)); + dict_mysql->dict.lookup = dict_mysql_lookup; + dict_mysql->dict.update = dict_mysql_update; + dict_mysql->dict.close = dict_mysql_close; + dict_mysql->dict.fd = -1; /* there's no file descriptor + * for locking */ + dict_mysql->name = (MYSQL_NAME *) mymalloc(sizeof(MYSQL_NAME)); + dict_mysql->name = mysqlname_parse(name); + dict_mysql->pldb = plmysql_init(dict_mysql->name->hostnames, + dict_mysql->name->len_hosts); + if (dict_mysql->pldb == NULL) + msg_fatal("couldn't intialize pldb!\n"); + dict_register(name, (DICT *) dict_mysql); + return &dict_mysql->dict; +} + +/* mysqlname_parse - parse mysql configuration file */ static MYSQL_NAME *mysqlname_parse(const char *mysqlcf_path) { int i; @@ -107,14 +371,14 @@ static MYSQL_NAME *mysqlname_parse(const char *mysqlcf_path) else name->username = mystrdup(nameval); if (msg_verbose) - msg_info("dict_mysql_parse: set username to '%s'", name->username); + msg_info("mysqlname_parse(): set username to '%s'", name->username); /* password lookup */ if ((nameval = (char *) dict_lookup("mysql_options", "password")) == NULL) name->password = mystrdup(""); else name->password = mystrdup(nameval); if (msg_verbose) - msg_info("dict_mysql_parse: set password to '%s'", name->password); + msg_info("mysqlname_parse(): set password to '%s'", name->password); /* database name lookup */ if ((nameval = (char *) dict_lookup("mysql_options", "dbname")) == NULL) @@ -122,7 +386,7 @@ static MYSQL_NAME *mysqlname_parse(const char *mysqlcf_path) else name->dbname = mystrdup(nameval); if (msg_verbose) - msg_info("mysql_name_parse: set database name to '%s'", name->dbname); + msg_info("mysqlname_parse(): set database name to '%s'", name->dbname); /* table lookup */ if ((nameval = (char *) dict_lookup("mysql_options", "table")) == NULL) @@ -130,7 +394,7 @@ static MYSQL_NAME *mysqlname_parse(const char *mysqlcf_path) else name->table = mystrdup(nameval); if (msg_verbose) - msg_info("mysql_name_parse: set table name to '%s'", name->table); + msg_info("mysqlname_parse(): set table name to '%s'", name->table); /* select field lookup */ if ((nameval = (char *) dict_lookup("mysql_options", "select_field")) == NULL) @@ -138,7 +402,7 @@ static MYSQL_NAME *mysqlname_parse(const char *mysqlcf_path) else name->select_field = mystrdup(nameval); if (msg_verbose) - msg_info("mysql_name_parse: set select_field to '%s'", name->select_field); + msg_info("mysqlname_parse(): set select_field to '%s'", name->select_field); /* where field lookup */ if ((nameval = (char *) dict_lookup("mysql_options", "where_field")) == NULL) @@ -146,7 +410,7 @@ static MYSQL_NAME *mysqlname_parse(const char *mysqlcf_path) else name->where_field = mystrdup(nameval); if (msg_verbose) - msg_info("mysql_name_parse: set where_field to '%s'", name->where_field); + msg_info("mysqlname_parse(): set where_field to '%s'", name->where_field); /* additional conditions */ if ((nameval = (char *) dict_lookup("mysql_options", "additional_conditions")) == NULL) @@ -154,7 +418,7 @@ static MYSQL_NAME *mysqlname_parse(const char *mysqlcf_path) else name->additional_conditions = mystrdup(nameval); if (msg_verbose) - msg_info("mysql_name_parse: set additional_conditions to '%s'", name->additional_conditions); + msg_info("mysqlname_parse(): set additional_conditions to '%s'", name->additional_conditions); /* mysql server hosts */ if ((nameval = (char *) dict_lookup("mysql_options", "hosts")) == NULL) @@ -167,7 +431,7 @@ static MYSQL_NAME *mysqlname_parse(const char *mysqlcf_path) if (hosts_argv->argc == 0) { /* no hosts specified, * default to 'localhost' */ - msg_info("mysql_name_parse: no hostnames specified, defaulting to 'localhost'"); + msg_info("mysqlname_parse(): no hostnames specified, defaulting to 'localhost'"); name->len_hosts = 1; name->hostnames = (char **) mymalloc(sizeof(char *)); name->hostnames[0] = mystrdup("localhost"); @@ -178,7 +442,8 @@ static MYSQL_NAME *mysqlname_parse(const char *mysqlcf_path) for (i = 0; hosts_argv->argv[i] != NULL; i++) { name->hostnames[i] = mystrdup(hosts_argv->argv[i]); if (msg_verbose) - msg_info("adding host '%s' to list of mysql server hosts", name->hostnames[i]); + msg_info("mysqlname_parse(): adding host '%s' to list of mysql server hosts", + name->hostnames[i]); } } myfree(hosts); @@ -186,124 +451,31 @@ static MYSQL_NAME *mysqlname_parse(const char *mysqlcf_path) return name; } -/* dict_mysql_lookup - find database entry return 0 if no alias found */ -static const char *dict_mysql_lookup(DICT *dict, const char *name) -{ - MYSQL_RES *query_res; - MYSQL_ROW row; - int i, - numrows; - static VSTRING *result; - static VSTRING *query = 0; - char *name_escaped = 0; - DICT_MYSQL *dict_mysql; - PLMYSQL *pldb; - dict_mysql = (DICT_MYSQL *) dict; - pldb = dict_mysql->pldb; - /* initialization for query */ - query = vstring_alloc(24); - vstring_strcpy(query, ""); - if ((name_escaped = (char *) mymalloc((sizeof(char) * (strlen(name) * 2) +1))) == NULL) { - msg_fatal("dict_mysql_lookup: out of memory."); - } - /* prepare the query */ - mysql_escape_string(name_escaped, name, (unsigned int) strlen(name)); - vstring_sprintf(query, "select %s from %s where %s = '%s' %s", dict_mysql->name->select_field, - dict_mysql->name->table, dict_mysql->name->where_field, name_escaped, - dict_mysql->name->additional_conditions); - if (msg_verbose) - msg_info("dict_mysql_lookup using sql query: %s", vstring_str(query)); - /* free mem associated with preparing the query */ - myfree(name_escaped); - /* do the query */ - if ((query_res = plmysql_query(pldb, vstring_str(query))) == NULL) { - dict_errno = DICT_ERR_RETRY; - vstring_free(query); - return 0; - } - dict_errno = 0; - /* free the vstring query */ - vstring_free(query); - numrows = mysql_num_rows(query_res); - if (msg_verbose) - msg_info("dict_mysql_lookup: retrieved %d rows", numrows); - if (numrows == 0) { - mysql_free_result(query_res); - return 0; - } - if (result == 0) - result = vstring_alloc(10); - vstring_strcpy(result, ""); - for (i = 0; i < numrows; i++) { - row = mysql_fetch_row(query_res); - if (msg_verbose > 1) - msg_info("dict_mysql_lookup: retrieved row: %d: %s", i, row[0]); - if (i > 0) - vstring_strcat(result, ","); - vstring_strcat(result, row[0]); - } - mysql_free_result(query_res); - return vstring_str(result); -} - -/* dict_mysql_close - unregister, disassociate from database */ -static void dict_mysql_close(DICT *dict) +/* + * plmysql_init - initalize a MYSQL database. + * Return NULL on failure, or a PLMYSQL * on success. + */ +static PLMYSQL *plmysql_init(char *hostnames[], + int len_hosts) { + PLMYSQL *PLDB; + MYSQL *dbs; int i; - DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict; + HOST host; - plmysql_dealloc(dict_mysql->pldb); - myfree(dict_mysql->name->username); - myfree(dict_mysql->name->password); - myfree(dict_mysql->name->dbname); - myfree(dict_mysql->name->table); - myfree(dict_mysql->name->select_field); - myfree(dict_mysql->name->where_field); - myfree(dict_mysql->name->additional_conditions); - for (i = 0; i < dict_mysql->name->len_hosts; i++) { - myfree(dict_mysql->name->hostnames[i]); + if ((PLDB = (PLMYSQL *) mymalloc(sizeof(PLMYSQL))) == NULL) { + msg_fatal("mymalloc of pldb failed"); } - myfree((char *) dict_mysql->name); -} - -/* dict_mysql_update - add or update table entry */ -static void dict_mysql_update(DICT *dict, const char *unused_name, const char *unused_value) -{ - DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict; - - msg_fatal("dict_mysql_update: attempt to update mysql database"); + PLDB->len_hosts = len_hosts; + if ((PLDB->db_hosts = (HOST *) mymalloc(sizeof(HOST) * len_hosts)) == NULL) + return NULL; + for (i = 0; i < len_hosts; i++) { + PLDB->db_hosts[i] = host_init(hostnames[i]); + } + return PLDB; } -/* dict_mysql_open - create association with database */ -DICT *dict_mysql_open(const char *name, int unused_flags, int unused_dict_flags) -{ - DICT_MYSQL *dict_mysql; - int connections; - - dict_mysql = (DICT_MYSQL *) mymalloc(sizeof(DICT_MYSQL)); - dict_mysql->dict.lookup = dict_mysql_lookup; - dict_mysql->dict.update = dict_mysql_update; - dict_mysql->dict.close = dict_mysql_close; - dict_mysql->dict.fd = -1; /* there's no file descriptor - * for locking */ - dict_mysql->name = (MYSQL_NAME *) mymalloc(sizeof(MYSQL_NAME)); - dict_mysql->name = mysqlname_parse(name); - dict_mysql->pldb = plmysql_init(dict_mysql->name->dbname, - dict_mysql->name->hostnames, - dict_mysql->name->len_hosts); - if (dict_mysql->pldb == NULL) - msg_fatal("couldn't intialize pldb!\n"); - connections = plmysql_connect(dict_mysql->pldb, dict_mysql->name->username, - dict_mysql->name->password); - if (connections == 0) - /* the mysql lookup mechanism will try to reconnect anyway ... */ - msg_warn("couldn't connect pldb to any database instances"); - else - msg_info("pldb connected to %d database instances", connections); - dict_register(name, (DICT *) dict_mysql); - return &dict_mysql->dict; -} /* host_init - initialize HOST structure */ static HOST host_init(char *hostname) @@ -320,34 +492,31 @@ static HOST host_init(char *hostname) return host; } -/* - * plmysql_init - initalize a MYSQL database. - * Return NULL on failure, or a PLMYSQL * on success. - */ -PLMYSQL *plmysql_init(char *dbname, - char *hostnames[], - int len_hosts) +/********************************************************************** + * public interface dict_mysql_close + * unregister, disassociate from database, freeing appropriate memory + **********************************************************************/ +static void dict_mysql_close(DICT *dict) { - PLMYSQL *PLDB; - MYSQL *dbs; int i; - HOST host; + DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict; - if ((PLDB = (PLMYSQL *) mymalloc(sizeof(PLMYSQL))) == NULL) { - msg_fatal("mymalloc of pldb failed"); - } - PLDB->dbname = dbname; - PLDB->len_hosts = len_hosts; - if ((PLDB->db_hosts = (HOST *) mymalloc(sizeof(HOST) * len_hosts)) == NULL) - return NULL; - for (i = 0; i < len_hosts; i++) { - PLDB->db_hosts[i] = host_init(hostnames[i]); + plmysql_dealloc(dict_mysql->pldb); + myfree(dict_mysql->name->username); + myfree(dict_mysql->name->password); + myfree(dict_mysql->name->dbname); + myfree(dict_mysql->name->table); + myfree(dict_mysql->name->select_field); + myfree(dict_mysql->name->where_field); + myfree(dict_mysql->name->additional_conditions); + for (i = 0; i < dict_mysql->name->len_hosts; i++) { + myfree(dict_mysql->name->hostnames[i]); } - return PLDB; + myfree((char *) dict_mysql->name); } /* plmysql_dealloc - free memory associated with PLMYSQL close databases */ -void plmysql_dealloc(PLMYSQL *PLDB) +static void plmysql_dealloc(PLMYSQL *PLDB) { int i; @@ -359,96 +528,16 @@ void plmysql_dealloc(PLMYSQL *PLDB) myfree((char *) (PLDB)); } -/* plmysql_down_host - down a HOST * */ -inline void plmysql_down_host(HOST *host) -{ - if (host->stat != STATFAIL) - host->ts = time(&(host->ts)); - host->stat = STATFAIL; -} - -/* plmysql_connect_single - - * used to reconnect to a single database when one is down and as a helper for - * plmysql_connect - */ -int plmysql_connect_single(PLMYSQL *PLDB, int host) -{ - if ((mysql_connect(&(PLDB->db_hosts[host].db), PLDB->db_hosts[host].hostname, - PLDB->username, PLDB->password))) { - if (mysql_select_db(&(PLDB->db_hosts[host].db), PLDB->dbname) == 0) { - PLDB->db_hosts[host].stat = STATACTIVE; - return 1; - } else { - plmysql_down_host(&(PLDB->db_hosts[host])); - msg_warn("%s", mysql_error(&PLDB->db_hosts[host].db)); - } - } else { - plmysql_down_host(&(PLDB->db_hosts[host])); - msg_warn("%s", mysql_error(&PLDB->db_hosts[host].db)); - } - return 0; -} - -/* - * plmysql_connect - - * given a PLMYSQL struct PLDB *, connect it and select db. - * return the number of databases successfully connected (0 for failure) - */ -int plmysql_connect(PLMYSQL *PLDB, char *username, char *password) -{ - int i, - res; - - res = 0; - - PLDB->username = username; - PLDB->password = password; - - for (i = 0; i < PLDB->len_hosts; i++) { - res = res + plmysql_connect_single(PLDB, i); - } - return res; -} - -/* plmysql_ready_reconn - - given a downed HOST, return whether or not it should retry connection -*/ -int plmysql_ready_reconn(HOST host) -{ - time_t t; - long now; - - now = (long) time(&t); - if ((now - ((long) host.ts)) >= RETRY_CONN_INTV) - return 1; - return 0; -} - -/* - * plmysql_query - process a MySQL query. Return 0 on success. - * On failure, log failure and try other db instances. - */ -MYSQL_RES *plmysql_query(PLMYSQL *PLDB, const char *query) +/********************************************************************** + * public interface dict_mysql_update - add or update table entry + * + *********************************************************************/ +static void dict_mysql_update(DICT *dict, const char *unused_name, const char *unused_value) { - int i; - MYSQL_RES *res; + DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict; - for (i = 0; i < PLDB->len_hosts; i++) { - if ((PLDB->db_hosts[i].stat != STATACTIVE) && - (plmysql_ready_reconn(PLDB->db_hosts[i]))) { - msg_warn("attempting to reconnect to host"); - plmysql_connect_single(PLDB, i); - continue; - } - if ((!(mysql_query(&(PLDB->db_hosts[i].db), query))) && \ - (res = mysql_store_result(&(PLDB->db_hosts[i].db)))) { - return res; - } - msg_warn("%s", mysql_error(&PLDB->db_hosts[i].db)); - plmysql_down_host(&(PLDB->db_hosts[i])); - } - return NULL; + msg_fatal("dict_mysql_update: attempt to update mysql database"); } #endif diff --git a/postfix/util/dict_mysql.h b/postfix/util/dict_mysql.h index 601ccda34..fdb9395d3 100644 --- a/postfix/util/dict_mysql.h +++ b/postfix/util/dict_mysql.h @@ -6,41 +6,9 @@ #define STATACTIVE 0 #define STATFAIL 1 #define STATUNTRIED 2 -#define RETRY_CONN_INTV 300 /* 5 minutes */ +#define RETRY_CONN_INTV 60 /* 1 minute */ extern DICT *dict_mysql_open(const char *name, int unused_flags, int dict_flags); -typedef struct { - char *hostname; - int stat; /* STATUNTRIED | STATFAIL | STATCUR */ - time_t ts; /* used for attempting reconnection - * every so often if a host is down */ - MYSQL db; -} HOST; - - -typedef struct { - char *username; /* login for database */ - char *password; /* password for database */ - char *dbname; /* the name of the database on all - * the servers */ - HOST *db_hosts; /* the hosts on which the databases - * reside */ - int len_hosts; /* number of hosts */ -} PLMYSQL; - -extern PLMYSQL *plmysql_init(char *dbname, char *hostnames[], int len_hosts); - -extern int plmysql_connect(PLMYSQL *PLDB, char *username, char *password); - -MYSQL_RES *plmysql_query(PLMYSQL *PLDB, const char *query); - -void plmysql_dealloc(PLMYSQL *PLDB); - -inline void plmysql_down_host(HOST *host); - -int plmysql_connect_single(PLMYSQL *PLDB, int host); - -int plmysql_ready_reconn(HOST host); #endif