Paranoia: defend against a very unlikely false alarm in
safe_open().
-20020125
+20021025
Feature: X-Original-To: message headers with the raw original
envelope recipient.
the original recipient address if it differs from the final
address.
-20020126
+20021026
Logging: SMTP UCE reject/warn/hold/discard logging now
includes queue ID. This will break some logfile analyzers.
Workaround: DJBDNS produces a bogus A record when given a
numerical hostname. File: dns/dns_lookup.c.
+20021030
+
+ Portability: support for Berkeley DB version 4.0 but not
+ for Berkeley DB version 4.1 (yes, the API is different).
+ Postfix is now going to be paranoid about the minor version
+ number, too. File: util/dict_db.c.
+
+ Documentation: updated LMTP_README file by Amos Gouaux.
+
+20021031
+
+ Bugfix: (bug introduced 20021026) log NOQUEUE when rejecting
+ ETRN, instead of trying to log a non-existent queue ID.
+ Victor Duchovni, Morgan Stanley. File: smtpd/smtpd_check.c.
+
+ Cleanup: allow optional text after commands in SMTPD access
+ maps. Based on initial effort by Victor Duchovni, Morgan
+ Stanley. File: smtpd/smtpd_check.c.
+
+ Portability: support for Berkeley DB version 4.1. This
+ version refuses to open zero-length files. This complicates
+ lock management and requires extra code to remove broken
+ files. Files: util/dict_db.c, global/mkmap*.[hc].
+
Open problems:
Low: revise other local delivery agent duplicate filters.
-[This file still needs to be updated - some information is obsolete]
-
1 - Postfix LMTP support
========================
This protocol opens up interesting possibilities: one Postfix front
end machine can drive multiple mailbox back end machines over LMTP.
As the mail load increases, you add more Postfix front end systems
-and more LMTP mailbox back end systems. This is the model that I
-had in mind when I began drafting the design for Postfix - a scalable
-architecture that allows you to keep adding SMTP servers and mailbox
-servers painlessly.
+and more LMTP mailbox back end systems. This is the model that
+Wietse had in mind when he began drafting the design for Postfix
+- a scalable architecture that allows you to keep adding SMTP
+servers and mailbox servers painlessly.
Such a distributed architecture needs glue to keep things together.
-You can use a networked database LDAP or mysql to share the user
+You can use a networked database (LDAP or mysql) to share the user
database among the front end and back end systems. Use a replicated
database so that no machine becomes a single point of failure for
-the entire mail infrastructure.
+the entire mail infrastructure. Or you can use rsync when files
+are small and/or when information does not change often.
Postfix LMTP support is based on a modified version of the Postfix
SMTP client. The initial version was by Philip A. Prindeville of
While certainly not the only application that could make use of
LMTP, it tends to be the most discussed. These examples are based
-on the forthcoming Cyrus 2.0.10, at least at the time of writing.
-The 2.x branch of Cyrus places greater emphasis on LMTP delivery
-than the previous releases. Those using older releases of Cyrus
-can find a discussion in the appendix of this document.
+on Cyrus 2.1.5. The 2.x branch of Cyrus places greater emphasis on
+LMTP delivery than the previous releases. Those using older releases
+of Cyrus can find a discussion in the appendix of this document.
There are a variety of ways LMTP delivery can be configured in
Postfix. The two basic flavors are delivery over UNIX-domain
Both socket flavors can be specified in either the Postfix main.cf
file (see section 5) or in a Postfix transport map (section 6).
What is the best approach for you depends upon the arrangement of
-your servers and the desired level of parallelization.
+your servers.
-Please be sure to study this entire document as there are trade-offs
-in convenience and in performance with these different approaches.
3 - LMTP over UNIX-domain sockets
=================================
With LMTP delivery to the local machine there is no good reason
to run the Postfix LMTP client chrooted.
+
4 - LMTP over TCP sockets
=========================
The destination port can be omitted as well. Currently the default
TCP port number for this type of connection is 24, but this can be
-customized in the "/etc/services" file. Specific examples are
+customized in the /etc/services file. Specific examples are
given later in this document.
NOTE:
- With connections over TCP sockets, later Cyrus LMTP server
- implementations insist on SASL-style authentication. This means
- that Postfix must be built with SASL support (see SASL_README).
- The examples below show how to enable this in the Postfix LMTP
- client.
+ With connections over TCP sockets, Cyrus 2.0.x LMTP server
+ implementations insisted on SASL-style authentication. This
+ meant that Postfix had to be built with SASL support (see
+ SASL_README). While newer Cyrus releases offer an option to
+ turn off this requirement, you must exercise great care in
+ both determining the approach used and the configuration of
+ your selection. It is imperative that you do not allow
+ unauthorized access to your LMTP server. The examples below
+ show both approaches.
Some Cyrus LMTP server implementations do not allow SASL-style
- authentication via plaintext passwords. You will have to jump
- some extra hoops in order to enable MD5 password support, or
- you will have to wait until this restriction is relaxed.
+ authentication via plaintext passwords over unencrypted
+ connections. This is not the case with 2.1.5. However, you
+ must realize that this could make your LMTP link vulnerable.
+ If your LMTP communications traverse exposed networks, you
+ should either use an encrypted connection or enable MD5 SASL
+ mechanisms.
5 - Configuring LMTP using main.cf configuration
Postfix and Cyrus, the corresponding configuration files should
look something like this:
-/etc/cyrus.conf:
-
- SERVICES {
- ...
- lmtpunix cmd="lmtpd" listen="/var/imap/socket/lmtp" prefork=1
- ...
- }
-
-/etc/postfix/main.cf:
-
- mailbox_transport = lmtp:unix:/var/imap/socket/lmtp
+ /etc/cyrus.conf:
+ SERVICES {
+ ...
+ lmtpunix cmd="lmtpd" listen="/var/imap/socket/lmtp" prefork=1
+ ...
+ }
-/etc/postfix/master.cf:
+ /etc/postfix/main.cf:
+ mailbox_transport = lmtp:unix:/var/imap/socket/lmtp
- lmtp unix - - n - - lmtp
+ /etc/postfix/master.cf:
+ lmtp unix - - n - - lmtp
In this case, the Postfix local delivery agent expands aliases
and .forward files, and delegates mailbox delivery to the Cyrus
lmtpd server via the socket "/var/imap/socket/lmtp".
-5.2.2 - LMTP over TCP sockets
------------------------------
+NOTE:
+
+ Make sure that both the user id that Cyrus runs under and the
+ Postfix user id can access the socket "/var/imap/socket/lmtp".
+ While this is implied by the example above, it is often
+ overlooked and so warrants emphasis.
+
+5.2.2 - LMTP over TCP sockets (non-SASL)
+----------------------------------------
For this example, suppose the following files are configured
thusly:
-/etc/cyrus.conf:
+ /etc/cyrus.conf:
+ SERVICES {
+ ...
+ lmtp cmd="lmtpd -a" listen="127.0.0.1:lmtp" prefork=1
+ ...
+ }
+
+ /etc/services:
+ lmtp 2003/tcp
+
+ /etc/postfix/main.cf:
+ mailbox_transport = lmtp:localhost
+
+ /etc/postfix/master.cf:
+ lmtp unix - - n - - lmtp
+
+With the above settings, the Postfix local delivery agent expands
+aliases and .forward files, and delegates mailbox delivery to the
+Cyrus LMTP server. Postfix makes a connection to port 2003 on the
+local host, subsequently transmitting the message to the lmtpd
+server managed by the Cyrus master process. The port number has
+been changed in /etc/services from 24 to 2003 as that is what the
+Cyrus LMTP server is now using.
+
+See the Cyrus lmtpd(8) man page to verify that the version you
+have supports the "-a" option.
+
+NOTE:
+
+ Consider this approach if and only if this particular host
+ does not allow direct user logins or user-controlled
+ processes. Otherwise, this LMTP server may be abused!
+
+ If the Cyrus lmtp service is to listen on a network other
+ than the local loop-back (127.0.0.1), be sure to install
+ Cyrus with tcp_wrappers support. Then use tcp_wrappers
+ to only allow access to the "lmtp" service from trusted
+ hosts. Otherwise, this LMTP server may be abused!
+
+ Section 10 contains an example using tcp_wrappers. For the
+ configuration above, replace "deliver" with "lmtp" in the
+ /etc/hosts.allow shown in that example.
- SERVICES {
- ...
- lmtp cmd="lmtpd" listen="127.0.0.1:lmtp" prefork=0
- ...
- }
+5.2.3 - LMTP over TCP sockets (SASL)
+------------------------------------
-/etc/services:
+In the following example "cyhost.my.domain" is the name of the Cyrus
+IMAP/POP server. It is important that you use this name consistently
+in the Postfix configuration settings. The first approach uses the
+PLAIN authentication mechanism (see the Cyrus SASL documentation.)
- lmtp 24/tcp
+Suppose the Cyrus server has the following files configured thusly:
-/etc/postfix/main.cf:
+ /etc/cyrus.conf:
+ SERVICES {
+ ...
+ lmtp cmd="lmtpd" listen="cyhost.my.domain:lmtp" prefork=1
+ ...
+ }
- mailbox_transport = lmtp:localhost
- lmtp_sasl_auth_enable = yes
- lmtp_sasl_password_maps = hash:/etc/postfix/lmtp_sasl_pass
+ /etc/imapd.conf:
+ lmtp_admins: lmtpuser
-/etc/postfix/lmtp_sasl_pass:
- localhost.my.domain username:password
+ /etc/services:
+ lmtp 2003/tcp
+
+The Postfix host (may be same box) has the following configuration:
-/etc/postfix/master.cf:
+ /etc/postfix/main.cf:
+ mailbox_transport = lmtp:cyhost.my.domain
+ lmtp_sasl_auth_enable = yes
+ lmtp_sasl_password_maps = hash:/etc/postfix/lmtp_sasl_pass
+ lmtp_sasl_security_options = noanonymous
- lmtp unix - - n - - lmtp
+ /etc/postfix/lmtp_sasl_pass:
+ cyhost.my.domain lmtpuser:password
+
+ /etc/services:
+ lmtp 2003/tcp
+
+ /etc/postfix/master.cf:
+ lmtp unix - - n - - lmtp
Instead of "hash", use the map type of your choice. Some systems
use "dbm" instead. Use "postconf -m" to find out what map types
are supported.
+If your version of Cyrus does not support "lmtp_admins" as a
+setting in imapd.conf, use "admins" instead.
+
With the above settings, the Postfix local delivery agent expands
aliases and .forward files, and delegates mailbox delivery to the
-Cyrus LMTP server. Postfix makes a connection to port 24 on the
+Cyrus LMTP server. Postfix makes a connection to port 2003 on the
local host, subsequently transmitting the message to the lmtpd
-server managed by the Cyrus master process.
+server managed by the Cyrus master process. The port number has
+been changed in /etc/services from 24 to 2003 as that is what the
+Cyrus LMTP server is now using. The SASL configuration on the Cyrus
+server will need to accept the user "lmtpuser" using the password
+specified in /etc/postfix/lmtp_sasl_pass on the Postfix host.
+
+If this LMTP conduit exists over an exposed network, you should
+compile Postfix with MD5 (CRAM or DIGEST) password support. See
+SASL_README for more details. Then configure Postfix as follows:
+
+ /etc/postfix/main.cf:
+ lmtp_sasl_security_options = noanonymous, noplaintext
+
+On the Cyrus host you should also set:
+
+ /etc/imapd.conf:
+ lmtp_allowplaintext: no
+
+You will need to make sure the "lmtpuser" is in the appropriate
+SASL database. As an example, the following would add "lmtpuser"
+to /etc/sasldb2:
+
+ saslpasswd2 -c -u cyhost.my.domain lmtpuser
+
+If you encounter difficulties with "lmtpuser" not being permitted
+to authenticate to the LMTP server, try the above command with the
+un-qualified hostname:
+
+ saslpasswd2 -c -u cyhost lmtpuser
+
+Also make sure the Cyrus user has read permission of the SASL
+database, /etc/sasldb2 in the example above.
+
+Incidentally, it is very likely that the Cyrus server and the
+Postfix server will need to use the same SASL backend databases
+(e.g., auxprop or saslauthd.) Currently it is not possible to
+assign different SASL backends for different Cyrus services.
+Only TLS (SSL) or STARTTLS can be used in conjunction with
+saslauthd. Since none of these encryption methods are available
+for LMTP, if you need to encrypt your LMTP connections, you will
+very likely have to use auxprop throughout.
6 - Configuring LMTP using transport map configuration
(IMAP/POP) server. Example:
/etc/postfix/transport:
-
domain1.tld lmtp1:unix:/path/name
domain2.tld lmtp2:lmtp2host
/etc/postfix/master.cf:
-
lmtp1 unix - - n - - lmtp
lmtp2 unix - - n - - lmtp
/etc/postfix/main.cf:
-
transport_maps = hash:/etc/postfix/transport
For details of the Cyrus LMTP server configuration, see section 5.
Depending on your user base, this can be considerable motivation
to using LMTP.
-However, there is a catch: the Postfix local delivery agent is
-designed to deliver one recipient at a time, which in most cases
-is more than adequate. So, if you wish to support single instance
-message store delivery, you will have to use a virtual table to
-map these users to the appropriate LMTP destination (at the time
-of writing, the Postfix transport table supports only per-domain
-routing, and not per-recipient routing).
-
-While the simplest thing to do would be to list the entire domain
-in the transport map for LMTP delivery, this by-passes alias
-expansion for otherwise local addresses (see section 5.1, delivery
-mechanism 2). If the site is to run software via aliases, like
-most Mailing List Management (MLM) software, a more complex solution
-is required. A virtual table should do the trick.
-
-As an example, suppose we wanted to support single instance message
-store delivery for the hosted (not local) domain "example.org".
-The configuration files for this domain could look something like
-this:
+With the examples in section 5.2, we can increase the number of
+recipients (to $mydestination) that can be handled at once:
- /etc/postfix/virtual:
-
- mlist@example.org mlist@localhost
-
- /etc/postfix/transport:
-
- example.org lmtp:unix:/var/imap/socket/lmtp
+ /etc/postfix/main.cf:
+ local_destination_recipient_limit = 300
- /etc/postfix/aliases:
+The 300 was arbitrarily chosen for this example. Be sure to pick a
+number that is appropriate for the capabilities of your hardware.
+The bigger the number, the more you can leverage the single instance
+message store. However, if it is too big the LMTP server will need
+too much time to deliver a message and Postfix will experience
+timeout errors. Choose this value very carefully.
- mlist: "|/path/to/mlm/software"
+NOTE:
- /etc/postfix/master.cf:
+ Not all local delivery agent transports can support a recipient
+ limit greater than 1. Be sure to check the man page of the
+ specific transport before attempting this with anything but the
+ Postfix LMTP client.
- lmtp unix - - n - - lmtp
+If we wish to apply this single instance message store technique
+with the configuration example in section 6, the setting would be:
/etc/postfix/main.cf:
+ lmtp1_destination_recipient_limit = 300
+ lmtp2_destination_recipient_limit = 300
- mydestination = localhost, $myhostname, $mydomain
- virtual_maps = hash:/etc/postfix/virtual
- transport_maps = hash:/etc/postfix/transport
- alias_maps = hash:/etc/postfix/aliases
- alias_database = hash:/etc/postfix/aliases
-
- /etc/cyrus.conf:
-
- SERVICES {
- ...
- lmtpunix cmd="lmtpd" listen="/var/imap/socket/lmtp" prefork=1
- ...
- }
-
-Breaking things down, we begin with the address "mlist@example.org",
-which represents a mailing list. By placing an entry in the virtual
-map to direct this mail to "mlist@localhost", we can override the
-transport map that would by default route all "@example.org" mail
-to a LMTP server via a UNIX-domain socket.
-
-To summarize, all mail that is to be processed by an alias entry
-must first be diverted with a virtual table entry so that it does
-not fall into the more general routing established by the transport
-table.
+As previously mentioned, exercise tremendous care backed by
+extensive analysis of your systems before setting the recipient
+limit like this.
9 - Improving connection caching performance
configuring a separate LMTP delivery transport for each LMTP server:
/etc/postfix/master.cf:
-
lmtp1 unix - - n - - lmtp
lmtp2 unix - - n - - lmtp
. . . . . . . .
mail lmtp2 transport for the LMTP server #2, and so on.
/etc/postfix/transport:
-
foo.com lmtp1:lmtp1host
bar.com lmtp2:lmtp2host
10 - Appendix: Older Cyrus versions
===================================
-First of all, if you are using a Cyrus 2.x version prior to 2.0.10,
-it would be good to upgrade. The previous 2.x releases were beta
-releases, and numerous bug fixes and enhancements have been
-incorporated into the 2.0.10 release.
+First of all, if you are using a Cyrus 2.x version prior to 2.1.4,
+you should really consider upgrading. There have been numerous bug
+fixes and performance improvements since the early 2.0 releases.
Further back, 1.6.24 was the last pre-2.x production release.
(Actually, there was a 1.6.25-BETA, but it is uncertain whether this
configure inetd. This involves the following file edits:
/etc/services:
-
- lmtp 24/tcp
+ lmtp 2003/tcp
/etc/inetd.conf:
-
lmtp stream tcp nowait cyrus /usr/sbin/tcpd /usr/cyrus/bin/deliver -e -l
/etc/hosts.allow:
-
deliver : localhost : ALLOW
deliver : ALL@ALL : DENY
Now comes the Postfix configuration. Basically, the Cyrus 2.x
discussions regarding LMTP delivery over TCP are also applicable to
-Cyrus 1.x, with the exception of the "/etc/cyrus.conf" file. A
+Cyrus 1.x, with the exception of the /etc/cyrus.conf file. A
typical Postfix configuration might look like this:
/etc/postfix/master.cf:
-
lmtp unix - - n - - lmtp
/etc/postfix/main.cf:
-
mailbox_transport = lmtp
It is also possible to use the transport map to route mail to your
Cyrus 1.6.24 LMTP server:
/etc/postfix/transport:
-
domain1.tld lmtp1:lmtp1host
domain2.tld lmtp2:lmtp2host
/etc/postfix/master.cf:
-
lmtp1 unix - - n - - lmtp
lmtp2 unix - - n - - lmtp
/etc/postfix/main.cf:
-
transport_maps = hash:/etc/postfix/transport
If you have read the discussion covering the Cyrus 2.x installation,
# Reject the address etc. that matches the pattern,
# and respond with the numerical code and text.
#
-# REJECT Reject the address etc. that matches the pattern. A
-# generic error response message is generated.
+# REJECT
+#
+# REJECT optional text...
+# Reject the address etc. that matches the pattern.
+# Reply with $reject_code optional text... when the
+# optional text is specified, otherwise reply with a
+# generic error response message.
#
# OK Accept the address etc. that matches the pattern.
#
# mat is generated by address-based relay authoriza-
# tion schemes.
#
-# HOLD Place the message on the hold queue, where it will
-# sit until someone either deletes it or releases it
-# for delivery. Mail that is placed on hold can be
-# examined with the postcat(1) command, and can be
-# destroyed or released with the postsuper(1) com-
-# mand.
+# DUNNO Pretend that the lookup key was not found in this
+# table, to prevents Postfix from trying substrings
+# of the lookup key (such as a subdomain name, or a
+# network address subnetwork).
#
-# Note: this action currently affects all recipients
+# HOLD
+#
+# HOLD optional text...
+# Place the message on the hold queue, where it will
+# sit until someone either deletes it or releases it
+# for delivery. Log the optional text if specified,
+# otherwise log a generic message.
+#
+# Mail that is placed on hold can be examined with
+# the postcat(1) command, and can be destroyed or
+# released with the postsuper(1) command.
+#
+# Note: this action currently affects all recipients
# of the message.
#
# DISCARD
-# Claim successful delivery and silently discard the
-# message.
+#
+# DISCARD optional text...
+# Claim successful delivery and silently discard the
+# message. Log the optional text if specified, oth-
+# erwise log a generic message.
#
# Note: this action currently affects all recipients
# of the message.
# check_client_access maptype:mapname
# look up client name, parent domains, client address,
# or networks obtained by stripping octets.
-# Reject if result is REJECT or "[45]xx text"
-# Permit if result is OK or all numerical.
+# Skip this lookup table if the result is DUNNO.
+# Reject the ETRN command if the result is REJECT text... or "[45]xx text"
+# Permit the ETRN command if the result is OK or all numerical.
# reject_rbl_client domain.tld: reject if the reverse client network
# address is listed in an A record under domain.tld.
# reject_rhsbl_client domain.tld: reject if the client hostname is listed
# check_client_access maptype:mapname
# look up client name, parent domains, client address,
# or networks obtained by stripping octets.
-# Reject if result is REJECT or "[45]xx text"
-# Permit if result is OK or all numerical.
+# Skip this lookup table if the result is DUNNO.
+# Reject the SMTP client if the result is REJECT text... or "[45]xx text"
+# Discard the message if the result is DISCARD text...
+# Hold the message in the queue if the result is HOLD text...
+# Release mail "on hold" with the postsuper(1) command.
+# Permit the SMTP client if the result is OK or all numerical.
# reject_rbl_client domain.tld: reject if the reversed client IP address
# is listed in an A record under domain.tld.
# reject_rhsbl_client domain.tld: reject if the client hostname is listed
# reject_non_fqdn_hostname: reject HELO hostname that is not in FQDN form
# check_helo_access maptype:mapname
# look up HELO hostname or parent domains.
-# Reject if result is REJECT or "[45]xx text"
-# Permit if result is OK or all numerical.
+# Skip this lookup table if the result is DUNNO.
+# Reject the HELO command if the result is REJECT text... or "[45]xx text"
+# Discard the message if the result is DISCARD text...
+# Hold the message in the queue if the result is HOLD text...
+# Release mail "on hold" with the postsuper(1) command.
+# Permit the HELO command if the result is OK or all numerical.
# reject: reject the request. Place this at the end of a restriction.
# permit: permit the request. Place this at the end of a restriction.
# warn_if_reject: next restriction logs a warning instead of rejecting.
# in an A record under domain.tld.
# check_sender_access maptype:mapname
# look up sender address, parent domain, or localpart@.
-# Reject if result is REJECT or "[45]xx text"
-# Permit if result is OK or all numerical.
+# Skip this lookup table if the result is DUNNO.
+# Reject the sender if the result is REJECT text... or "[45]xx text"
+# Discard the message if the result is DISCARD text...
+# Hold the message in the queue if the result is HOLD text...
+# Release mail "on hold" with the postsuper(1) command.
+# Permit the sender if the result is OK or all numerical.
# reject_sender_login_mismatch: reject if $smtpd_sender_login_maps specifies
# a MAIL FROM address owner, but the client is not (SASL) logged in as
# that MAIL FROM address owner; or if the client is (SASL) logged in, but
# reject_unknown_recipient_domain: reject domains without A or MX record.
# check_recipient_access maptype:mapname
# look up recipient address, parent domain, or localpart@.
-# Reject if result is REJECT or "[45]xx text"
-# Permit if result is OK or all numerical.
+# Skip this lookup table if the result is DUNNO.
+# Reject the recipient if the result is REJECT text... or "[45]xx text"
+# Discard the message if the result is DISCARD text...
+# Hold the message in the queue if the result is HOLD text...
+# Release mail "on hold" with the postsuper(1) command.
+# Permit the recipient if the result is OK or all numerical.
# reject_non_fqdn_recipient: reject recipient address that is not in FQDN form
# reject: reject the request. Place this at the end of a restriction.
# permit: permit the request. Place this at the end of a restriction.
Reject the address etc. that matches the pattern,
and respond with the numerical code and text.
- <b>REJECT</b> Reject the address etc. that matches the pattern. A
- generic error response message is generated.
+ <b>REJECT</b>
+
+ <b>REJECT</b> <i>optional</i> <i>text...</i>
+ Reject the address etc. that matches the pattern.
+ Reply with <i>$reject_code</i> <i>optional</i> <i>text...</i> when the
+ optional text is specified, otherwise reply with a
+ generic error response message.
<b>OK</b> Accept the address etc. that matches the pattern.
mat is generated by address-based relay authoriza-
tion schemes.
- <b>HOLD</b> Place the message on the <b>hold</b> queue, where it will
- sit until someone either deletes it or releases it
- for delivery. Mail that is placed on hold can be
- examined with the <a href="postcat.1.html"><b>postcat</b>(1)</a> command, and can be
- destroyed or released with the <a href="postsuper.1.html"><b>postsuper</b>(1)</a> com-
- mand.
+ <b>DUNNO</b> Pretend that the lookup key was not found in this
+ table, to prevents Postfix from trying substrings
+ of the lookup key (such as a subdomain name, or a
+ network address subnetwork).
- Note: this action currently affects all recipients
+ <b>HOLD</b>
+
+ <b>HOLD</b> <i>optional</i> <i>text...</i>
+ Place the message on the <b>hold</b> queue, where it will
+ sit until someone either deletes it or releases it
+ for delivery. Log the optional text if specified,
+ otherwise log a generic message.
+
+ Mail that is placed on hold can be examined with
+ the <a href="postcat.1.html"><b>postcat</b>(1)</a> command, and can be destroyed or
+ released with the <a href="postsuper.1.html"><b>postsuper</b>(1)</a> command.
+
+ Note: this action currently affects all recipients
of the message.
<b>DISCARD</b>
- Claim successful delivery and silently discard the
- message.
+
+ <b>DISCARD</b> <i>optional</i> <i>text...</i>
+ Claim successful delivery and silently discard the
+ message. Log the optional text if specified, oth-
+ erwise log a generic message.
Note: this action currently affects all recipients
of the message.
<dt> <i>maptype</i>:<i>mapname</i> <dd> Search the named <a
href="access.5.html">access database</a> for the domain specified
in the ETRN command, or its parent domains. Reject the request if
-the result is <b>REJECT</b> or "[<b>45</b>]<i>XX text</i>". Permit
+the result is <b>REJECT</b> <i>text...</i> or "[<b>45</b>]<i>XX
+text</i>". Permit
the request if the result is <b>OK</b> or <b>RELAY</b> or
all-numerical. Otherwise, treat the result as another list of UCE
restrictions. The <b>access_map_reject_code </b> parameter specifies
Reject the address etc. that matches the pattern, and respond with
the numerical code and text.
.IP \fBREJECT\fR
-Reject the address etc. that matches the pattern. A generic
-error response message is generated.
+.IP "\fBREJECT \fIoptional text...\fR
+Reject the address etc. that matches the pattern. Reply with
+\fI$reject_code optional text...\fR when the optional text is
+specified, otherwise reply with a generic error response message.
.IP \fBOK\fR
Accept the address etc. that matches the pattern.
.IP \fIall-numerical\fR
An all-numerical result is treated as OK. This format is
generated by address-based relay authorization schemes.
+.IP \fBDUNNO\fR
+Pretend that the lookup key was not found in this table, to
+prevents Postfix from trying substrings of the lookup key
+(such as a subdomain name, or a network address subnetwork).
.IP \fBHOLD\fR
+.IP "\fBHOLD \fIoptional text...\fR"
Place the message on the \fBhold\fR queue, where it will sit
until someone either deletes it or releases it for delivery.
+Log the optional text if specified, otherwise log a generic
+message.
+
Mail that is placed on hold can be examined with the
\fBpostcat\fR(1) command, and can be destroyed or released with
the \fBpostsuper\fR(1) command.
.sp
Note: this action currently affects all recipients of the message.
.IP \fBDISCARD\fR
+.IP "\fBDISCARD \fIoptional text...\fR
Claim successful delivery and silently discard the message.
+Log the optional text if specified, otherwise log a generic
+message.
.sp
Note: this action currently affects all recipients of the message.
.IP "\fBFILTER \fItransport:destination\fR"
# Reject the address etc. that matches the pattern, and respond with
# the numerical code and text.
# .IP \fBREJECT\fR
-# Reject the address etc. that matches the pattern. A generic
-# error response message is generated.
+# .IP "\fBREJECT \fIoptional text...\fR
+# Reject the address etc. that matches the pattern. Reply with
+# \fI$reject_code optional text...\fR when the optional text is
+# specified, otherwise reply with a generic error response message.
# .IP \fBOK\fR
# Accept the address etc. that matches the pattern.
# .IP \fIall-numerical\fR
# An all-numerical result is treated as OK. This format is
# generated by address-based relay authorization schemes.
+# .IP \fBDUNNO\fR
+# Pretend that the lookup key was not found in this table, to
+# prevents Postfix from trying substrings of the lookup key
+# (such as a subdomain name, or a network address subnetwork).
# .IP \fBHOLD\fR
+# .IP "\fBHOLD \fIoptional text...\fR"
# Place the message on the \fBhold\fR queue, where it will sit
# until someone either deletes it or releases it for delivery.
+# Log the optional text if specified, otherwise log a generic
+# message.
+#
# Mail that is placed on hold can be examined with the
# \fBpostcat\fR(1) command, and can be destroyed or released with
# the \fBpostsuper\fR(1) command.
# .sp
# Note: this action currently affects all recipients of the message.
# .IP \fBDISCARD\fR
+# .IP "\fBDISCARD \fIoptional text...\fR
# Claim successful delivery and silently discard the message.
+# Log the optional text if specified, otherwise log a generic
+# message.
# .sp
# Note: this action currently affects all recipients of the message.
# .IP "\fBFILTER \fItransport:destination\fR"
mkmap_db.o: ../../include/vstream.h
mkmap_db.o: ../../include/argv.h
mkmap_db.o: ../../include/dict_db.h
+mkmap_db.o: ../../include/myflock.h
mkmap_db.o: mail_params.h
mkmap_db.o: mkmap.h
mkmap_dbm.o: mkmap_dbm.c
mkmap_dbm.o: ../../include/vstream.h
mkmap_dbm.o: ../../include/argv.h
mkmap_dbm.o: ../../include/dict_dbm.h
+mkmap_dbm.o: ../../include/myflock.h
mkmap_dbm.o: mkmap.h
mkmap_open.o: mkmap_open.c
mkmap_open.o: ../../include/sys_defs.h
mkmap_open.o: ../../include/dict_dbm.h
mkmap_open.o: ../../include/sigdelay.h
mkmap_open.o: ../../include/mymalloc.h
-mkmap_open.o: ../../include/myflock.h
mkmap_open.o: mkmap.h
mynetworks.o: mynetworks.c
mynetworks.o: ../../include/sys_defs.h
* Patches change the patchlevel and the release date. Snapshots change the
* release date only, unless they include the same bugfix as a patch release.
*/
-#define MAIL_RELEASE_DATE "20021029"
+#define MAIL_RELEASE_DATE "20021031"
#define VAR_MAIL_VERSION "mail_version"
#define DEF_MAIL_VERSION "1.1.11-" MAIL_RELEASE_DATE
/*
* A database handle is an opaque structure. The user is not supposed to
- * know its implementation.
+ * know its implementation. We try to open and lock a file before DB/DBM
+ * initialization. However, if the file does not exist then we may have to
+ * acquire the lock after the DB/DBM initialization.
*/
typedef struct MKMAP {
- struct DICT *(*open) (const char *, int, int);
- struct DICT *dict;
- char *lock_file;
- int lock_fd;
+ struct DICT *(*open) (const char *, int, int); /* dict_xx_open() */
+ struct DICT *dict; /* dict_xx_open() result */
+ char *lock_file; /* lock file name */
+ int lock_fd; /* locked open file, or -1 */
+ void (*after_open) (struct MKMAP *); /* may be null */
} MKMAP;
extern MKMAP *mkmap_open(const char *, const char *, int, int);
/* This module implements support for creating DB databases.
/*
/* mkmap_hash_open() and mkmap_btree_open() take a file name,
-/* append the ".db" suffix, and create or open the named DB
-/* database. This routine is a DB-specific helper for the more
-/* general mkmap_open() interface.
+/* append the ".db" suffix, and do whatever initialization is
+/* required before the Berkeley DB open routine is called.
/*
/* All errors are fatal.
/* SEE ALSO
/* System library. */
#include <sys_defs.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
/* Utility library. */
#include <stringops.h>
#include <dict.h>
#include <dict_db.h>
+#include <myflock.h>
/* Global library. */
#include <db.h>
#endif
-/* mkmap_db_open - create or open database */
+/* mkmap_db_after_open - lock newly created database */
-static MKMAP *mkmap_db_open(const char *path,
+static void mkmap_db_after_open(MKMAP *mkmap)
+{
+ if (mkmap->lock_fd < 0) {
+ if ((mkmap->lock_fd = open(mkmap->lock_file, O_RDWR, 0644)) < 0)
+ msg_fatal("open lockfile %s: %m", mkmap->lock_file);
+ if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("lock %s: %m", mkmap->lock_file);
+ }
+}
+
+/* mkmap_db_before_open - lock existing database */
+
+static MKMAP *mkmap_db_before_open(const char *path,
DICT *(*db_open) (const char *, int, int))
{
MKMAP *mkmap = (MKMAP *) mymalloc(sizeof(*mkmap));
+ struct stat st;
/*
* Override the default per-table cache size for map (re)builds.
*/
mkmap->lock_file = concatenate(path, ".db", (char *) 0);
mkmap->open = db_open;
+ mkmap->after_open = mkmap_db_after_open;
/*
* Unfortunately, not all systems that might support db databases do
* support locking on open(), so we open the file before updating it.
+ *
+ * XXX Berkeley DB 4.1 refuses to open a zero-length file. This means we can
+ * open and lock only an existing file, and that we must not truncate it.
+ */
+ if ((mkmap->lock_fd = open(mkmap->lock_file, O_RDWR, 0644)) < 0) {
+ if (errno != ENOENT)
+ msg_fatal("open %s: %m", mkmap->lock_file);
+ }
+
+ /*
+ * Get an exclusive lock - we're going to change the database so we can't
+ * have any spectators.
+ *
+ * XXX Horror. Berkeley DB 4.1 refuses to open a zero-length file. This
+ * means that we must examine the size while the file is locked, and that
+ * we must unlink a zero-length file while it is locked. Avoid a race
+ * condition where two processes try to open the same zero-length file
+ * and where the second process ends up deleting the wrong file.
*/
- if ((mkmap->lock_fd = open(mkmap->lock_file, O_CREAT | O_RDWR, 0644)) < 0)
- msg_fatal("open %s: %m", mkmap->lock_file);
+ else {
+ if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("lock %s: %m", mkmap->lock_file);
+ if (fstat(mkmap->lock_fd, &st) < 0)
+ msg_fatal("fstat %s: %m", mkmap->lock_file);
+ if (st.st_size == 0) {
+ if (st.st_nlink > 0) {
+ if (unlink(mkmap->lock_file) < 0)
+ msg_fatal("cannot remove zero-length database file %s: %m",
+ mkmap->lock_file);
+ msg_warn("removing zero-length database file: %s",
+ mkmap->lock_file);
+ }
+ close(mkmap->lock_fd);
+ mkmap->lock_fd = -1;
+ }
+ }
return (mkmap);
}
MKMAP *mkmap_hash_open(const char *path)
{
- return (mkmap_db_open(path, dict_hash_open));
+ return (mkmap_db_before_open(path, dict_hash_open));
}
/* mkmap_btree_open - create or open btree DB file */
MKMAP *mkmap_btree_open(const char *path)
{
- return (mkmap_db_open(path, dict_btree_open));
+ return (mkmap_db_before_open(path, dict_btree_open));
}
#endif
#include <stringops.h>
#include <dict.h>
#include <dict_dbm.h>
+#include <myflock.h>
/* Application-specific. */
*/
mkmap->lock_file = concatenate(path, ".dir", (char *) 0);
mkmap->open = dict_dbm_open;
+ mkmap->after_open = 0;
/*
* Unfortunately, not all systems support locking on open(), so we open
msg_warn("close %s: %m", pag_file);
myfree(pag_file);
+ /*
+ * Get an exclusive lock - we're going to change the database so we can't
+ * have any spectators.
+ */
+ if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("lock %s: %m", mkmap->lock_file);
+
return (mkmap);
}
#include <dict_dbm.h>
#include <sigdelay.h>
#include <mymalloc.h>
-#include <myflock.h>
/* Global library. */
*/
typedef struct {
char *type;
- MKMAP *(*create_or_open) (const char *);
+ MKMAP *(*before_open) (const char *);
} MKMAP_OPEN_INFO;
MKMAP_OPEN_INFO mkmap_types[] = {
msg_info("open %s %s", type, path);
/*
- * Create or open the desired map file(s).
+ * Do whatever before-open initialization is needed.
*/
- mkmap = mp->create_or_open(path);
-
- /*
- * Get an exclusive lock - we're going to change the database so we can't
- * have any spectators.
- */
- if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
- msg_fatal("lock %s: %m", mkmap->lock_file);
+ mkmap = mp->before_open(path);
/*
* Delay signal delivery, so that we won't leave the database in an
mkmap->dict->lock_fd = -1; /* XXX just in case */
mkmap->dict->stat_fd = -1; /* XXX just in case */
mkmap->dict->flags |= DICT_FLAG_DUP_WARN;
+
+ /*
+ * Do whatever post-open initialization is needed.
+ */
+ if (mkmap->after_open)
+ mkmap->after_open(mkmap);
+
return (mkmap);
}
@$(EXPORT) make -f Makefile.in Makefile 1>&2
tests: smtpd_check_test smtpd_check_test2 smtpd_acl_test smtpd_exp_test \
- smtpd_token_test
+ smtpd_token_test smtpd_check_test4
smtpd_check_test: smtpd_check smtpd_check.in smtpd_check.ref smtpd_check_access
../postmap/postmap hash:smtpd_check_access
diff smtpd_check.ref2 smtpd_check.tmp
rm -f smtpd_check.tmp smtpd_check_access.*
+smtpd_check_test4: smtpd_check smtpd_check.in4 smtpd_check.ref4 smtpd_check_access
+ ../postmap/postmap hash:smtpd_check_access
+ ./smtpd_check <smtpd_check.in4 >smtpd_check.tmp 2>&1
+ diff smtpd_check.ref4 smtpd_check.tmp
+ rm -f smtpd_check.tmp smtpd_check_access.*
+
smtpd_acl_test: smtpd_check smtpd_acl.in smtpd_acl.ref smtpd_check_access
../postmap/postmap hash:smtpd_check_access
./smtpd_check <smtpd_acl.in >smtpd_check.tmp 2>&1
/* Perform a lookup in the specified access table. Reject the request
/* when the lookup result is REJECT or when the result begins with a
/* 4xx or 5xx status code. Other numerical status codes are not
-/* permitted. Allow the request otherwise. The
-/* \fIaccess_map_reject_code\fR configuration parameter specifies the
-/* reject status code (default: 554).
+/* permitted. DUNNO suppresses less precise searches in the same map, but
+/* neither allows nor denies the request. Allow the request otherwise.
+/* The \fIaccess_map_reject_code\fR configuration parameter specifies
+/* the reject status code (default: 554).
/* .IP "check_client_access maptype:mapname"
/* Look up the client host name or any of its parent domains, or
/* the client address or any network obtained by stripping octets
VSTRING *buf = vstring_alloc(100);
vstring_sprintf(buf, "%s: %s: %s from %s: %s;",
- state->queue_id, whatsup, state->where,
- state->namaddr, text);
+ state->queue_id ? state->queue_id : "NOQUEUE",
+ whatsup, state->where, state->namaddr, text);
if (state->sender)
vstring_sprintf_append(buf, " from=<%s>", state->sender);
if (state->recipient)
ARGV *restrictions;
jmp_buf savebuf;
int status;
+ const char *cmd_text;
+ int cmd_len;
+
+ /*
+ * Parse into command and text. Do not change the input.
+ */
+ cmd_text = value + strcspn(value, " \t");
+ cmd_len = cmd_text - value;
+ while (*cmd_text && ISSPACE(*cmd_text))
+ cmd_text++;
if (msg_verbose)
msg_info("%s: %s %s %s", myname, table, value, datum);
+#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
+
/*
- * DUNNO means skip this table.
+ * DUNNO means skip this table. Silently ignore optional text.
*/
- if (strcasecmp(value, "DUNNO") == 0)
+ if (STREQUAL(value, "DUNNO", cmd_len))
return (SMTPD_CHECK_DUNNO);
/*
- * REJECT means NO. Generate a generic error response.
+ * REJECT means NO. Use optional text or generate a generic error
+ * response.
*/
- if (strcasecmp(value, "REJECT") == 0)
+ if (STREQUAL(value, "REJECT", cmd_len)) {
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
- "%d <%s>: %s rejected: Access denied",
- var_access_map_code, reply_name, reply_class));
+ "%d <%s>: %s rejected: %s",
+ var_access_map_code, reply_name, reply_class,
+ *cmd_text ? cmd_text : "Access denied"));
+ }
/*
* FILTER means deliver to content filter. But we may still change our
* mind, and reject/discard the message for other reasons.
*/
-#define FILTER_LEN (sizeof("FILTER") - 1)
-
- if (strncasecmp(value, "FILTER", FILTER_LEN) == 0) {
- if (value[FILTER_LEN] == 0) {
+ if (STREQUAL(value, "FILTER", cmd_len)) {
+ if (*cmd_text == 0) {
msg_warn("access map %s entry %s has FILTER entry without value",
table, datum);
return (SMTPD_CHECK_DUNNO);
- }
- if (ISSPACE(value[FILTER_LEN])) {
- value += FILTER_LEN;
- while (ISSPACE(*value))
- value++;
+ } else {
vstring_sprintf(error_text, "<%s>: %s triggers FILTER %s",
- reply_name, reply_class, value);
+ reply_name, reply_class, cmd_text);
log_whatsup(state, "filter", STR(error_text));
#ifndef TEST
rec_fprintf(state->dest->stream, REC_TYPE_FILT, "%s", value);
* HOLD means deliver later. But we may still change our mind, and
* reject/discard the message for other reasons.
*/
- if (strcasecmp(value, "HOLD") == 0) {
- vstring_sprintf(error_text, "<%s>: %s triggers HOLD action",
- reply_name, reply_class);
+ if (STREQUAL(value, "HOLD", cmd_len)) {
+ vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class,
+ *cmd_text ? cmd_text : "triggers HOLD action");
log_whatsup(state, "hold", STR(error_text));
#ifndef TEST
rec_fprintf(state->dest->stream, REC_TYPE_FLGS, "%d",
* XXX Set some global flag that disables all further restrictions.
* Triggering a "reject" or "hold" action after "discard" is silly.
*/
- if (strcasecmp(value, "DISCARD") == 0) {
- vstring_sprintf(error_text, "<%s>: %s triggers DISCARD action",
- reply_name, reply_class);
+ if (STREQUAL(value, "DISCARD", cmd_len)) {
+ vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class,
+ *cmd_text ? cmd_text : "triggers DISCARD action");
log_whatsup(state, "discard", STR(error_text));
#ifndef TEST
rec_fprintf(state->dest->stream, REC_TYPE_FLGS, "%d",
* 4xx or 5xx means NO as well. smtpd_check_reject() will validate the
* response status code.
*/
- if (ISDIGIT(value[0]) && ISDIGIT(value[1]) && ISDIGIT(value[2])) {
+ if (cmd_len == 3 && *cmd_text
+ && ISDIGIT(value[0]) && ISDIGIT(value[1]) && ISDIGIT(value[2])) {
code = atoi(value);
- while (ISDIGIT(*value) || ISSPACE(*value))
- value++;
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: %s",
- code, reply_name, reply_class, value));
+ code, reply_name, reply_class, cmd_text));
}
/*
- * OK or RELAY means YES.
+ * OK or RELAY means YES. Ignore trailing text.
*/
- if (strcasecmp(value, "OK") == 0 || strcasecmp(value, "RELAY") == 0)
+ if (STREQUAL(value, "OK", cmd_len) || STREQUAL(value, "RELAY", cmd_len))
return (SMTPD_CHECK_OK);
/*
--- /dev/null
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+#
+# Test the nex access map features
+#
+sender_restrictions hash:./smtpd_check_access
+mail rejecttext@bad.domain
+mail filter@filter.domain
+mail filtertext@filter.domain
+mail hold@hold.domain
+mail holdtext@hold.domain
+mail discard@hold.domain
+mail discardtext@hold.domain
+mail dunnotext@dunno.domain
--- /dev/null
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> #
+>>> # Test the nex access map features
+>>> #
+>>> sender_restrictions hash:./smtpd_check_access
+OK
+>>> mail rejecttext@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 554 <rejecttext@bad.domain>: Sender address rejected: text; from=<rejecttext@bad.domain> proto=SMTP
+554 <rejecttext@bad.domain>: Sender address rejected: text
+>>> mail filter@filter.domain
+./smtpd_check: warning: access map hash:./smtpd_check_access entry filter@filter.domain has FILTER entry without value
+OK
+>>> mail filtertext@filter.domain
+./smtpd_check: <queue id>: filter: MAIL from localhost[127.0.0.1]: <filtertext@filter.domain>: Sender address triggers FILTER text; from=<filtertext@filter.domain> proto=SMTP
+OK
+>>> mail hold@hold.domain
+./smtpd_check: <queue id>: hold: MAIL from localhost[127.0.0.1]: <hold@hold.domain>: Sender address triggers HOLD action; from=<hold@hold.domain> proto=SMTP
+OK
+>>> mail holdtext@hold.domain
+./smtpd_check: <queue id>: hold: MAIL from localhost[127.0.0.1]: <holdtext@hold.domain>: Sender address text; from=<holdtext@hold.domain> proto=SMTP
+OK
+>>> mail discard@hold.domain
+./smtpd_check: <queue id>: discard: MAIL from localhost[127.0.0.1]: <discard@hold.domain>: Sender address triggers DISCARD action; from=<discard@hold.domain> proto=SMTP
+OK
+>>> mail discardtext@hold.domain
+./smtpd_check: <queue id>: discard: MAIL from localhost[127.0.0.1]: <discardtext@hold.domain>: Sender address text; from=<discardtext@hold.domain> proto=SMTP
+OK
+>>> mail dunnotext@dunno.domain
+OK
recipient=$recipient recipient_name=$recipient_name recipient_domain=$recipient_domain
rbl_code=$rbl_code rbl_domain=$rbl_domain rbl_txt=$rbl_txt rbl_what=$rbl_what
rbl_class=$rbl_class
+
+rejecttext@bad.domain reject text
+filter@filter.domain filter
+filtertext@filter.domain filter text
+hold@hold.domain hold
+holdtext@hold.domain hold text
+discard@hold.domain discard
+discardtext@hold.domain discard text
+dunnotext@dunno.domain dunno text
int patch_version;
(void) db_version(&major_version, &minor_version, &patch_version);
- if (major_version != DB_VERSION_MAJOR)
+ if (major_version != DB_VERSION_MAJOR || minor_version != DB_VERSION_MINOR)
msg_fatal("incorrect version of Berkeley DB: "
- "compiled against %d.%d.%d, linked against %d.%d.%d",
+ "compiled against %d.%d.%d, run-time linked against %d.%d.%d",
DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH,
major_version, minor_version, patch_version);
#endif
*
* Programs such as postmap/postalias use their own large-grained (in the
* time domain) locks while rewriting the entire file.
+ *
+ * XXX DB version 4.1 will not open a zero-length file. This means we must
+ * open an existing file without O_CREAT|O_TRUNC, and that we must let
+ * db_open() create a non-existent file for us.
*/
+#define LOCK_OPEN_FLAGS(f) ((f) & ~(O_CREAT|O_TRUNC))
+
if (dict_flags & DICT_FLAG_LOCK) {
- if ((lock_fd = open(db_path, open_flags, 0644)) < 0)
- msg_fatal("open database %s: %m", db_path);
- if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
- msg_fatal("shared-lock database %s for open: %m", db_path);
+ if ((lock_fd = open(db_path, LOCK_OPEN_FLAGS(open_flags), 0644)) < 0) {
+ if (errno != ENOENT)
+ msg_fatal("open database %s: %m", db_path);
+ } else {
+ if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("shared-lock database %s for open: %m", db_path);
+ }
}
/*
msg_fatal("set DB cache size %d: %m", dict_db_cache_size);
if (type == DB_HASH && db->set_h_nelem(db, DICT_DB_NELM) != 0)
msg_fatal("set DB hash element count %d: %m", DICT_DB_NELM);
+#if (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 0)
+ if ((errno = db->open(db, 0, db_path, 0, type, db_flags, 0644)) != 0)
+ msg_fatal("open database %s: %m", db_path);
+#elif (DB_VERSION_MAJOR == 3 || DB_VERSION_MAJOR == 4)
if ((errno = db->open(db, db_path, 0, type, db_flags, 0644)) != 0)
msg_fatal("open database %s: %m", db_path);
+#else
+#error "Unsupported Berkeley DB version"
+#endif
if ((errno = db->fd(db, &dbfd)) != 0)
msg_fatal("get database file descriptor: %m");
#endif
- if (dict_flags & DICT_FLAG_LOCK) {
+ if ((dict_flags & DICT_FLAG_LOCK) && lock_fd >= 0) {
if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
msg_fatal("unlock database %s for open: %m", db_path);
if (close(lock_fd) < 0)