/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:
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,
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
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.
-Incompatible changes with snapshot 19991127
+Incompatible changes with snapshot 19991209
===========================================
- In an SMTPD access map, an all-numeric right-hand side now means
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
# 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/
#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
#
# 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/
#
# 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:
#
# 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
#
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
/* 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
/* 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:
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);
+}
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
* 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
postalias - Postfix alias database maintenance
<b>SYNOPSIS</b>
- <b>postalias</b> [<b>-ivw</b>] [<b>-c</b> <i>config_dir</i>] [<b>-q</b> <i>key</i>]
+ <b>postalias</b> [<b>-Ninvw</b>] [<b>-c</b> <i>config_dir</i>] [<b>-q</b> <i>key</i>]
[<i>file_type</i>:]<i>file_name</i> ...
<b>DESCRIPTION</b>
Options:
+ <b>-N</b> 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.
+
<b>-c</b> <i>config_dir</i>
- Read the <b>main.cf</b> configuration file in the named
+ Read the <b>main.cf</b> configuration file in the named
directory.
- <b>-i</b> Incremental mode. Read entries from standard input
+ <b>-i</b> Incremental mode. Read entries from standard input
and do not truncate an existing database. By
- default, <b>postalias</b> creates a new database from the
+ default, <b>postalias</b> creates a new database from the
entries in <b>file</b><i>_</i><b>name</b>.
+ <b>-n</b> 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.
+
<b>-q</b> <i>key</i> Search the specified maps for <i>key</i> and print the
first value found on the standard output stream.
The exit status is non-zero if the requested infor-
Arguments:
- <i>file_type</i>
- The type of database to be produced.
-
- <b>btree</b> The output is a btree file, named
- <i>file_name</i><b>.db</b>. This is available only on
- systems with support for <b>db</b> databases.
-
- <b>dbm</b> The output consists of two files, named
- <i>file_name</i><b>.pag</b> and <i>file_name</i><b>.dir</b>. This is
- available only on systems with support for
POSTALIAS(1) POSTALIAS(1)
+ <i>file_type</i>
+ The type of database to be produced.
+
+ <b>btree</b> The output is a btree file, named
+ <i>file_name</i><b>.db</b>. This is available only on
+ systems with support for <b>db</b> databases.
+
+ <b>dbm</b> The output consists of two files, named
+ <i>file_name</i><b>.pag</b> and <i>file_name</i><b>.dir</b>. This is
+ available only on systems with support for
<b>dbm</b> databases.
<b>hash</b> The output is a hashed file, named
<a href="aliases.5.html">aliases(5)</a> format of alias database input file.
<a href="sendmail.1.html">sendmail(1)</a> mail posting and compatibility interface.
+
+
+
+
+ 2
+
+
+
+
+
+POSTALIAS(1) POSTALIAS(1)
+
+
<b>LICENSE</b>
The Secure Mailer license must be distributed with this
software.
- 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
</pre> </body> </html>
postmap - Postfix lookup table management
<b>SYNOPSIS</b>
- <b>postmap</b> [<b>-ivw</b>] [<b>-c</b> <i>config_dir</i>] [<b>-q</b> <i>key</i>]
+ <b>postmap</b> [<b>-Ninvw</b>] [<b>-c</b> <i>config_dir</i>] [<b>-q</b> <i>key</i>]
[<i>file_type</i>:]<i>file_name</i> ...
<b>DESCRIPTION</b>
Options:
+ <b>-N</b> 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.
+
<b>-c</b> <i>config_dir</i>
- Read the <b>main.cf</b> configuration file in the named
+ Read the <b>main.cf</b> configuration file in the named
directory.
- <b>-i</b> Incremental mode. Read entries from standard input
+ <b>-i</b> Incremental mode. Read entries from standard input
and do not truncate an existing database. By
- default, <b>postmap</b> creates a new database from the
+ default, <b>postmap</b> creates a new database from the
entries in <b>file</b><i>_</i><b>name</b>.
- <b>-q</b> <i>key</i> Search the specified maps for <i>key</i> 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.
-
POSTMAP(1) POSTMAP(1)
+ <b>-n</b> 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.
+
+ <b>-q</b> <i>key</i> Search the specified maps for <i>key</i> 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.
+
<b>-v</b> Enable verbose logging for debugging purposes. Mul-
tiple <b>-v</b> options make the software increasingly
verbose.
<b>MAIL</b><i>_</i><b>VERBOSE</b>
Enable verbose logging for debugging purposes.
-<b>CONFIGURATION</b> <b>PARAMETERS</b>
- <b>database</b><i>_</i><b>type</b>
- Default output database type. On many UNIX sys-
- tems, the default database type is either <b>hash</b> or
- <b>dbm</b>.
-
-<b>LICENSE</b>
- The Secure Mailer license must be distributed with this
- software.
-
2
POSTMAP(1) POSTMAP(1)
+<b>CONFIGURATION</b> <b>PARAMETERS</b>
+ <b>database</b><i>_</i><b>type</b>
+ Default output database type. On many UNIX sys-
+ tems, the default database type is either <b>hash</b> or
+ <b>dbm</b>.
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this
+ software.
+
<b>AUTHOR(S)</b>
Wietse Venema
IBM T.J. Watson Research
-
-
-
-
-
-
-
-
-
-
<dt> <b>permit_auth_destination</b> <dd> Ignore the client hostname.
Permit the request when the resolved destination address matches
-the <a href="basic.html#mydestination">$mydestination</a>, the
+<a href="basic.html#mydestination">$mydestination</a>, the
machine IP addresses, or <a href="#relay_domains"> $relay_domains</a>.
<p>
* 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 */
}
/*
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);
.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
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
.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
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
/* 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
/* 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
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);
}
/*
* 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");
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;
struct stat st;
int junk;
+ /*
+ * Be consistent with file permissions.
+ */
umask(022);
/*
/* 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
/* 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
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);
}
/*
* 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");
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;
}
/*
- * 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;
/*
* 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)
* 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)
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));
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);
/* 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
#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;
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;
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)
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)
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)
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)
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)
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)
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");
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);
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)
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;
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
#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