-TABOUNCE
-TALIAS_TOKEN
-TARGV
+-TATTR_CLNT
-TATTR_TABLE
+-TAUTO_CLNT
-TBH_TABLE
-TBINATTR
-TBINATTR_INFO
and header/body checks. This eliminates the need to configure
multiple cleanup services in the master.cf file.
+20030707
+
+ Feature: context dependent SASL security options (i.e.
+ different options when TLS is enabled/disabled). Lutz
+ Jaenicke. Files: */*sasl_glue.[hc].
+
+20030708
+
+ Hardened the attr_scan routines for exposure to an untrusted
+ environment, in preparation of the tuple0 and tuple64
+ protocols that will be used for SMTP policy delegation.
+
+ Feature: address filter for RBL lookups, for use with
+ multi-valued RBL services. File: smtpd/smtpd_check.c.
+
+ Feature: accept_socket option to pass the spawn(8) listen
+ socket to a persistent non-Postfix process. File: spawn/spawn.c.
+
+20030709
+
+ Cleanup: use off_t instead of int for VSTREAM file offsets.
+ This was needed for mailboxes > 2GB on 32-bit systems.
+ Files: util/vstream.c, global/mail_copy.c.
+
+20030710
+
+ Support for multiple A and TXT results in RBL lookups.
+ Victor Duchovni, Morgan Stanley. File: smtpd/smtpd_check.c.
+
+ Support for attribute-based query-reply protocols.
+ Files: util/attr_clnt.[hc], util/auto_clnt.[hc].
+
+20030711
+
+ Support for plain "name=value\n" attribute protocol.
+ Files: util/attr_{scan,print}_plain.c.
+
+ Bugfix: the LMTP session caching code did not reset the
+ EHLO server feature list when it needed to reconnect.
+ Problem found by Tobias Erbsland.
+
+20030712
+
+ Feature: delegated SMTP policy server. As an example, see
+ the greylisting server in examples/smtpd-policy. Specify
+ "check_smtpd_policy_service" in smtpd_mumble_restrictions.
+ See SMTPD_POLICY_SERVICE_README for details.
+
+
Open problems:
Low: smtp-source may block when sending large test messages.
Recipient address verification may be useful to block mail for
undeliverable recipients on mail relay hosts that do not have a
copy of all the relayed recipient addresses. This prevents the mail
-queue from filling up with undeliverable and bounced SPAM.
+queue from filling up with undeliverable and bounced mail.
Recipient address verification is relatively straightforward and
there are no surprises. If a recipient probe fails, then Postfix
content.
2 - Reject the mail (by sending a suitable status code back to
- Postfix) so that it is returned to sender.
+ Postfix). Postfix will return the mail to the sender.
3 - Send the mail somewhere else.
smtp inet n - n - - smtpd
-o smtpd_proxy_filter=26
:26 inet n - n - - smtpd
+ -o smtpd_client_restrictions=
+ -o smtpd_helo_restrictions=
+ -o smtpd_sender_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o mynetworks=127.0.0.0/8
-o receive_override_options=no_unknown_recipient_checks
Note: do not specify spaces around the "=" or "," characters.
# most delivery transports. For the local delivery agent the default is 2.
#local_destination_concurrency_limit = 2
-#default_destination_concurrency_limit = 10
+#default_destination_concurrency_limit = 20
# DEBUGGING CONTROL
#
#
smtp_pix_workaround_threshold_time = 500s
+# The smtp_quote_rfc821_envelope parameter controls whether the
+# Postfix SMTP client quotes addresses in MAIL FROM and RCPT TO
+# commands as required by RFC 821. This includes putting quotes around
+# an address localpart that ends in ".".
+#
+# The default is to comply with RFC 821. If you have to send mail to
+# a broken SMTP server, configure a special SMTP client in master.cf
+# with "-o smtp_quote_rfc821_envelope=no" on the SMTP client command
+# line.
+#
+smtp_quote_rfc821_envelope = yes
+
#
# RATE CONTROLS
#
# terminated by the ASCII newline character. Request and
# reply parameters (see below) are separated by whitespace.
#
+# Send and receive operations must complete in 100 seconds.
+#
# REQUEST FORMAT
# Each request specifies a command, a lookup key, and possi-
# bly a lookup result.
# This request is currently not implemented.
#
# REPLY FORMAT
-# Each reply specifies a status code and text. Replies must
-# be no longer than 4096 characters including the newline
+# Each reply specifies a status code and text. Replies must
+# be no longer than 4096 characters including the newline
# terminator.
#
# 500 SPACE text NEWLINE
-# In case of a lookup request, the requested data
-# does not exist. In case of an update request, the
+# In case of a lookup request, the requested data
+# does not exist. In case of an update request, the
# request was rejected. The text describes the
# nature of the problem.
#
# 400 SPACE text NEWLINE
-# This indicates an error condition. The text
-# describes the nature of the problem. The client
+# This indicates an error condition. The text
+# describes the nature of the problem. The client
# should retry the request later.
#
# 200 SPACE text NEWLINE
# The request was successful. In the case of a lookup
-# request, the text contains an encoded version of
+# request, the text contains an encoded version of
# the requested data.
#
# ENCODING
-# In request and reply parameters, the character %, each
+# In request and reply parameters, the character %, each
# non-printing character, and each whitespace character must
-# be replaced by %XX, where XX is the corresponding ASCII
-# hexadecimal character value. The hexadecimal codes can be
+# be replaced by %XX, where XX is the corresponding ASCII
+# hexadecimal character value. The hexadecimal codes can be
# specified in any case (upper, lower, mixed).
#
-# The Postfix client always encodes a request. The server
-# may omit the encoding as long as the reply is guaranteed
+# The Postfix client always encodes a request. The server
+# may omit the encoding as long as the reply is guaranteed
# to not contain the % or NEWLINE character.
#
# SECURITY
-# Do not use TCP lookup tables for security critical pur-
-# poses. The client-server connection is not protected and
+# Do not use TCP lookup tables for security critical pur-
+# poses. The client-server connection is not protected and
# the server is not authenticated.
#
# SEE ALSO
# BUGS
# Only the lookup method is currently implemented.
#
-# The client does not hang up when the connection is idle
+# The client does not hang up when the connection is idle
# for a long time.
#
# LICENSE
-# The Secure Mailer license must be distributed with this
+# The Secure Mailer license must be distributed with this
# software.
#
# AUTHOR(S)
--- /dev/null
+#!/usr/bin/perl
+
+use DB_File;
+use Fcntl;
+use Sys::Syslog qw(:DEFAULT setlogsock);
+
+#
+# Usage: smtpd-policy.pl [-v]
+#
+# Demo delegated Postfix SMTPD policy server. This server implements
+# greylisting. State is kept in a Berkeley DB database. Logging is
+# sent to syslogd.
+#
+# To run this from /etc/postfix/master.cf:
+#
+# policy unix - n n - - spawn
+# user=nobody argv=/usr/bin/perl /usr/libexec/postfix/smtpd-policy.pl
+#
+# To use this from Postfix SMTPD, use in /etc/postfix/main.cf:
+#
+# smtpd_policy_service_endpoint = plain:unix:private/policy
+# smtpd_recipient_restrictions = ... check_policy_service ...
+#
+# This script runs as one PERL process per SMTP server process.
+# By default, a Postfix SMTP server process terminates after 100
+# seconds of idle time, or after serving 100 clients.
+#
+
+#
+# greylist status database and greylist time interval. DO NOT create the
+# greylist status database in a world-writable directory such as /tmp
+# or /var/tmp. DO NOT create the greylist database in a file system
+# that can run out of space.
+#
+$database_name="/var/mta/smtpd-policy.db";
+$greylist_delay=3600;
+
+#
+# Syslogging options for verbose mode and for fatal errors. Comment
+# out the $syslog_socktype line if syslogging does not work.
+#
+$syslog_socktype = 'unix'; # inet, unix, stream, console
+$syslog_facility="mail";
+$syslog_options="pid";
+$syslog_priority="info";
+
+#
+# Demo policy routine. The result is an action just like it would
+# be specified on the right-hand side of a Postfix access table.
+# Request attributes are passed in via the %attr hash.
+#
+sub policy {
+ local(*attr) = @_;
+ my($key, $time_stamp, $now);
+
+ # Open the database on the fly.
+ open_database() unless $database_obj;
+
+ # Lookup the time stamp for this client/sender/recipient.
+ $key = $attr{"client_address"}."/".$attr{"sender"}."/".$attr{"recipient"};
+ $time_stamp = read_database($key);
+ $now = time();
+
+ # If new request, add this client/sender/recipient to the database.
+ if ($time_stamp == 0) {
+ $time_stamp = $now;
+ update_database($key, $time_stamp);
+ }
+
+ syslog $syslog_priority, "request age %d", $now - $time_stamp if $verbose;
+ if ($time_stamp + $greylist_delay < $now) {
+ return "ok";
+ } else {
+ return "450 request is greylisted";
+ }
+}
+
+#
+# You should not have to make changes below this point.
+#
+sub LOCK_SH { 1 }; # Shared lock (used for reading).
+sub LOCK_EX { 2 }; # Exclusive lock (used for writing).
+sub LOCK_NB { 4 }; # Don't block (for testing).
+sub LOCK_UN { 8 }; # Release lock.
+
+#
+# Log an error and abort.
+#
+sub fatal_exit {
+ syslog "err", @_;
+ exit 1;
+}
+
+#
+# Open hash database.
+#
+sub open_database {
+ my($database_fd);
+
+ $database_obj = tie(%db_hash, 'DB_File', $database_name,
+ O_CREAT|O_RDWR, 0644) ||
+ fatal_exit "Cannot open database %s: %m", $database_name;
+ $database_fd = $database_obj->fd;
+ open DATABASE_HANDLE, "+<&=$database_fd" ||
+ fatal_exit "Cannot fdopen database %s: %m", $database_name;
+ syslog $syslog_priority, "open %s", $database_name if $verbose;
+}
+
+#
+# Read database.
+#
+sub read_database {
+ my($key) = @_;
+ my($value);
+
+ flock DATABASE_HANDLE, LOCK_SH ||
+ fatal_exit "Can't get shared lock on %s: %m", $database_name;
+ $value = $db_hash{$key};
+ syslog $syslog_priority, "lookup %s: %s", $key, $value if $verbose;
+ flock DATABASE_HANDLE, LOCK_UN ||
+ fatal_exit "Can't unlock %s: %m", $database_name;
+ return $value;
+}
+
+#
+# Update database.
+#
+sub update_database {
+ my($key, $value) = @_;
+
+ syslog $syslog_priority, "store %s: %s", $key, $value if $verbose;
+ flock DATABASE_HANDLE, LOCK_EX ||
+ fatal_exit "Can't exclusively lock %s: %m", $database_name;
+ $db_hash{$key} = $value;
+ $database_obj->sync() &&
+ fatal_exit "Can't update %s: %m", $database_name;
+ flock DATABASE_HANDLE, LOCK_UN ||
+ fatal_exit "Can't unlock %s: %m", $database_name;
+}
+
+#
+# This process runs as a daemon, so it can't log to a terminal. Use
+# syslog so that people can actually see our messages.
+#
+setlogsock $syslog_socktype;
+openlog $0, $syslog_options, $syslog_facility;
+
+#
+# We don't need getopt() for now.
+#
+while ($option = shift(@ARGV)) {
+ if ($option eq "-v") {
+ $verbose = 1;
+ } else {
+ syslog $syslog_priority, "Invalid option: %s. Usage: %s [-v]",
+ $option, $0;
+ exit 1;
+ }
+}
+
+#
+# Unbuffer standard output.
+#
+select((select(STDOUT), $| = 1)[0]);
+
+#
+# Receive a bunch of attributes, evaluate the policy, send the result.
+#
+while (<STDIN>) {
+ if (/([^=]+)=(.*)\n/) {
+ $attr{$1} = $2;
+ } else {
+ if ($verbose) {
+ for (keys %attr) {
+ syslog $syslog_priority, "Attribute: %s=%s", $_, $attr{$_};
+ }
+ }
+ $action = &policy(*attr);
+ syslog $syslog_priority, "Action: %s", $action if $verbose;
+ print STDOUT "action=$action\n\n";
+ %attr = ();
+ }
+}
from the <a href="master.8.html"><b>master</b>(8)</a> process manager.
Mail addressed to the local <b>double-bounce</b> address is
- silently discarded. This stops potential loops caused by
- undeliverable bounce notifications.
+ logged and discarded. This stops potential loops caused
+ by undeliverable bounce notifications.
-<b>MAIL</b> <b>QUEUES</b>
+<b>MAIL QUEUES</b>
The <b>nqmgr</b> daemon maintains the following queues:
<b>incoming</b>
<b>hold</b> Messages that are kept "on hold" are kept here
until someone sets them free.
-<b>DELIVERY</b> <b>STATUS</b> <b>REPORTS</b>
+<b>DELIVERY STATUS REPORTS</b>
The <b>nqmgr</b> daemon keeps an eye on per-message delivery sta-
tus reports in the following directories. Each status
report file has the same name as the corresponding message
either opening queue files (input) or for message delivery
(output).
- <b>leaky</b> <b>bucket</b>
+ <b>leaky bucket</b>
This strategy limits the number of messages in the
<b>active</b> queue and prevents the queue manager from
running out of memory under heavy load.
from the <b>deferred</b> queue. This prevents a large mail
backlog from blocking the delivery of new mail.
- <b>slow</b> <b>start</b>
+ <b>slow start</b>
This strategy eliminates "thundering herd" problems
by slowly adjusting the number of parallel deliver-
ies to the same destination.
- <b>round</b> <b>robin</b>
+ <b>round robin</b>
The queue manager sorts delivery requests by desti-
nation. Round-robin selection prevents one desti-
nation from dominating deliveries to other destina-
tions.
- <b>exponential</b> <b>backoff</b>
+ <b>exponential backoff</b>
Mail that cannot be delivered upon the first
attempt is deferred. The time interval between
delivery attempts is doubled after each attempt.
- <b>destination</b> <b>status</b> <b>cache</b>
+ <b>destination status cache</b>
The queue manager avoids unnecessary delivery
attempts by maintaining a short-term, in-memory
list of unreachable destinations.
- <b>preemptive</b> <b>message</b> <b>scheduling</b>
+ <b>preemptive message scheduling</b>
The queue manager attempts to minimize the average
per-recipient delay while still preserving the cor-
rect per-message delays, using a sophisticated pre-
actions (the message is followed by the symbolic constant
used internally by the software):
- <b>D</b> <b>(QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>DEFERRED)</b>
+ <b>D (QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>DEFERRED)</b>
Start a deferred queue scan. If a deferred queue
scan is already in progress, that scan will be
restarted as soon as it finishes.
- <b>I</b> <b>(QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>INCOMING)</b>
+ <b>I (QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>INCOMING)</b>
Start an incoming queue scan. If an incoming queue
scan is already in progress, that scan will be
restarted as soon as it finishes.
- <b>A</b> <b>(QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>ALL)</b>
+ <b>A (QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>ALL)</b>
Ignore deferred queue file time stamps. The request
affects the next deferred queue scan.
- <b>F</b> <b>(QMGR</b><i>_</i><b>REQ</b><i>_</i><b>FLUSH</b><i>_</i><b>DEAD)</b>
+ <b>F (QMGR</b><i>_</i><b>REQ</b><i>_</i><b>FLUSH</b><i>_</i><b>DEAD)</b>
Purge all information about dead transports and
destinations.
- <b>W</b> <b>(TRIGGER</b><i>_</i><b>REQ</b><i>_</i><b>WAKEUP)</b>
+ <b>W (TRIGGER</b><i>_</i><b>REQ</b><i>_</i><b>WAKEUP)</b>
Wakeup call, This is used by the master server to
instantiate servers that should not go away for-
ever. The action is to start an incoming queue
Multiple identical trigger requests are collapsed into
one, and trigger requests are sorted so that <b>A</b> and <b>F</b> pre-
cede <b>D</b> and <b>I</b>. Thus, in order to force a deferred queue
- run, one would request <b>A</b> <b>F</b> <b>D</b>; in order to notify the queue
+ run, one would request <b>A F D</b>; in order to notify the queue
manager of the arrival of new mail one would request <b>I</b>.
<b>STANDARDS</b>
sudden burst of inbound mail can negatively impact out-
bound delivery rates.
-<b>CONFIGURATION</b> <b>PARAMETERS</b>
+<b>CONFIGURATION PARAMETERS</b>
The following <b>main.cf</b> parameters are especially relevant
to this program. See the Postfix <b>main.cf</b> file for syntax
- details and for default values. Use the <b>postfix</b> <b>reload</b>
+ details and for default values. Use the <b>postfix reload</b>
command after a configuration change.
<b>Miscellaneous</b>
<b>queue</b><i>_</i><b>directory</b>
Top-level directory of the Postfix queue.
-<b>Active</b> <b>queue</b> <b>controls</b>
+<b>Active queue controls</b>
In the text below, <i>transport</i> is the first field in a <b>mas-</b>
<b>ter.cf</b> entry.
all preempting messages delivered by the transport
<i>transport</i> can have.
-<b>Timing</b> <b>controls</b>
+<b>Timing controls</b>
<b>minimal</b><i>_</i><b>backoff</b><i>_</i><b>time</b>
Minimal time in seconds between delivery attempts
of a deferred message.
Time in seconds between attempts to contact a bro-
ken delivery transport.
-<b>Concurrency</b> <b>controls</b>
+<b>Concurrency controls</b>
<b>initial</b><i>_</i><b>destination</b><i>_</i><b>concurrency</b>
Initial per-destination concurrency level for par-
allel delivery to the same destination.
same destination, for delivery via the named mes-
sage <i>transport</i>.
-<b>Recipient</b> <b>controls</b>
+<b>Recipient controls</b>
<b>default</b><i>_</i><b>destination</b><i>_</i><b>recipient</b><i>_</i><b>limit</b>
Default limit on the number of recipients per mes-
sage transfer.
Limit on the number of recipients per message
transfer, for the named message <i>transport</i>.
-<b>Message</b> <b>scheduling</b>
+<b>Message scheduling</b>
<i>transport_</i><b>delivery</b><i>_</i><b>slot</b><i>_</i><b>cost</b> (valid range: 0,2,3...)
This parameter basically controls how often a mes-
sage delivered by <i>transport</i> can be preempted by
Default values for the transport specific parame-
ters described above.
-<b>SEE</b> <b>ALSO</b>
+<b>SEE ALSO</b>
<a href="master.8.html">master(8)</a>, process manager
syslogd(8) system logging
<a href="trivial-rewrite.8.html">trivial-rewrite(8)</a>, address routing
from the <a href="master.8.html"><b>master</b>(8)</a> process manager.
Mail addressed to the local <b>double-bounce</b> address is
- silently discarded. This stops potential loops caused by
- undeliverable bounce notifications.
+ logged and discarded. This stops potential loops caused
+ by undeliverable bounce notifications.
-<b>MAIL</b> <b>QUEUES</b>
+<b>MAIL QUEUES</b>
The <b>qmgr</b> daemon maintains the following queues:
<b>incoming</b>
<b>hold</b> Messages that are kept "on hold" are kept here
until someone sets them free.
-<b>DELIVERY</b> <b>STATUS</b> <b>REPORTS</b>
+<b>DELIVERY STATUS REPORTS</b>
The <b>qmgr</b> daemon keeps an eye on per-message delivery sta-
tus reports in the following directories. Each status
report file has the same name as the corresponding message
either opening queue files (input) or for message delivery
(output).
- <b>leaky</b> <b>bucket</b>
+ <b>leaky bucket</b>
This strategy limits the number of messages in the
<b>active</b> queue and prevents the queue manager from
running out of memory under heavy load.
from the <b>deferred</b> queue. This prevents a large mail
backlog from blocking the delivery of new mail.
- <b>slow</b> <b>start</b>
+ <b>slow start</b>
This strategy eliminates "thundering herd" problems
by slowly adjusting the number of parallel deliver-
ies to the same destination.
- <b>round</b> <b>robin</b>
+ <b>round robin</b>
The queue manager sorts delivery requests by desti-
nation. Round-robin selection prevents one desti-
nation from dominating deliveries to other destina-
tions.
- <b>exponential</b> <b>backoff</b>
+ <b>exponential backoff</b>
Mail that cannot be delivered upon the first
attempt is deferred. The time interval between
delivery attempts is doubled after each attempt.
- <b>destination</b> <b>status</b> <b>cache</b>
+ <b>destination status cache</b>
The queue manager avoids unnecessary delivery
attempts by maintaining a short-term, in-memory
list of unreachable destinations.
actions (the message is followed by the symbolic constant
used internally by the software):
- <b>D</b> <b>(QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>DEFERRED)</b>
+ <b>D (QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>DEFERRED)</b>
Start a deferred queue scan. If a deferred queue
scan is already in progress, that scan will be
restarted as soon as it finishes.
- <b>I</b> <b>(QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>INCOMING)</b>
+ <b>I (QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>INCOMING)</b>
Start an incoming queue scan. If an incoming queue
scan is already in progress, that scan will be
restarted as soon as it finishes.
- <b>A</b> <b>(QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>ALL)</b>
+ <b>A (QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>ALL)</b>
Ignore deferred queue file time stamps. The request
affects the next deferred queue scan.
- <b>F</b> <b>(QMGR</b><i>_</i><b>REQ</b><i>_</i><b>FLUSH</b><i>_</i><b>DEAD)</b>
+ <b>F (QMGR</b><i>_</i><b>REQ</b><i>_</i><b>FLUSH</b><i>_</i><b>DEAD)</b>
Purge all information about dead transports and
destinations.
- <b>W</b> <b>(TRIGGER</b><i>_</i><b>REQ</b><i>_</i><b>WAKEUP)</b>
+ <b>W (TRIGGER</b><i>_</i><b>REQ</b><i>_</i><b>WAKEUP)</b>
Wakeup call, This is used by the master server to
instantiate servers that should not go away for-
ever. The action is to start an incoming queue
Multiple identical trigger requests are collapsed into
one, and trigger requests are sorted so that <b>A</b> and <b>F</b> pre-
cede <b>D</b> and <b>I</b>. Thus, in order to force a deferred queue
- run, one would request <b>A</b> <b>F</b> <b>D</b>; in order to notify the queue
+ run, one would request <b>A F D</b>; in order to notify the queue
manager of the arrival of new mail one would request <b>I</b>.
<b>STANDARDS</b>
sudden burst of inbound mail can negatively impact out-
bound delivery rates.
-<b>CONFIGURATION</b> <b>PARAMETERS</b>
+<b>CONFIGURATION PARAMETERS</b>
The following <b>main.cf</b> parameters are especially relevant
to this program. See the Postfix <b>main.cf</b> file for syntax
- details and for default values. Use the <b>postfix</b> <b>reload</b>
+ details and for default values. Use the <b>postfix reload</b>
command after a configuration change.
<b>Miscellaneous</b>
<b>queue</b><i>_</i><b>directory</b>
Top-level directory of the Postfix queue.
-<b>Active</b> <b>queue</b> <b>controls</b>
+<b>Active queue controls</b>
<b>qmgr</b><i>_</i><b>clog</b><i>_</i><b>warn</b><i>_</i><b>time</b>
Minimal delay between warnings that a specific des-
tination is clogging up the active queue. Specify 0
This parameter also limits the size of the short-
term, in-memory destination cache.
-<b>Timing</b> <b>controls</b>
+<b>Timing controls</b>
<b>minimal</b><i>_</i><b>backoff</b><i>_</i><b>time</b>
Minimal time in seconds between delivery attempts
of a deferred message.
Time in seconds between attempts to contact a bro-
ken delivery transport.
-<b>Concurrency</b> <b>controls</b>
+<b>Concurrency controls</b>
In the text below, <i>transport</i> is the first field in a <b>mas-</b>
<b>ter.cf</b> entry.
same destination, for delivery via the named mes-
sage <i>transport</i>.
-<b>Recipient</b> <b>controls</b>
+<b>Recipient controls</b>
<b>default</b><i>_</i><b>destination</b><i>_</i><b>recipient</b><i>_</i><b>limit</b>
Default limit on the number of recipients per mes-
sage transfer.
Limit on the number of recipients per message
transfer, for the named message <i>transport</i>.
-<b>SEE</b> <b>ALSO</b>
+<b>SEE ALSO</b>
<a href="master.8.html">master(8)</a>, process manager
syslogd(8) system logging
<a href="trivial-rewrite.8.html">trivial-rewrite(8)</a>, address routing
quently, it cannot extract information from queue files in
the <b>maildrop</b> directory.
-<b>SEE</b> <b>ALSO</b>
+<b>SEE ALSO</b>
<a href="cleanup.8.html">cleanup(8)</a> canonicalize and enqueue mail
<a href="pickup.8.html">pickup(8)</a> local mail pickup service
<a href="qmgr.8.html">qmgr(8)</a> mail being delivered, delayed mail
and of other trouble.
<b>BUGS</b>
-<b>CONFIGURATION</b> <b>PARAMETERS</b>
+<b>CONFIGURATION PARAMETERS</b>
The following <b>main.cf</b> parameters are especially relevant
to this program. See the Postfix <b>main.cf</b> file for syntax
- details and for default values. Use the <b>postfix</b> <b>reload</b>
+ details and for default values. Use the <b>postfix reload</b>
command after a configuration change.
<b>Miscellaneous</b>
<b>ignore</b><i>_</i><b>mx</b><i>_</i><b>lookup</b><i>_</i><b>error</b>
When a name server fails to respond to an MX query,
- search for an A record instead deferring mail
+ search for an A record, instead of deferring mail
delivery.
<b>inet</b><i>_</i><b>interfaces</b>
PIX firewall <CR><LF>.<CR><LF> bug workaround is
turned on.
-<b>MIME</b> <b>Conversion</b>
+<b>MIME Conversion</b>
<b>disable</b><i>_</i><b>mime</b><i>_</i><b>output</b><i>_</i><b>conversion</b>
Disable the conversion of 8BITMIME format to 7BIT
format when the remote system does not advertise
nested deeper, when converting from 8BITMIME format
to 7BIT format.
-<b>Authentication</b> <b>controls</b>
+<b>Authentication controls</b>
<b>smtp</b><i>_</i><b>sasl</b><i>_</i><b>auth</b><i>_</i><b>enable</b>
Enable per-session authentication as per <a href="http://www.faqs.org/rfcs/rfc2554.html">RFC 2554</a>
(SASL). By default, Postfix is built without SASL
<b>noanonymous</b>
Disallow anonymous logins.
-<b>Resource</b> <b>controls</b>
+<b>Resource controls</b>
<b>smtp</b><i>_</i><b>destination</b><i>_</i><b>concurrency</b><i>_</i><b>limit</b>
Limit the number of parallel deliveries to the same
destination. The default limit is taken from the
ery. The default limit is taken from the
<b>default</b><i>_</i><b>destination</b><i>_</i><b>recipient</b><i>_</i><b>limit</b> parameter.
-<b>Timeout</b> <b>controls</b>
+<b>Timeout controls</b>
The default time unit is seconds; an explicit time unit
can be specified by appending a one-letter suffix to the
value: s (seconds), m (minutes), h (hours), d (days) or w
receiving the server response.
<b>smtp</b><i>_</i><b>mail</b><i>_</i><b>timeout</b>
- Timeout for sending the <b>MAIL</b> <b>FROM</b> command, and for
+ Timeout for sending the <b>MAIL FROM</b> command, and for
receiving the server response.
<b>smtp</b><i>_</i><b>rcpt</b><i>_</i><b>timeout</b>
- Timeout for sending the <b>RCPT</b> <b>TO</b> command, and for
+ Timeout for sending the <b>RCPT TO</b> command, and for
receiving the server response.
<b>smtp</b><i>_</i><b>data</b><i>_</i><b>init</b><i>_</i><b>timeout</b>
Timeout for sending the <b>QUIT</b> command, and for
receiving the server response.
-<b>SEE</b> <b>ALSO</b>
+<b>SEE ALSO</b>
<a href="bounce.8.html">bounce(8)</a> non-delivery status reports
<a href="master.8.html">master(8)</a> process manager
<a href="qmgr.8.html">qmgr(8)</a> queue manager
overrides built-in command definitions.
<b>Content inspection controls</b>
+ Optionally, Postfix can be configured to send new mail to
+ external content filter software AFTER the mail is queued.
+
<b>content</b><i>_</i><b>filter</b>
The name of a mail delivery transport that filters
mail and that either bounces mail or re-injects the
cally specified with the SMTP server <b>after</b>
an external content filter.
+<b>Pass-through proxy</b>
+ Optionally, the Postfix SMTP server can be configured to
+ forward all mail to a proxy server, for example a real-
+ time content filter, BEFORE mail is queued.
+
+ <b>smtpd</b><i>_</i><b>proxy</b><i>_</i><b>filter</b>
+ The <i>host:port</i> of the SMTP proxy server. The <i>host</i> or
+ <i>host:</i> portion is optional.
+
+ <b>smtpd</b><i>_</i><b>proxy</b><i>_</i><b>timeout</b>
+ Timeout for connecting to, sending to and receiving
+ from the SMTP proxy server.
+
+ <b>smtpd</b><i>_</i><b>proxy</b><i>_</i><b>ehlo</b>
+ The hostname to use when sending an EHLO command to
+ the SMTP proxy server.
+
<b>Authentication controls</b>
<b>enable</b><i>_</i><b>sasl</b><i>_</i><b>authentication</b>
- Enable per-session authentication as per <a href="http://www.faqs.org/rfcs/rfc2554.html">RFC 2554</a>
- (SASL). This functionality is available only when
+ Enable per-session authentication as per <a href="http://www.faqs.org/rfcs/rfc2554.html">RFC 2554</a>
+ (SASL). This functionality is available only when
explicitly selected at program build time and
explicitly enabled at runtime.
Disallow anonymous logins.
<b>smtpd</b><i>_</i><b>sender</b><i>_</i><b>login</b><i>_</i><b>maps</b>
- Maps that specify the SASL login name that owns a
- MAIL FROM sender address. Used by the
+ Maps that specify the SASL login name that owns a
+ MAIL FROM sender address. Used by the
<b>reject</b><i>_</i><b>sender</b><i>_</i><b>login</b><i>_</i><b>mismatch</b> sender anti-spoofing
restriction.
-<b>Pass-through proxy</b>
- Optionally, the Postfix SMTP server can be configured to
- forward all mail to a proxy server, for example a real-
- time content filter. This proxy server should support the
- same MAIL FROM and RCPT TO command syntax as Postfix, but
- does not need to support ESMTP command pipelining.
-
- <b>smtpd</b><i>_</i><b>proxy</b><i>_</i><b>filter</b>
- The <i>host:port</i> of the SMTP proxy server. The <i>host</i> or
- <i>host:</i> portion is optional.
-
- <b>smtpd</b><i>_</i><b>proxy</b><i>_</i><b>timeout</b>
- Timeout for connecting to, sending to and receiving
- from the SMTP proxy server.
-
- <b>smtpd</b><i>_</i><b>proxy</b><i>_</i><b>ehlo</b>
- The hostname to use when sending an EHLO command to
- the SMTP proxy server.
-
<b>Miscellaneous</b>
<b>authorized</b><i>_</i><b>verp</b><i>_</i><b>clients</b>
Hostnames, domain names and/or addresses of clients
SMTP session before it is penalized with tarpit
delays.
+<b>Delegated policy</b>
+ <b>smtpd</b><i>_</i><b>policy</b><i>_</i><b>service</b><i>_</i><b>endpoint</b>
+ The <i>transport</i>:<i>endpoint</i> of a server that speaks the
+ delegated SMTP policy protocol. <i>transport</i> is either
+ <b>inet</b> or <b>unix</b>. <i>endpoint</i> is either <i>host:port</i>, <b>pri-</b>
+ <b>vate/</b><i>servicename</i> or <b>public/</b><i>servicename</i>.
+
+ <b>smtpd</b><i>_</i><b>policy</b><i>_</i><b>service</b><i>_</i><b>timeout</b>
+ Time limit for connecting to, writing to and
+ receiving from a delegated SMTP policy server.
+
+ <b>smtpd</b><i>_</i><b>policy</b><i>_</i><b>service</b><i>_</i><b>max</b><i>_</i><b>idle</b>
+ Time after which an unused policy service connec-
+ tion is closed.
+
+ <b>smtpd</b><i>_</i><b>policy</b><i>_</i><b>service</b><i>_</i><b>timeout</b>
+ Time after which an active policy service connec-
+ tion is closed.
+
<b>UCE control restrictions</b>
<b>parent</b><i>_</i><b>domain</b><i>_</i><b>matches</b><i>_</i><b>subdomains</b>
List of Postfix features that use <i>domain.tld</i> pat-
This daemon expects to be run from the <a href="master.8.html"><b>master</b>(8)</a> process
manager.
-<b>COMMAND</b> <b>ATTRIBUTE</b> <b>SYNTAX</b>
+<b>COMMAND ATTRIBUTE SYNTAX</b>
The external command attributes are given in the <b>master.cf</b>
file at the end of a service definition. The syntax is as
follows:
talk to the external command and thus is not vulnerable to
data-driven attacks.
-<b>CONFIGURATION</b> <b>PARAMETERS</b>
+<b>CONFIGURATION PARAMETERS</b>
The following <b>main.cf</b> parameters are especially relevant
to this program. See the Postfix <b>main.cf</b> file for syntax
- details and for default values. Use the <b>postfix</b> <b>reload</b>
+ details and for default values. Use the <b>postfix reload</b>
command after a configuration change.
<b>Miscellaneous</b>
The process privileges used while not running an
external command.
-<b>Resource</b> <b>control</b>
+<b>Resource control</b>
<i>service_</i><b>command</b><i>_</i><b>time</b><i>_</i><b>limit</b>
The amount of time the command is allowed to run
before it is killed with force. The <i>service</i> name is
default time limit is given by the global <b>com-</b>
<b>mand</b><i>_</i><b>time</b><i>_</i><b>limit</b> configuration parameter.
-<b>SEE</b> <b>ALSO</b>
+<b>SEE ALSO</b>
<a href="master.8.html">master(8)</a> process manager
syslogd(8) system logging
terminated by the ASCII newline character. Request and
reply parameters (see below) are separated by whitespace.
+ Send and receive operations must complete in 100 seconds.
+
<b>REQUEST FORMAT</b>
Each request specifies a command, a lookup key, and possi-
bly a lookup result.
This request is currently not implemented.
<b>REPLY FORMAT</b>
- Each reply specifies a status code and text. Replies must
- be no longer than 4096 characters including the newline
+ Each reply specifies a status code and text. Replies must
+ be no longer than 4096 characters including the newline
terminator.
<b>500</b> SPACE <i>text</i> NEWLINE
- In case of a lookup request, the requested data
- does not exist. In case of an update request, the
+ In case of a lookup request, the requested data
+ does not exist. In case of an update request, the
request was rejected. The text describes the
nature of the problem.
<b>400</b> SPACE <i>text</i> NEWLINE
- This indicates an error condition. The text
- describes the nature of the problem. The client
+ This indicates an error condition. The text
+ describes the nature of the problem. The client
should retry the request later.
<b>200</b> SPACE <i>text</i> NEWLINE
The request was successful. In the case of a lookup
- request, the text contains an encoded version of
+ request, the text contains an encoded version of
the requested data.
<b>ENCODING</b>
- In request and reply parameters, the character %, each
+ In request and reply parameters, the character %, each
non-printing character, and each whitespace character must
- be replaced by %XX, where XX is the corresponding ASCII
- hexadecimal character value. The hexadecimal codes can be
+ be replaced by %XX, where XX is the corresponding ASCII
+ hexadecimal character value. The hexadecimal codes can be
specified in any case (upper, lower, mixed).
- The Postfix client always encodes a request. The server
- may omit the encoding as long as the reply is guaranteed
+ The Postfix client always encodes a request. The server
+ may omit the encoding as long as the reply is guaranteed
to not contain the % or NEWLINE character.
<b>SECURITY</b>
- Do not use TCP lookup tables for security critical pur-
- poses. The client-server connection is not protected and
+ Do not use TCP lookup tables for security critical pur-
+ poses. The client-server connection is not protected and
the server is not authenticated.
<b>SEE ALSO</b>
<b>BUGS</b>
Only the lookup method is currently implemented.
- The client does not hang up when the connection is idle
+ The client does not hang up when the connection is idle
for a long time.
<b>LICENSE</b>
- The Secure Mailer license must be distributed with this
+ The Secure Mailer license must be distributed with this
software.
<b>AUTHOR(S)</b>
replies are sent as one line of ASCII text, terminated by the
ASCII newline character. Request and reply parameters (see below)
are separated by whitespace.
+
+Send and receive operations must complete in 100 seconds.
.SH REQUEST FORMAT
.na
.nf
This program expects to be run from the \fBmaster\fR(8) process
manager.
-Mail addressed to the local \fBdouble-bounce\fR address is silently
-discarded. This stops potential loops caused by undeliverable
-bounce notifications.
+Mail addressed to the local \fBdouble-bounce\fR address is
+logged and discarded. This stops potential loops caused by
+undeliverable bounce notifications.
.SH MAIL QUEUES
.na
.nf
This program expects to be run from the \fBmaster\fR(8) process
manager.
-Mail addressed to the local \fBdouble-bounce\fR address is silently
-discarded. This stops potential loops caused by undeliverable
-bounce notifications.
+Mail addressed to the local \fBdouble-bounce\fR address is
+logged and discarded. This stops potential loops caused by
+undeliverable bounce notifications.
.SH MAIL QUEUES
.na
.nf
or if a destination is unreachable.
.IP \fBignore_mx_lookup_error\fR
When a name server fails to respond to an MX query, search for an
-A record instead deferring mail delivery.
+A record, instead of deferring mail delivery.
.IP \fBinet_interfaces\fR
The network interface addresses that this mail system receives
mail on. When any of those addresses appears in the list of mail
without any parameter syntax checking and without any state change.
This list overrides built-in command definitions.
.SH "Content inspection controls"
+Optionally, Postfix can be configured to send new mail to
+external content filter software AFTER the mail is queued.
.IP \fBcontent_filter\fR
The name of a mail delivery transport that filters mail and that
either bounces mail or re-injects the result back into Postfix.
Disable header/body_checks. This is typically specified with the
SMTP server \fBafter\fR an external content filter.
.RE
+.SH "Pass-through proxy"
+.ad
+.fi
+.ad
+Optionally, the Postfix SMTP server can be configured to
+forward all mail to a proxy server, for example a real-time
+content filter, BEFORE mail is queued.
+.IP \fBsmtpd_proxy_filter\fR
+The \fIhost:port\fR of the SMTP proxy server. The \fIhost\fR
+or \fIhost:\fR portion is optional.
+.IP \fBsmtpd_proxy_timeout\fR
+Timeout for connecting to, sending to and receiving from
+the SMTP proxy server.
+.IP \fBsmtpd_proxy_ehlo\fR
+The hostname to use when sending an EHLO command to the
+SMTP proxy server.
.SH "Authentication controls"
.IP \fBenable_sasl_authentication\fR
Enable per-session authentication as per RFC 2554 (SASL).
Maps that specify the SASL login name that owns a MAIL FROM sender
address. Used by the \fBreject_sender_login_mismatch\fR sender
anti-spoofing restriction.
-.SH "Pass-through proxy"
-.ad
-.fi
-.ad
-Optionally, the Postfix SMTP server can be configured to
-forward all mail to a proxy server, for example a real-time
-content filter. This proxy server should support the same
-MAIL FROM and RCPT TO command syntax as Postfix, but does not
-need to support ESMTP command pipelining.
-.IP \fBsmtpd_proxy_filter\fR
-The \fIhost:port\fR of the SMTP proxy server. The \fIhost\fR
-or \fIhost:\fR portion is optional.
-.IP \fBsmtpd_proxy_timeout\fR
-Timeout for connecting to, sending to and receiving from
-the SMTP proxy server.
-.IP \fBsmtpd_proxy_ehlo\fR
-The hostname to use when sending an EHLO command to the
-SMTP proxy server.
.SH Miscellaneous
.ad
.fi
Limit the number of times a client can issue a junk command
such as NOOP, VRFY, ETRN or RSET in one SMTP session before
it is penalized with tarpit delays.
+.SH "Delegated policy"
+.ad
+.fi
+.IP \fBsmtpd_policy_service_endpoint\fR
+The \fItransport\fR:\fIendpoint\fR of a server that speaks
+the delegated SMTP policy protocol. \fItransport\fR is
+either \fBinet\fR or \fBunix\fR. \fIendpoint\fR is either
+\fIhost:port\fR, \fBprivate/\fIservicename\fR or
+\fBpublic/\fIservicename\fR.
+.IP \fBsmtpd_policy_service_timeout\fR
+Time limit for connecting to, writing to and receiving from
+a delegated SMTP policy server.
+.IP \fBsmtpd_policy_service_max_idle\fR
+Time after which an unused policy service connection is closed.
+.IP \fBsmtpd_policy_service_timeout\fR
+Time after which an active policy service connection is closed.
.SH "UCE control restrictions"
.ad
.fi
# replies are sent as one line of ASCII text, terminated by the
# ASCII newline character. Request and reply parameters (see below)
# are separated by whitespace.
+#
+# Send and receive operations must complete in 100 seconds.
# REQUEST FORMAT
# .ad
# .fi
vstring_sprintf(why, "Name service error for name=%s type=%s: "
"Malformed name server reply",
name, dns_strtype(type));
- case DNS_NOTFOUND:
case DNS_OK:
+ /* Don't return DNS_OK when all results are censored away. */
+ if (rrlist && *rrlist == 0)
+ msg_panic("dns_lookup: name=%s type=%s: success but no data",
+ name, dns_strtype(type));
+ case DNS_NOTFOUND:
return (status);
case DNS_RECURSE:
if (msg_verbose)
char *myname = "mail_copy";
VSTRING *buf;
char *bp;
- long orig_length;
+ off_t orig_length;
int read_error;
int write_error;
int corrupt_error = 0;
*/
#ifndef NO_TRUNCATE
if ((flags & MAIL_COPY_TOFILE) != 0)
- if ((orig_length = vstream_fseek(dst, 0L, SEEK_END)) < 0)
+ if ((orig_length = vstream_fseek(dst, (off_t) 0, SEEK_END)) < 0)
msg_fatal("seek file %s: %m", VSTREAM_PATH(dst));
#endif
buf = vstring_alloc(100);
#ifndef NO_TRUNCATE
if ((flags & MAIL_COPY_TOFILE) != 0)
if (corrupt_error || read_error || write_error)
- ftruncate(vstream_fileno(dst), (off_t) orig_length);
+ ftruncate(vstream_fileno(dst), orig_length);
#endif
write_error |= vstream_fclose(dst);
if (why && read_error)
#define DEF_INPUT_TRANSP ""
extern char *var_smtpd_input_transp;
+ /*
+ * SMTP server policy delegation.
+ */
+#define VAR_SMTPD_POLICY_SRV "smtpd_policy_service_endpoint"
+#define DEF_SMTPD_POLICY_SRV ""
+extern char *var_smtpd_policy_srv;
+
+#define VAR_SMTPD_POLICY_TMOUT "smtpd_policy_service_timeout"
+#define DEF_SMTPD_POLICY_TMOUT "100s"
+extern int var_smtpd_policy_tmout;
+
+#define VAR_SMTPD_POLICY_IDLE "smtpd_policy_service_max_idle"
+#define DEF_SMTPD_POLICY_IDLE "300s"
+extern int var_smtpd_policy_idle;
+
+#define VAR_SMTPD_POLICY_TTL "smtpd_policy_service_max_ttl"
+#define DEF_SMTPD_POLICY_TTL "1000s"
+extern int var_smtpd_policy_ttl;
+
+#define CHECK_POLICY_SERVICE "check_smtpd_policy_service"
+
/* LICENSE
/* .ad
/* .fi
* 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 "20030706"
+#define MAIL_RELEASE_DATE "20030712"
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "2.0.13-" MAIL_RELEASE_DATE
+#define DEF_MAIL_VERSION "2.0.14-" MAIL_RELEASE_DATE
extern char *var_mail_version;
/*
* LMTP server. Otherwise, we might do the wrong thing when the server
* advertises a really huge message size limit.
*/
+ state->features = 0;
lines = resp->str;
(void) mystrtok(&lines, "\n");
while ((words = mystrtok(&lines, "\n")) != 0) {
extern void lmtp_sasl_initialize(void);
extern void lmtp_sasl_connect(LMTP_STATE *);
extern int lmtp_sasl_passwd_lookup(LMTP_STATE *);
-extern void lmtp_sasl_start(LMTP_STATE *);
+extern void lmtp_sasl_start(LMTP_STATE *, const char *, const char *);
extern int lmtp_sasl_authenticate(LMTP_STATE *, VSTRING *);
extern void lmtp_sasl_cleanup(LMTP_STATE *);
/* void lmtp_sasl_connect(state)
/* LMTP_STATE *state;
/*
-/* void lmtp_sasl_start(state)
+/* void lmtp_sasl_start(state, sasl_opts_name, sasl_opts_var)
/* LMTP_STATE *state;
/*
/* int lmtp_sasl_passwd_lookup(state)
/*
/* lmtp_sasl_start() performs per-session initialization. This
/* routine must be called once per session before doing any SASL
-/* authentication.
+/* authentication. The sasl_opts_name and sasl_opts_var parameters are
+/* the postfix configuration parameters setting the security
+/* policy of the SASL authentication.
/*
/* lmtp_sasl_passwd_lookup() looks up the username/password
/* for the current SMTP server. The result is zero in case
0,
};
-static int lmtp_sasl_sec_opts;
-
/*
* Silly little macros.
*/
if (sasl_client_init(callbacks) != SASL_OK)
msg_fatal("SASL library initialization");
- /*
- * Configuration parameters.
- */
- lmtp_sasl_sec_opts = name_mask(VAR_LMTP_SASL_OPTS, lmtp_sasl_sec_mask,
- var_lmtp_sasl_opts);
}
/* lmtp_sasl_connect - per-session client initialization */
/* lmtp_sasl_start - per-session SASL initialization */
-void lmtp_sasl_start(LMTP_STATE *state)
+void lmtp_sasl_start(LMTP_STATE *state, const char *sasl_opts_name,
+ const char *sasl_opts_var)
{
static sasl_callback_t callbacks[] = {
{SASL_CB_USER, &lmtp_sasl_get_user, 0},
sec_props.min_ssf = 0;
sec_props.max_ssf = 1; /* don't allow real SASL
* security layer */
- sec_props.security_flags = lmtp_sasl_sec_opts;
+ sec_props.security_flags = name_mask(sasl_opts_name, lmtp_sasl_sec_mask,
+ sasl_opts_var);
sec_props.maxbufsize = 0;
sec_props.property_names = 0;
sec_props.property_values = 0;
* required, and assume that an authentication error is recoverable.
*/
if (lmtp_sasl_passwd_lookup(state) != 0) {
- lmtp_sasl_start(state);
+ lmtp_sasl_start(state, VAR_LMTP_SASL_OPTS, var_lmtp_sasl_opts);
if (lmtp_sasl_authenticate(state, why) <= 0)
ret = lmtp_site_fail(state, 450, "Authentication failed: %s",
vstring_str(why));
/* This program expects to be run from the \fBmaster\fR(8) process
/* manager.
/*
-/* Mail addressed to the local \fBdouble-bounce\fR address is silently
-/* discarded. This stops potential loops caused by undeliverable
-/* bounce notifications.
+/* Mail addressed to the local \fBdouble-bounce\fR address is
+/* logged and discarded. This stops potential loops caused by
+/* undeliverable bounce notifications.
/* MAIL QUEUES
/* .ad
/* .fi
/* This program expects to be run from the \fBmaster\fR(8) process
/* manager.
/*
-/* Mail addressed to the local \fBdouble-bounce\fR address is silently
-/* discarded. This stops potential loops caused by undeliverable
-/* bounce notifications.
+/* Mail addressed to the local \fBdouble-bounce\fR address is
+/* logged and discarded. This stops potential loops caused by
+/* undeliverable bounce notifications.
/* MAIL QUEUES
/* .ad
/* .fi
static struct queue_info queue_info[] = {
MAIL_QUEUE_MAILDROP, scan_dir_next,
- MAIL_QUEUE_INCOMING, mail_scan_dir_next,
MAIL_QUEUE_ACTIVE, mail_scan_dir_next,
+ MAIL_QUEUE_INCOMING, mail_scan_dir_next,
MAIL_QUEUE_DEFERRED, mail_scan_dir_next,
MAIL_QUEUE_HOLD, mail_scan_dir_next,
0,
/* or if a destination is unreachable.
/* .IP \fBignore_mx_lookup_error\fR
/* When a name server fails to respond to an MX query, search for an
-/* A record instead deferring mail delivery.
+/* A record, instead of deferring mail delivery.
/* .IP \fBinet_interfaces\fR
/* The network interface addresses that this mail system receives
/* mail on. When any of those addresses appears in the list of mail
extern void smtp_sasl_initialize(void);
extern void smtp_sasl_connect(SMTP_STATE *);
extern int smtp_sasl_passwd_lookup(SMTP_STATE *);
-extern void smtp_sasl_start(SMTP_STATE *);
+extern void smtp_sasl_start(SMTP_STATE *, const char *, const char *);
extern int smtp_sasl_authenticate(SMTP_STATE *, VSTRING *);
extern void smtp_sasl_cleanup(SMTP_STATE *);
/* void smtp_sasl_connect(state)
/* SMTP_STATE *state;
/*
-/* void smtp_sasl_start(state)
+/* void smtp_sasl_start(state, sasl_opts_name, sasl_opts_var)
/* SMTP_STATE *state;
/*
/* int smtp_sasl_passwd_lookup(state)
/*
/* smtp_sasl_start() performs per-session initialization. This
/* routine must be called once per session before doing any SASL
-/* authentication.
+/* authentication. The sasl_opts_name and sasl_opts_var parameters are
+/* the postfix configuration parameters setting the security
+/* policy of the SASL authentication.
/*
/* smtp_sasl_passwd_lookup() looks up the username/password
/* for the current SMTP server. The result is zero in case
0,
};
-static int smtp_sasl_sec_opts;
-
/*
* Silly little macros.
*/
if (sasl_client_init(callbacks) != SASL_OK)
msg_fatal("SASL library initialization");
- /*
- * Configuration parameters.
- */
- smtp_sasl_sec_opts = name_mask(VAR_SMTP_SASL_OPTS, smtp_sasl_sec_mask,
- var_smtp_sasl_opts);
}
/* smtp_sasl_connect - per-session client initialization */
/* smtp_sasl_start - per-session SASL initialization */
-void smtp_sasl_start(SMTP_STATE *state)
+void smtp_sasl_start(SMTP_STATE *state, const char *sasl_opts_name,
+ const char *sasl_opts_var)
{
static sasl_callback_t callbacks[] = {
{SASL_CB_USER, &smtp_sasl_get_user, 0},
sec_props.min_ssf = 0;
sec_props.max_ssf = 1; /* don't allow real SASL
* security layer */
- sec_props.security_flags = smtp_sasl_sec_opts;
+ sec_props.security_flags = name_mask(sasl_opts_name, smtp_sasl_sec_mask,
+ sasl_opts_var);
sec_props.maxbufsize = 0;
sec_props.property_names = 0;
sec_props.property_values = 0;
* required, and assume that an authentication error is recoverable.
*/
if (smtp_sasl_passwd_lookup(state) != 0) {
- smtp_sasl_start(state);
+ smtp_sasl_start(state, VAR_SMTP_SASL_OPTS, var_smtp_sasl_opts);
if (smtp_sasl_authenticate(state, why) <= 0)
ret = smtp_site_fail(state, 450, "Authentication failed: %s",
vstring_str(why));
/* without any parameter syntax checking and without any state change.
/* This list overrides built-in command definitions.
/* .SH "Content inspection controls"
+/* Optionally, Postfix can be configured to send new mail to
+/* external content filter software AFTER the mail is queued.
/* .IP \fBcontent_filter\fR
/* The name of a mail delivery transport that filters mail and that
/* either bounces mail or re-injects the result back into Postfix.
/* Disable header/body_checks. This is typically specified with the
/* SMTP server \fBafter\fR an external content filter.
/* .RE
+/* .SH "Pass-through proxy"
+/* .ad
+/* .fi
+/* .ad
+/* Optionally, the Postfix SMTP server can be configured to
+/* forward all mail to a proxy server, for example a real-time
+/* content filter, BEFORE mail is queued.
+/* .IP \fBsmtpd_proxy_filter\fR
+/* The \fIhost:port\fR of the SMTP proxy server. The \fIhost\fR
+/* or \fIhost:\fR portion is optional.
+/* .IP \fBsmtpd_proxy_timeout\fR
+/* Timeout for connecting to, sending to and receiving from
+/* the SMTP proxy server.
+/* .IP \fBsmtpd_proxy_ehlo\fR
+/* The hostname to use when sending an EHLO command to the
+/* SMTP proxy server.
/* .SH "Authentication controls"
/* .IP \fBenable_sasl_authentication\fR
/* Enable per-session authentication as per RFC 2554 (SASL).
/* Maps that specify the SASL login name that owns a MAIL FROM sender
/* address. Used by the \fBreject_sender_login_mismatch\fR sender
/* anti-spoofing restriction.
-/* .SH "Pass-through proxy"
-/* .ad
-/* .fi
-/* .ad
-/* Optionally, the Postfix SMTP server can be configured to
-/* forward all mail to a proxy server, for example a real-time
-/* content filter. This proxy server should support the same
-/* MAIL FROM and RCPT TO command syntax as Postfix, but does not
-/* need to support ESMTP command pipelining.
-/* .IP \fBsmtpd_proxy_filter\fR
-/* The \fIhost:port\fR of the SMTP proxy server. The \fIhost\fR
-/* or \fIhost:\fR portion is optional.
-/* .IP \fBsmtpd_proxy_timeout\fR
-/* Timeout for connecting to, sending to and receiving from
-/* the SMTP proxy server.
-/* .IP \fBsmtpd_proxy_ehlo\fR
-/* The hostname to use when sending an EHLO command to the
-/* SMTP proxy server.
/* .SH Miscellaneous
/* .ad
/* .fi
/* Limit the number of times a client can issue a junk command
/* such as NOOP, VRFY, ETRN or RSET in one SMTP session before
/* it is penalized with tarpit delays.
+/* .SH "Delegated policy"
+/* .ad
+/* .fi
+/* .IP \fBsmtpd_policy_service_endpoint\fR
+/* The \fItransport\fR:\fIendpoint\fR of a server that speaks
+/* the delegated SMTP policy protocol. \fItransport\fR is
+/* either \fBinet\fR or \fBunix\fR. \fIendpoint\fR is either
+/* \fIhost:port\fR, \fBprivate/\fIservicename\fR or
+/* \fBpublic/\fIservicename\fR.
+/* .IP \fBsmtpd_policy_service_timeout\fR
+/* Time limit for connecting to, writing to and receiving from
+/* a delegated SMTP policy server.
+/* .IP \fBsmtpd_policy_service_max_idle\fR
+/* Time after which an unused policy service connection is closed.
+/* .IP \fBsmtpd_policy_service_timeout\fR
+/* Time after which an active policy service connection is closed.
/* .SH "UCE control restrictions"
/* .ad
/* .fi
int var_smtpd_proxy_tmout;
char *var_smtpd_proxy_ehlo;
char *var_input_transp;
+char *var_smtpd_policy_srv;
+int var_smtpd_policy_tmout;
+int var_smtpd_policy_idle;
+int var_smtpd_policy_ttl;
/*
* Silly little macros.
VAR_SMTPD_ERR_SLEEP, DEF_SMTPD_ERR_SLEEP, &var_smtpd_err_sleep, 0, 0,
VAR_SMTPD_PROXY_TMOUT, DEF_SMTPD_PROXY_TMOUT, &var_smtpd_proxy_tmout, 1, 0,
VAR_VERIFY_POLL_DELAY, DEF_VERIFY_POLL_DELAY, &var_verify_poll_delay, 1, 0,
+ VAR_SMTPD_POLICY_TMOUT, DEF_SMTPD_POLICY_TMOUT, &var_smtpd_policy_tmout, 1, 0,
+ VAR_SMTPD_POLICY_IDLE, DEF_SMTPD_POLICY_IDLE, &var_smtpd_policy_idle, 1, 0,
+ VAR_SMTPD_POLICY_TTL, DEF_SMTPD_POLICY_TTL, &var_smtpd_policy_ttl, 1, 0,
0,
};
static CONFIG_BOOL_TABLE bool_table[] = {
VAR_SMTPD_PROXY_FILT, DEF_SMTPD_PROXY_FILT, &var_smtpd_proxy_filt, 0, 0,
VAR_SMTPD_PROXY_EHLO, DEF_SMTPD_PROXY_EHLO, &var_smtpd_proxy_ehlo, 0, 0,
VAR_INPUT_TRANSP, DEF_INPUT_TRANSP, &var_input_transp, 0, 0,
+ VAR_SMTPD_POLICY_SRV, DEF_SMTPD_POLICY_SRV, &var_smtpd_policy_srv, 0, 0,
0,
};
static CONFIG_RAW_TABLE raw_table[] = {
/*
* SMPTD peer information lookup.
*/
-void smtpd_peer_init(SMTPD_STATE *state);
-void smtpd_peer_reset(SMTPD_STATE *state);
+extern void smtpd_peer_init(SMTPD_STATE *state);
+extern void smtpd_peer_reset(SMTPD_STATE *state);
/*
* Transparency: before mail is queued, do we check for unknown recipients,
* do we allow address mapping, automatic bcc, header/body checks?
*/
-int smtpd_input_transp_mask;
+extern int smtpd_input_transp_mask;
/* LICENSE
/* .ad
/* Reject, defer or permit the request unconditionally. This is to be used
/* at the end of a restriction list in order to make the default
/* action explicit.
+/* .IP check_policy_service
+/* query an external policy service with client, helo, sender, recipient
+/* and queue ID attributes.
/* .IP reject_unknown_client
/* Reject the request when the client hostname could not be found.
/* The \fIunknown_client_reject_code\fR configuration parameter
/* The message would use up more than half the available queue file
/* system space. This is a temporary error.
/* .PP
+/* smtpd_check_data() enforces generic restrictions after the
+/* client has sent the DATA command.
+/*
/* Arguments:
/* .IP name
/* The client hostname, or \fIunknown\fR.
#include <htable.h>
#include <ctable.h>
#include <mac_expand.h>
+#include <myrand.h>
+#include <attr_clnt.h>
/* DNS library. */
*/
static int check_rcpt_maps(SMTPD_STATE *state, const char *recipient);
+ /*
+ * Delegated policy.
+ */
+static ATTR_CLNT *policy_clnt;
+
/*
* Reject context.
*/
* Cached RBL lookup state.
*/
typedef struct {
- char *txt; /* TXT record or "" */
+ ARGV *txt; /* TXT records or NULL */
+ ARGV *a; /* A records */
} SMTPD_RBL_STATE;
static void *rbl_pagein(const char *, void *);
*/
typedef struct {
SMTPD_STATE *state; /* general state */
- SMTPD_RBL_STATE *rbl_state; /* cached RBL state */
const char *domain; /* query domain */
const char *what; /* rejected value */
const char *class; /* name of rejected value */
+ const char *txt; /* randomly selected trimmed TXT rr */
} SMTPD_RBL_EXPAND_CONTEXT;
/* resolve_pagein - page in an address resolver result */
*/
expand_filter = vstring_alloc(10);
unescape(expand_filter, var_smtpd_exp_filter);
+
+ /*
+ * Delegated policy.
+ */
+ if (*var_smtpd_policy_srv)
+ policy_clnt = attr_clnt_create(var_smtpd_policy_srv,
+ var_smtpd_policy_tmout,
+ var_smtpd_policy_idle,
+ var_smtpd_policy_ttl);
}
/* log_whatsup - log as much context as we have */
VSTRING *why;
int dns_status;
SMTPD_RBL_STATE *rbl;
+ DNS_RR *addr_list;
+ struct in_addr addr;
+ DNS_RR *rr;
/*
* Do the query. If the DNS lookup produces no definitive reply, give the
* because an RBL server is unavailable.
*/
why = vstring_alloc(10);
- dns_status = dns_lookup(query, T_A, 0, (DNS_RR **) 0,
- (VSTRING *) 0, why);
+ dns_status = dns_lookup(query, T_A, 0, &addr_list, (VSTRING *) 0, why);
if (dns_status != DNS_OK && dns_status != DNS_NOTFOUND)
msg_warn("%s: RBL lookup error: %s", query, STR(why));
vstring_free(why);
* Save the result. Yes, we cache negative results as well as positive
* results.
*/
+#define RBL_TXT_LIMIT 256
+
rbl = (SMTPD_RBL_STATE *) mymalloc(sizeof(*rbl));
if (dns_lookup(query, T_TXT, 0, &txt_list,
(VSTRING *) 0, (VSTRING *) 0) == DNS_OK) {
- rbl->txt = mystrndup(txt_list->data, 512);
+ rbl->txt = argv_alloc(1);
+ for (rr = txt_list; rr != 0; rr = rr->next)
+ argv_addn(rbl->txt, rr->data, rr->data_len > RBL_TXT_LIMIT ?
+ RBL_TXT_LIMIT : rr->data_len, ARGV_END);
dns_rr_free(txt_list);
} else
- rbl->txt = mystrdup("");
+ rbl->txt = 0;
+ rbl->a = argv_alloc(1);
+ for (rr = addr_list; rr != 0; rr = rr->next) {
+ memcpy((char *) &addr.s_addr, addr_list->data, sizeof(addr.s_addr));
+ argv_add(rbl->a, inet_ntoa(addr), ARGV_END);
+ }
+ dns_rr_free(addr_list);
return ((void *) rbl);
}
if (rbl != 0) {
if (rbl->txt)
- myfree(rbl->txt);
+ argv_free(rbl->txt);
+ if (rbl->a)
+ argv_free(rbl->a);
myfree((char *) rbl);
}
}
char *context)
{
SMTPD_RBL_EXPAND_CONTEXT *rbl_exp = (SMTPD_RBL_EXPAND_CONTEXT *) context;
- SMTPD_RBL_STATE *rbl = rbl_exp->rbl_state;
SMTPD_STATE *state = rbl_exp->state;
if (state->expand_buf == 0)
} else if (STREQ(name, MAIL_ATTR_RBL_DOMAIN)) {
return (rbl_exp->domain);
} else if (STREQ(name, MAIL_ATTR_RBL_REASON)) {
- return (rbl->txt);
+ return (rbl_exp->txt);
} else if (STREQ(name, MAIL_ATTR_RBL_TXT)) {/* LaMont compat */
- return (rbl->txt);
+ return (rbl_exp->txt);
} else if (STREQ(name, MAIL_ATTR_RBL_WHAT)) {
return (rbl_exp->what);
} else if (STREQ(name, MAIL_ATTR_RBL_CLASS)) {
}
why = vstring_alloc(100);
rbl_exp.state = state;
- rbl_exp.rbl_state = rbl;
rbl_exp.domain = rbl_domain;
rbl_exp.what = what;
rbl_exp.class = reply_class;
+
+ /*
+ * XXX When presented with multiple txt records pick a random one! There
+ * is no way to pair up the A records with associated TXT records. If a
+ * client connects multiple times, it will learn the right reject reason
+ * at least some of the time.
+ */
+ rbl_exp.txt = mystrdup(rbl->txt == 0 ? "" :
+ rbl->txt->argc == 1 ? rbl->txt->argv[0] :
+ rbl->txt->argv[myrand() % rbl->txt->argc]);
+
for (;;) {
if (template == 0)
template = var_def_rbl_reply;
* Clean up.
*/
vstring_free(why);
+ myfree((char *) rbl_exp.txt);
return (result);
}
+/* rbl_match_addr - match address list */
+
+static int rbl_match_addr(SMTPD_RBL_STATE *rbl, const char *addr)
+{
+ char **cpp;
+
+ for (cpp = rbl->a->argv; *cpp; cpp++)
+ if (strcmp(*cpp, addr) == 0)
+ return (1);
+ return (0);
+}
+
/* reject_rbl_addr - reject if address in real-time blackhole list */
static int reject_rbl_addr(SMTPD_STATE *state, const char *rbl_domain,
VSTRING *query;
int i;
SMTPD_RBL_STATE *rbl;
+ const char *reply_addr;
if (msg_verbose)
msg_info("%s: %s %s", myname, reply_class, addr);
}
argv_free(octets);
vstring_strcat(query, rbl_domain);
+ reply_addr = split_at(STR(query), '=');
rbl = (SMTPD_RBL_STATE *) ctable_locate(smtpd_rbl_cache, STR(query));
- vstring_free(query);
/*
* If the record exists, the address is blacklisted.
*/
- if (rbl == 0) {
+ if (rbl == 0 || (reply_addr != 0 && !rbl_match_addr(rbl, reply_addr))) {
+ vstring_free(query);
return (SMTPD_CHECK_DUNNO);
} else {
+ vstring_free(query);
return (rbl_reject_reply(state, rbl, rbl_domain, addr, reply_class));
}
}
VSTRING *query;
SMTPD_RBL_STATE *rbl;
const char *domain;
+ const char *reply_addr;
if (msg_verbose)
msg_info("%s: %s %s", myname, reply_class, what);
query = vstring_alloc(100);
vstring_sprintf(query, "%s.%s", domain, rbl_domain);
+ reply_addr = split_at(STR(query), '=');
rbl = (SMTPD_RBL_STATE *) ctable_locate(smtpd_rbl_cache, STR(query));
- vstring_free(query);
/*
* If the record exists, the domain is blacklisted.
*/
- if (rbl == 0) {
+ if (rbl == 0 || (reply_addr != 0 && !rbl_match_addr(rbl, reply_addr))) {
+ vstring_free(query);
return (SMTPD_CHECK_DUNNO);
} else {
+ vstring_free(query);
return (rbl_reject_reply(state, rbl, rbl_domain, what, reply_class));
}
}
return (SMTPD_CHECK_DUNNO);
}
+/* check_policy_service - check delegated policy service */
+
+static int check_policy_service(SMTPD_STATE *state, const char *reply_name,
+ const char *reply_class, const char *def_acl)
+{
+ static VSTRING *action = 0;
+
+ /*
+ * Sanity check.
+ */
+ if (policy_clnt == 0) {
+ msg_warn("%s is used in %s restrictions, but no service is defined",
+ CHECK_POLICY_SERVICE, reply_class);
+ return (SMTPD_CHECK_DUNNO);
+ }
+
+ /*
+ * Initialize.
+ */
+ if (action == 0)
+ action = vstring_alloc(10);
+
+ if (attr_clnt_request(policy_clnt,
+ ATTR_FLAG_NONE, /* Query attributes. */
+ ATTR_TYPE_STR, MAIL_ATTR_CLIENT, state->namaddr,
+ ATTR_TYPE_STR, MAIL_ATTR_CLIENT_ADDR, state->addr,
+ ATTR_TYPE_STR, MAIL_ATTR_CLIENT_NAME, state->name,
+ ATTR_TYPE_STR, MAIL_ATTR_HELO_NAME,
+ state->helo_name ? state->helo_name : "",
+ ATTR_TYPE_STR, MAIL_ATTR_SENDER,
+ state->sender ? state->sender : "",
+ ATTR_TYPE_STR, MAIL_ATTR_RECIP,
+ state->recipient ? state->recipient : "",
+ ATTR_TYPE_STR, MAIL_ATTR_QUEUEID,
+ state->queue_id ? state->queue_id : "",
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes. */
+ ATTR_TYPE_STR, MAIL_ATTR_ACTION, action,
+ ATTR_TYPE_END) != 1) {
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ "450 Server configuration problem"));
+ } else {
+
+ /*
+ * XXX This produces bogus error messages when the reply is
+ * malformed.
+ */
+ return (check_table_result(state, var_smtpd_policy_srv, STR(action),
+ "policy query", reply_name,
+ reply_class, def_acl));
+ }
+}
+
/* is_map_command - restriction has form: check_xxx_access type:name */
static int is_map_command(SMTPD_STATE *state, const char *name,
cpp[1], REJECT_ALL);
} else if (strcasecmp(name, REJECT_UNAUTH_PIPE) == 0) {
status = reject_unauth_pipelining(state, reply_name, reply_class);
+ } else if (strcasecmp(name, CHECK_POLICY_SERVICE) == 0) {
+ status = check_policy_service(state, reply_name,
+ reply_class, def_acl);
} else if (strcasecmp(name, DEFER_IF_PERMIT) == 0) {
DEFER_IF_PERMIT2(state, MAIL_ERROR_POLICY,
"450 <%s>: %s rejected: defer_if_permit requested",
#include <smtpd_chat.h>
+int smtpd_input_transp_mask;
+
/*
* Dummies. These are never set.
*/
char *var_def_rbl_reply;
char *var_relay_rcpt_maps;
char *var_verify_sender;
+char *var_smtpd_sasl_opts;
+char *var_smtpd_policy_srv;
typedef struct {
char *name;
VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps,
VAR_VERIFY_SENDER, DEF_VERIFY_SENDER, &var_verify_sender,
VAR_MAIL_NAME, DEF_MAIL_NAME, &var_mail_name,
+ VAR_SMTPD_SASL_OPTS, DEF_SMTPD_SASL_OPTS, &var_smtpd_sasl_opts,
+ VAR_SMTPD_POLICY_SRV, DEF_SMTPD_POLICY_SRV, &var_smtpd_policy_srv, 0, 0,
0,
};
int var_show_unk_rcpt_table;
int var_verify_poll_count;
int var_verify_poll_delay;
+int var_smtpd_policy_tmout;
+int var_smtpd_policy_idle;
+int var_smtpd_policy_ttl;
static INT_TABLE int_table[] = {
"msg_verbose", 0, &msg_verbose,
VAR_VIRT_MAILBOX_CODE, DEF_VIRT_MAILBOX_CODE, &var_virt_mailbox_code,
VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table,
VAR_VERIFY_POLL_COUNT, DEF_VERIFY_POLL_COUNT, &var_verify_poll_count,
- VAR_VERIFY_POLL_DELAY, DEF_VERIFY_POLL_DELAY, &var_verify_poll_delay,
0,
};
/* smtpd_sasl_connect - stub */
-void smtpd_sasl_connect(SMTPD_STATE *state)
+void smtpd_sasl_connect(SMTPD_STATE *state, const char *opts_name,
+ const char *opts_var)
{
msg_panic("smtpd_sasl_connect was called");
}
extern char *smtpd_check_rcpt(SMTPD_STATE *, char *);
extern char *smtpd_check_etrn(SMTPD_STATE *, char *);
extern char *smtpd_check_data(SMTPD_STATE *);
+extern char *smtpd_check_policy(SMTPD_STATE *, char *);
/* LICENSE
/* .ad
rcpt rname@rdomain
client foo 127.0.0.2
rcpt rname@rdomain
+recipient_restrictions reject_rbl_client,relays.mail-abuse.org=127.0.0.2
+client foo 127.0.0.2
+rcpt rname@rdomain
+client foo 127.0.0.1
+rcpt rname@rdomain
#
# RHSBL sender domain name
#
>>> rcpt rname@rdomain
./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 Service unavailable; Client host [127.0.0.2] blocked using relays.mail-abuse.org; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
554 Service unavailable; Client host [127.0.0.2] blocked using relays.mail-abuse.org
+>>> recipient_restrictions reject_rbl_client,relays.mail-abuse.org=127.0.0.2
+OK
+>>> client foo 127.0.0.2
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 Service unavailable; Client host [127.0.0.2] blocked using relays.mail-abuse.org=127.0.0.2; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 Service unavailable; Client host [127.0.0.2] blocked using relays.mail-abuse.org=127.0.0.2
+>>> client foo 127.0.0.1
+OK
+>>> rcpt rname@rdomain
+OK
>>> #
>>> # RHSBL sender domain name
>>> #
/* /* other fields... */
/* .in -4
/* } SMTPD_STATE;
-/* SMTP-LEVEL ROUTINES
+/*
/* int smtpd_proxy_open(state, service, timeout, ehlo_name, mail_from)
/* SMTPD_STATE *state;
/* const char *service;
/*
/* void smtpd_sasl_initialize()
/*
-/* void smtpd_sasl_connect(state)
+/* void smtpd_sasl_connect(state, sasl_opts_name, sasl_opts_var)
/* SMTPD_STATE *state;
/*
/* char *smtpd_sasl_authenticate(state, sasl_method, init_response)
/*
/* smtpd_sasl_connect() performs per-connection initialization.
/* This routine should be called once at the start of every
-/* connection.
+/* connection. The sasl_opts_name and sasl_opts_var parameters
+/* are the postfix configuration parameters setting the security
+/* policy of the SASL authentication.
/*
/* smtpd_sasl_authenticate() implements the authentication dialog.
/* The result is a null pointer in case of success, an SMTP reply
0,
};
-static int smtpd_sasl_opts;
-
/* smtpd_sasl_initialize - per-process initialization */
void smtpd_sasl_initialize(void)
if (sasl_server_init(callbacks, "smtpd") != SASL_OK)
msg_fatal("SASL per-process initialization failed");
- /*
- * Configuration parameters.
- */
- smtpd_sasl_opts = name_mask(VAR_SMTPD_SASL_OPTS, smtpd_sasl_mask,
- var_smtpd_sasl_opts);
}
/* smtpd_sasl_connect - per-connection initialization */
-void smtpd_sasl_connect(SMTPD_STATE *state)
+void smtpd_sasl_connect(SMTPD_STATE *state, const char *sasl_opts_name,
+ const char *sasl_opts_var)
{
#if SASL_VERSION_MAJOR < 2
unsigned sasl_mechanism_count;
sec_props.min_ssf = 0;
sec_props.max_ssf = 1; /* don't allow real SASL
* security layer */
- sec_props.security_flags = smtpd_sasl_opts;
+ sec_props.security_flags = name_mask(sasl_opts_name, smtpd_sasl_mask,
+ sasl_opts_var);
sec_props.maxbufsize = 0;
sec_props.property_names = 0;
sec_props.property_values = 0;
* SASL protocol interface
*/
extern void smtpd_sasl_initialize(void);
-extern void smtpd_sasl_connect(SMTPD_STATE *);
+extern void smtpd_sasl_connect(SMTPD_STATE *, const char *, const char *);
extern void smtpd_sasl_disconnect(SMTPD_STATE *);
extern char *smtpd_sasl_authenticate(SMTPD_STATE *, const char *, const char *);
extern void smtpd_sasl_logout(SMTPD_STATE *);
if (SMTPD_STAND_ALONE(state))
var_smtpd_sasl_enable = 0;
if (var_smtpd_sasl_enable)
- smtpd_sasl_connect(state);
+ smtpd_sasl_connect(state, VAR_SMTPD_SASL_OPTS, var_smtpd_sasl_opts);
#endif
/*
if (*group == 0)
group = 0;
if ((pwd = getpwnam(user)) == 0)
- msg_fatal("%s: unknown username: %s", myname, user);
+ msg_fatal("unknown user name: %s", user);
attr->uid = pwd->pw_uid;
if (group != 0) {
if ((grp = getgrnam(group)) == 0)
- msg_fatal("%s: unknown group: %s", myname, group);
+ msg_fatal("unknown group name: %s", group);
attr->gid = grp->gr_gid;
} else {
attr->gid = pwd->pw_gid;
unescape.c unix_connect.c unix_listen.c unix_trigger.c unsafe.c \
username.c valid_hostname.c vbuf.c vbuf_print.c vstream.c \
vstream_popen.c vstring.c vstring_vstream.c watchdog.c writable.c \
- write_buf.c write_wait.c
+ write_buf.c write_wait.c auto_clnt.c attr_clnt.c attr_scan_plain.c \
+ attr_print_plain.c
OBJS = alldig.o argv.o argv_split.o attr_print0.o attr_print64.o \
attr_scan0.o attr_scan64.o base64_code.o basename.o binhash.o \
chroot_uid.o clean_env.o close_on_exec.o concatenate.o ctable.o \
unescape.o unix_connect.o unix_listen.o unix_trigger.o unsafe.o \
username.o valid_hostname.o vbuf.o vbuf_print.o vstream.o \
vstream_popen.o vstring.o vstring_vstream.o watchdog.o writable.o \
- write_buf.o write_wait.o $(STRCASE)
+ write_buf.o write_wait.o auto_clnt.o attr_clnt.o attr_scan_plain.o \
+ attr_print_plain.o $(STRCASE)
HDRS = argv.h attr.h base64_code.h binhash.h chroot_uid.h clean_env.h \
connect.h ctable.h dict.h dict_db.h dict_dbm.h dict_env.h \
dict_cidr.h dict_ht.h dict_ldap.h dict_mysql.h dict_ni.h dict_nis.h \
scan_dir.h set_eugid.h set_ugid.h sigdelay.h spawn_command.h \
split_at.h stat_as.h stringops.h sys_defs.h timed_connect.h \
timed_wait.h trigger.h username.h valid_hostname.h vbuf.h \
- vbuf_print.h vstream.h vstring.h vstring_vstream.h watchdog.h
+ vbuf_print.h vstream.h vstring.h vstring_vstream.h watchdog.h \
+ auto_clnt.h attr_clnt.h
TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \
stream_test.c dup2_pass_on_exec.c
WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
mv junk $@.o
+attr_scan_plain: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+attr_print_plain: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
depend: $(MAKES)
(sed '1,/^# do not edit/!d' Makefile.in; \
set -e; for i in [a-z][a-z0-9]*.c; do \
tests: valid_hostname_test mac_expand_test dict_test unescape_test \
hex_quote_test ctable_test inet_addr_list_test base64_code_test \
attr_scan64_test attr_scan0_test dict_pcre_test host_port_test \
- dict_cidr_test
+ dict_cidr_test attr_scan_plain_test
valid_hostname_test: valid_hostname valid_hostname.in valid_hostname.ref
./valid_hostname <valid_hostname.in 2>valid_hostname.tmp
diff host_port.ref host_port.tmp
rm -f host_port.tmp
+attr_scan_plain_test: attr_print_plain attr_scan_plain attr_scan_plain.ref
+ (./attr_print_plain 2>&3 | (sleep 1; ./attr_scan_plain)) >attr_scan_plain.tmp 2>&1 3>&1
+ diff attr_scan_plain.ref attr_scan_plain.tmp
+ rm -f attr_scan_plain.tmp
+
# do not edit below this line - it is generated by 'make depend'
alldig.o: alldig.c
alldig.o: sys_defs.h
argv.o: argv.c
argv.o: sys_defs.h
argv.o: mymalloc.h
+argv.o: msg.h
argv.o: argv.h
argv_split.o: argv_split.c
argv_split.o: sys_defs.h
argv_split.o: vstring.h
argv_split.o: vbuf.h
argv_split.o: argv.h
+attr_clnt.o: attr_clnt.c
+attr_clnt.o: sys_defs.h
+attr_clnt.o: msg.h
+attr_clnt.o: mymalloc.h
+attr_clnt.o: split_at.h
+attr_clnt.o: vstream.h
+attr_clnt.o: vbuf.h
+attr_clnt.o: connect.h
+attr_clnt.o: iostuff.h
+attr_clnt.o: attr.h
+attr_clnt.o: auto_clnt.h
+attr_clnt.o: attr_clnt.h
attr_print0.o: attr_print0.c
attr_print0.o: sys_defs.h
attr_print0.o: msg.h
attr_print64.o: base64_code.h
attr_print64.o: vstring.h
attr_print64.o: attr.h
+attr_print_plain.o: attr_print_plain.c
+attr_print_plain.o: sys_defs.h
+attr_print_plain.o: msg.h
+attr_print_plain.o: mymalloc.h
+attr_print_plain.o: vstream.h
+attr_print_plain.o: vbuf.h
+attr_print_plain.o: htable.h
+attr_print_plain.o: base64_code.h
+attr_print_plain.o: vstring.h
+attr_print_plain.o: attr.h
attr_scan0.o: attr_scan0.c
attr_scan0.o: sys_defs.h
attr_scan0.o: msg.h
attr_scan64.o: htable.h
attr_scan64.o: base64_code.h
attr_scan64.o: attr.h
+attr_scan_plain.o: attr_scan_plain.c
+attr_scan_plain.o: sys_defs.h
+attr_scan_plain.o: msg.h
+attr_scan_plain.o: mymalloc.h
+attr_scan_plain.o: vstream.h
+attr_scan_plain.o: vbuf.h
+attr_scan_plain.o: vstring.h
+attr_scan_plain.o: htable.h
+attr_scan_plain.o: attr.h
+auto_clnt.o: auto_clnt.c
+auto_clnt.o: sys_defs.h
+auto_clnt.o: msg.h
+auto_clnt.o: mymalloc.h
+auto_clnt.o: vstream.h
+auto_clnt.o: vbuf.h
+auto_clnt.o: events.h
+auto_clnt.o: iostuff.h
+auto_clnt.o: auto_clnt.h
base64_code.o: base64_code.c
base64_code.o: sys_defs.h
base64_code.o: msg.h
/* ARGV *argvp;
/* char *arg;
/*
+/* void argv_addn(argvp, arg, arg_len, ..., ARGV_END)
+/* ARGV *argvp;
+/* char *arg;
+/* int arg_len;
+/*
/* void argv_terminate(argvp);
/* ARGV *argvp;
/* DESCRIPTION
/* Terminate the argument list with a null pointer. The manifest
/* constant ARGV_END provides a convenient notation for this.
/*
+/* argv_addn() is like argv_add(), but each string is followed
+/* by a string length argument.
+/*
/* argv_free() releases storage for a string array, and conveniently
/* returns a null pointer.
/*
/* Application-specific. */
#include "mymalloc.h"
+#include "msg.h"
#include "argv.h"
/* argv_free - destroy string array */
return (argvp);
}
+/* argv_extend - extend array */
+
+static void argv_extend(ARGV *argvp)
+{
+ argvp->len *= 2;
+ argvp->argv = (char **)
+ myrealloc((char *) argvp->argv,
+ (argvp->len + 1) * sizeof(char *));
+}
+
/* argv_add - add string to vector */
void argv_add(ARGV *argvp,...)
/*
* Make sure that always argvp->argc < argvp->len.
*/
+#define ARGV_SPACE_LEFT(a) ((a)->len - (a)->argc - 1)
+
va_start(ap, argvp);
while ((arg = va_arg(ap, char *)) != 0) {
- if (argvp->argc + 1 >= argvp->len) {
- argvp->len *= 2;
- argvp->argv = (char **)
- myrealloc((char *) argvp->argv,
- (argvp->len + 1) * sizeof(char *));
- }
+ if (ARGV_SPACE_LEFT(argvp) <= 0)
+ argv_extend(argvp);
argvp->argv[argvp->argc++] = mystrdup(arg);
}
va_end(ap);
argvp->argv[argvp->argc] = 0;
}
+/* argv_addn - add string to vector */
+
+void argv_addn(ARGV *argvp,...)
+{
+ char *arg;
+ int len;
+ va_list ap;
+
+ /*
+ * Make sure that always argvp->argc < argvp->len.
+ */
+ va_start(ap, argvp);
+ while ((arg = va_arg(ap, char *)) != 0) {
+ if ((len = va_arg(ap, int)) < 0)
+ msg_panic("argv_addn: bad string length %d", len);
+ if (ARGV_SPACE_LEFT(argvp) <= 0)
+ argv_extend(argvp);
+ argvp->argv[argvp->argc++] = mystrndup(arg, len);
+ }
+ va_end(ap);
+ argvp->argv[argvp->argc] = 0;
+}
+
/* argv_terminate - terminate string array */
void argv_terminate(ARGV *argvp)
extern ARGV *argv_alloc(int);
extern void argv_add(ARGV *,...);
+extern void argv_addn(ARGV *,...);
extern void argv_terminate(ARGV *);
extern ARGV *argv_free(ARGV *);
#define ATTR_TYPE_NUM 1 /* Unsigned integer */
#define ATTR_TYPE_STR 2 /* Character string */
#define ATTR_TYPE_HASH 3 /* Hash table */
+#define ATTR_TYPE_NV 3 /* Name-value table */
#define ATTR_TYPE_LONG 4 /* Unsigned long */
+#define ATTR_HASH_LIMIT 1024 /* Size of hash table */
+
/*
* Flags that control processing. See attr_scan(3) for documentation.
*/
extern int attr_scan0(VSTREAM *, int,...);
extern int attr_vscan0(VSTREAM *, int, va_list);
+ /*
+ * attr_scan_plain.c.
+ */
+extern int attr_print_plain(VSTREAM *, int,...);
+extern int attr_vprint_plain(VSTREAM *, int, va_list);
+
+ /*
+ * attr_print_plain.c.
+ */
+extern int attr_scan_plain(VSTREAM *, int,...);
+extern int attr_vscan_plain(VSTREAM *, int, va_list);
+
+
/*
* Attribute names for testing the compatibility of the read and write
* routines.
--- /dev/null
+/*++
+/* NAME
+/* attr_clnt 3
+/* SUMMARY
+/* attribute query-reply client
+/* SYNOPSIS
+/* #include <attr_clnt.h>
+/*
+/* ATTR_CLNT *attr_clnt_create(server, timeout, max_idle, max_ttl)
+/* const char *server;
+/* int timeout;
+/* int max_idle;
+/* int max_ttl;
+/*
+/* int attr_clnt_request(client,
+/* send_flags, send_type, send_name, ..., ATTR_TYPE_END,
+/* recv_flags, recv_type, recv_name, ..., ATTR_TYPE_END)
+/* ATTR_CLNT *client;
+/* int send_flags;
+/* int send_type;
+/* const char *send_name;
+/* int recv_flags;
+/* int recv_type;
+/* const char *recv_name;
+/*
+/* void attr_clnt_free(client)
+/* ATTR_CLNT *client;
+/* DESCRIPTION
+/* This module implements a client for a simple attribute-based
+/* protocol as described in attr_scan0(3) and attr_scan64(3).
+/*
+/* attr_clnt_create() creates a client handle. The server
+/* argument specifies "transport:servername" where transport is
+/* currently limited to "inet" or "unix", and servername has the
+/* form "host:port", "private/servicename" or "public/servicename".
+/* The timeout parameter limits the time for sending or receiving
+/* a reply, and the ttl parameter controls how long an unused
+/* connection is kept open.
+/*
+/* attr_clnt_request() sends the specified request attributes and
+/* receives a reply. The reply argument specifies a name-value table.
+/* The other arguments are as described in attr_print0(3) and in
+/* attr_print64(3). The result is the number of attributes received
+/* or -1 in case of trouble.
+/*
+/* attr_clnt_free() destroys a client handle and closes its connection.
+/* DIAGNOSTICS
+/* Warnings: communication failure.
+/* SEE ALSO
+/* auto_clnt(3), client endpoint management
+/* attr_scan_plain(3), attribute protocol
+/* attr_print_plain(3), attribute protocol
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <split_at.h>
+#include <vstream.h>
+#include <connect.h>
+#include <htable.h>
+#include <attr.h>
+#include <auto_clnt.h>
+#include <attr_clnt.h>
+
+/* Application-specific. */
+
+struct ATTR_CLNT {
+ AUTO_CLNT *auto_clnt;
+ int (*connect) (const char *, int, int);
+ char *endpoint;
+ int timeout;
+ int (*print) (VSTREAM *, int, va_list);
+ int (*scan) (VSTREAM *, int, va_list);
+};
+
+/* attr_clnt_connect - connect to server */
+
+static VSTREAM *attr_clnt_connect(void *context)
+{
+ const char *myname = "attr_clnt_connect";
+ ATTR_CLNT *client = (ATTR_CLNT *) context;
+ VSTREAM *fp;
+ int fd;
+
+ fd = client->connect(client->endpoint, NON_BLOCKING, client->timeout);
+ if (fd < 0) {
+ msg_warn("connect to %s: %m", client->endpoint);
+ return (0);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: connected to %s", myname, client->endpoint);
+ fp = vstream_fdopen(fd, O_RDWR);
+ vstream_control(fp, VSTREAM_CTL_PATH, client->endpoint,
+ VSTREAM_CTL_TIMEOUT, client->timeout,
+ VSTREAM_CTL_END);
+ return (fp);
+ }
+}
+
+/* attr_clnt_free - destroy attribute client */
+
+void attr_clnt_free(ATTR_CLNT *client)
+{
+ myfree(client->endpoint);
+ auto_clnt_free(client->auto_clnt);
+ myfree((char *) client);
+}
+
+/* attr_clnt_create - create attribute client */
+
+ATTR_CLNT *attr_clnt_create(const char *service, int timeout,
+ int max_idle, int max_ttl)
+{
+ const char *myname = "attr_clnt_create";
+ char *transport = mystrdup(service);
+ char *endpoint;
+ ATTR_CLNT *client;
+
+ if ((endpoint = split_at(transport, ':')) == 0 || *endpoint == 0)
+ msg_fatal("missing attribute server endpoint: %s", service);
+ if (msg_verbose)
+ msg_info("%s: transport=%s endpoint=%s", myname, transport, endpoint);
+
+ client = (ATTR_CLNT *) mymalloc(sizeof(*client));
+ client->scan = attr_vscan_plain;
+ client->print = attr_vprint_plain;
+ client->endpoint = mystrdup(endpoint);
+ client->timeout = timeout;
+ if (strcmp(transport, "inet") == 0) {
+ client->connect = inet_connect;
+ } else if (strcmp(transport, "unix") == 0) {
+ client->connect = unix_connect;
+ } else {
+ msg_fatal("invalid attribute transport name: %s", service);
+ }
+ client->auto_clnt = auto_clnt_create(max_idle, max_ttl,
+ attr_clnt_connect, (void *) client);
+ myfree(transport);
+ return (client);
+}
+
+/* attr_clnt_request - send query, receive reply */
+
+int attr_clnt_request(ATTR_CLNT *client, int send_flags,...)
+{
+ const char *myname = "attr_clnt_request";
+ VSTREAM *stream;
+ int count = 0;
+ va_list ap;
+ int type;
+ int recv_flags;
+ int err;
+ int ret;
+
+ /*
+ * XXX If the stream is readable before we send anything, then assume the
+ * remote end disconnected.
+ *
+ * XXX For some reason we can't simply call the scan routine after the print
+ * routine, that messes up the argument list.
+ */
+#define SKIP_ARG(ap, type) { \
+ (void) va_arg(ap, char *); \
+ (void) va_arg(ap, type); \
+ }
+
+ for (;;) {
+ if ((stream = auto_clnt_access(client->auto_clnt)) != 0
+ && readable(vstream_fileno(stream)) == 0) {
+ errno = 0;
+ va_start(ap, send_flags);
+ err = (client->print(stream, send_flags, ap) != 0
+ || vstream_fflush(stream) != 0);
+ va_end(ap);
+ if (err == 0) {
+ va_start(ap, send_flags);
+ while ((type = va_arg(ap, int)) != ATTR_TYPE_END) {
+ switch (type) {
+ case ATTR_TYPE_STR:
+ SKIP_ARG(ap, char *);
+ break;
+ case ATTR_TYPE_NUM:
+ SKIP_ARG(ap, int);
+ break;
+ case ATTR_TYPE_LONG:
+ SKIP_ARG(ap, long);
+ break;
+ case ATTR_TYPE_HASH:
+ SKIP_ARG(ap, HTABLE *);
+ break;
+ default:
+ msg_panic("%s: unexpected attribute type %d",
+ myname, type);
+ }
+ }
+ recv_flags = va_arg(ap, int);
+ ret = client->scan(stream, recv_flags, ap);
+ va_end(ap);
+ if (ret > 0)
+ return (ret);
+ }
+ }
+ if (++count >= 2
+ || msg_verbose
+ || (errno != EPIPE && errno != ENOENT && errno != ECONNRESET))
+ msg_warn("problem talking to server %s: %m", client->endpoint);
+ if (count >= 3)
+ return (-1);
+ sleep(1); /* XXX make configurable */
+ auto_clnt_recover(client->auto_clnt);
+ }
+}
--- /dev/null
+#ifndef _ATTR_CLNT_H_INCLUDED_
+#define _ATTR_CLNT_H_INCLUDED_
+
+/*++
+/* NAME
+/* attr_clnt 3h
+/* SUMMARY
+/* attribute query-reply client
+/* SYNOPSIS
+/* #include <attr_clnt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <attr.h>
+
+ /*
+ * External interface.
+ */
+typedef struct ATTR_CLNT ATTR_CLNT;
+
+extern ATTR_CLNT *attr_clnt_create(const char *, int, int, int);
+extern int attr_clnt_request(ATTR_CLNT *, int,...);
+extern void attr_clnt_free(ATTR_CLNT *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
/* SYNOPSIS
/* #include <attr.h>
/*
-/* int attr_print0(fp, flags, type, name, ...)
+/* int attr_print0(fp, flags, type, name, ..., ATTR_TYPE_END)
/* VSTREAM fp;
/* int flags;
/* int type;
/* va_list ap;
/* DESCRIPTION
/* attr_print0() takes zero or more (name, value) simple attributes
-/* or (name, count, value) list attributes, and converts its input
-/* to a byte stream that can be recovered with attr_scan0(). The stream
-/* is not flushed.
+/* and converts its input to a byte stream that can be recovered with
+/* attr_scan0(). The stream is not flushed.
/*
/* attr_vprint0() provides an alternate interface that is convenient
/* for calling from within variadoc functions.
/* This argument is followed by an attribute name and a null-terminated
/* string.
/* .IP "ATTR_TYPE_HASH (HTABLE *)"
-/* The content of the hash table is sent as a sequence of string-valued
-/* attributes with names equal to the hash table lookup key.
+/* .IP "ATTR_TYPE_NAMEVAL (NVTABLE *)"
+/* The content of the table is sent as a sequence of string-valued
+/* attributes with names equal to the table lookup keys.
/* .IP ATTR_TYPE_END
/* This terminates the attribute list.
/* .RE
/* SYNOPSIS
/* #include <attr.h>
/*
-/* int attr_print64(fp, flags, type, name, ...)
+/* int attr_print64(fp, flags, type, name, ..., ATTR_TYPE_END)
/* VSTREAM fp;
/* int flags;
/* int type;
/* va_list ap;
/* DESCRIPTION
/* attr_print64() takes zero or more (name, value) simple attributes
-/* or (name, count, value) list attributes, and converts its input
-/* to a byte stream that can be recovered with attr_scan64(). The stream
-/* is not flushed.
+/* and converts its input to a byte stream that can be recovered with
+/* attr_scan64(). The stream is not flushed.
/*
/* attr_vprint64() provides an alternate interface that is convenient
/* for calling from within variadoc functions.
/* This argument is followed by an attribute name and a null-terminated
/* string.
/* .IP "ATTR_TYPE_HASH (HTABLE *)"
-/* The content of the hash table is sent as a sequence of string-valued
-/* attributes with names equal to the hash table lookup key.
+/* .IP "ATTR_TYPE_NAMEVAL (NVTABLE *)"
+/* The content of the table is sent as a sequence of string-valued
+/* attributes with names equal to the table lookup keys.
/* .IP ATTR_TYPE_END
/* This terminates the attribute list.
/* .RE
int_val = va_arg(ap, int);
VSTREAM_PUTC(':', fp);
attr_print64_num(fp, (unsigned) int_val);
+ VSTREAM_PUTC('\n', fp);
if (msg_verbose)
msg_info("send attr %s = %u", attr_name, int_val);
break;
long_val = va_arg(ap, long);
VSTREAM_PUTC(':', fp);
attr_print64_long_num(fp, (unsigned long) long_val);
+ VSTREAM_PUTC('\n', fp);
if (msg_verbose)
msg_info("send attr %s = %lu", attr_name, long_val);
break;
str_val = va_arg(ap, char *);
VSTREAM_PUTC(':', fp);
attr_print64_str(fp, str_val, strlen(str_val));
+ VSTREAM_PUTC('\n', fp);
if (msg_verbose)
msg_info("send attr %s = %s", attr_name, str_val);
break;
attr_print64_str(fp, ht[0]->key, strlen(ht[0]->key));
VSTREAM_PUTC(':', fp);
attr_print64_str(fp, ht[0]->value, strlen(ht[0]->value));
+ VSTREAM_PUTC('\n', fp);
if (msg_verbose)
msg_info("send attr name %s value %s",
ht[0]->key, ht[0]->value);
- if (ht[1] != 0)
- VSTREAM_PUTC('\n', fp);
}
myfree((char *) ht_info_list);
break;
default:
msg_panic("%s: unknown type code: %d", myname, attr_type);
}
- VSTREAM_PUTC('\n', fp);
}
if ((flags & ATTR_FLAG_MORE) == 0)
VSTREAM_PUTC('\n', fp);
--- /dev/null
+/*++
+/* NAME
+/* attr_print_plain 3
+/* SUMMARY
+/* send attributes over byte stream
+/* SYNOPSIS
+/* #include <attr.h>
+/*
+/* int attr_print_plain(fp, flags, type, name, ..., ATTR_TYPE_END)
+/* VSTREAM fp;
+/* int flags;
+/* int type;
+/* char *name;
+/*
+/* int attr_vprint_plain(fp, flags, ap)
+/* VSTREAM fp;
+/* int flags;
+/* va_list ap;
+/* DESCRIPTION
+/* attr_print_plain() takes zero or more (name, value) simple attributes
+/* and converts its input to a byte stream that can be recovered with
+/* attr_scan_plain(). The stream is not flushed.
+/*
+/* attr_vprint_plain() provides an alternate interface that is convenient
+/* for calling from within variadoc functions.
+/*
+/* Attributes are sent in the requested order as specified with the
+/* attr_print_plain() argument list. This routine satisfies the formatting
+/* rules as outlined in attr_scan_plain(3).
+/*
+/* Arguments:
+/* .IP fp
+/* Stream to write the result to.
+/* .IP flags
+/* The bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP ATTR_FLAG_MORE
+/* After sending the requested attributes, leave the output stream in
+/* a state that is usable for more attribute sending operations on
+/* the same output attribute list.
+/* By default, attr_print_plain() automatically appends an attribute list
+/* terminator when it has sent the last requested attribute.
+/* .RE
+/* .IP type
+/* The type determines the arguments that follow.
+/* .RS
+/* .IP "ATTR_TYPE_NUM (char *, int)"
+/* This argument is followed by an attribute name and an integer.
+/* .IP "ATTR_TYPE_NUM (char *, long)"
+/* This argument is followed by an attribute name and a long integer.
+/* .IP "ATTR_TYPE_STR (char *, char *)"
+/* This argument is followed by an attribute name and a null-terminated
+/* string.
+/* .IP "ATTR_TYPE_HASH (HTABLE *)"
+/* .IP "ATTR_TYPE_NAMEVAL (NVTABLE *)"
+/* The content of the table is sent as a sequence of string-valued
+/* attributes with names equal to the table lookup keys.
+/* .IP ATTR_TYPE_END
+/* This terminates the attribute list.
+/* .RE
+/* DIAGNOSTICS
+/* The result value is 0 in case of success, VSTREAM_EOF in case
+/* of trouble.
+/*
+/* Panic: interface violation. All system call errors are fatal.
+/* SEE ALSO
+/* attr_scan_plain(3) recover attributes from byte stream
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <htable.h>
+#include <base64_code.h>
+#include <attr.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* attr_vprint_plain - send attribute list to stream */
+
+int attr_vprint_plain(VSTREAM *fp, int flags, va_list ap)
+{
+ const char *myname = "attr_print_plain";
+ int attr_type;
+ char *attr_name;
+ unsigned int_val;
+ unsigned long long_val;
+ char *str_val;
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+
+ /*
+ * Sanity check.
+ */
+ if (flags & ~ATTR_FLAG_ALL)
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+
+ /*
+ * Iterate over all (type, name, value) triples, and produce output on
+ * the fly.
+ */
+ while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) {
+ switch (attr_type) {
+ case ATTR_TYPE_NUM:
+ attr_name = va_arg(ap, char *);
+ int_val = va_arg(ap, int);
+ vstream_fprintf(fp, "%s=%u\n", attr_name, (unsigned) int_val);
+ if (msg_verbose)
+ msg_info("send attr %s = %u", attr_name, (unsigned) int_val);
+ break;
+ case ATTR_TYPE_LONG:
+ attr_name = va_arg(ap, char *);
+ long_val = va_arg(ap, long);
+ vstream_fprintf(fp, "%s=%lu\n", attr_name, long_val);
+ if (msg_verbose)
+ msg_info("send attr %s = %lu", attr_name, long_val);
+ break;
+ case ATTR_TYPE_STR:
+ attr_name = va_arg(ap, char *);
+ str_val = va_arg(ap, char *);
+ vstream_fprintf(fp, "%s=%s\n", attr_name, str_val);
+ if (msg_verbose)
+ msg_info("send attr %s = %s", attr_name, str_val);
+ break;
+ case ATTR_TYPE_HASH:
+ ht_info_list = htable_list(va_arg(ap, HTABLE *));
+ for (ht = ht_info_list; *ht; ht++) {
+ vstream_fprintf(fp, "%s=%s\n", ht[0]->key, ht[0]->value);
+ if (msg_verbose)
+ msg_info("send attr name %s value %s",
+ ht[0]->key, ht[0]->value);
+ }
+ myfree((char *) ht_info_list);
+ break;
+ default:
+ msg_panic("%s: unknown type code: %d", myname, attr_type);
+ }
+ }
+ if ((flags & ATTR_FLAG_MORE) == 0)
+ VSTREAM_PUTC('\n', fp);
+ return (vstream_ferror(fp));
+}
+
+int attr_print_plain(VSTREAM *fp, int flags,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, flags);
+ ret = attr_vprint_plain(fp, flags, ap);
+ va_end(ap);
+ return (ret);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof of concept test program. Mirror image of the attr_scan_plain test
+ * program.
+ */
+#include <msg_vstream.h>
+
+int main(int unused_argc, char **argv)
+{
+ HTABLE *table = htable_create(1);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_verbose = 1;
+ htable_enter(table, "foo-name", mystrdup("foo-value"));
+ htable_enter(table, "bar-name", mystrdup("bar-value"));
+ attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE,
+ ATTR_TYPE_NUM, ATTR_NAME_NUM, 4711,
+ ATTR_TYPE_LONG, ATTR_NAME_LONG, 1234,
+ ATTR_TYPE_STR, ATTR_NAME_STR, "whoopee",
+ ATTR_TYPE_HASH, table,
+ ATTR_TYPE_END);
+ attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE,
+ ATTR_TYPE_NUM, ATTR_NAME_NUM, 4711,
+ ATTR_TYPE_LONG, ATTR_NAME_LONG, 1234,
+ ATTR_TYPE_STR, ATTR_NAME_STR, "whoopee",
+ ATTR_TYPE_END);
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("write error: %m");
+
+ htable_free(table, myfree);
+ return (0);
+}
+
+#endif
/* SYNOPSIS
/* #include <attr.h>
/*
-/* int attr_scan0(fp, flags, type, name, ...)
+/* int attr_scan0(fp, flags, type, name, ..., ATTR_TYPE_END)
/* VSTREAM fp;
/* int flags;
/* int type;
/* .in
/*
/* All attribute names and attribute values are sent as null terminated
-/* strings. Each string must be no longer than 2*var_line_limit
-/* characters. The formatting rules favor implementations in C.
+/* strings. Each string must be no longer than 4*var_line_limit
+/* characters including the terminator.
+/* These formatting rules favor implementations in C.
/*
/* Normally, attributes must be received in the sequence as specified with
/* the attr_scan0() argument list. The input stream may contain additional
/* .IP "ATTR_TYPE_STR (char *, VSTRING *)"
/* This argument is followed by an attribute name and a VSTRING pointer.
/* .IP "ATTR_TYPE_HASH (HTABLE *)"
+/* .IP "ATTR_TYPE_NAMEVAL (NVTABLE *)"
/* All further input attributes are processed as string attributes.
/* No specific attribute sequence is enforced.
/* All attributes up to the attribute list terminator are read,
/* but only the first instance of each attribute is stored.
+/* There can be no more than 1024 attributes in a hash table.
/* .sp
/* The attribute string values are stored in the hash table under
/* keys equal to the attribute name (obtained from the input stream).
/* This argument terminates the requested attribute list.
/* .RE
/* BUGS
-/* ATTR_TYPE_HASH accepts attributes with arbitrary names from possibly
-/* untrusted sources. This is unsafe, unless the resulting table is
-/* queried only with known to be good attribute names.
+/* ATTR_TYPE_HASH (ATTR_TYPE_NAMEVAL) accepts attributes with arbitrary
+/* names from possibly untrusted sources.
+/* This is unsafe, unless the resulting table is queried only with
+/* known to be good attribute names.
/* DIAGNOSTICS
/* attr_scan0() and attr_vscan0() return -1 when malformed input is
/* detected (string too long, incomplete line, missing end marker).
static int attr_scan0_string(VSTREAM *fp, VSTRING *plain_buf, const char *context)
{
extern int var_line_limit; /* XXX */
- int limit = var_line_limit * 2;
+ int limit = var_line_limit * 4;
int ch;
- if ((ch = vstring_get_null(plain_buf, fp)) == VSTREAM_EOF) {
+ if ((ch = vstring_get_null_bound(plain_buf, fp, limit)) == VSTREAM_EOF) {
msg_warn("%s on %s while reading %s",
vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
VSTREAM_PATH(fp), context);
STR(name_buf), VSTREAM_PATH(fp));
return (conversions);
}
+ } else if (hash_table->used >= ATTR_HASH_LIMIT) {
+ msg_warn("attribute count exceeds limit %d in input from %s",
+ ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
+ return (conversions);
} else {
htable_enter(hash_table, STR(name_buf),
mystrdup(STR(str_buf)));
/* SYNOPSIS
/* #include <attr.h>
/*
-/* int attr_scan64(fp, flags, type, name, ...)
+/* int attr_scan64(fp, flags, type, name, ..., ATTR_TYPE_END)
/* VSTREAM fp;
/* int flags;
/* int type;
/* .in
/*
/* All attribute names and attribute values are sent as base64-encoded
-/* strings. Each base64 encoding must be no longer than 2*var_line_limit
+/* strings. Each base64 encoding must be no longer than 4*var_line_limit
/* characters. The formatting rules aim to make implementations in PERL
/* and other languages easy.
/*
/* .IP "ATTR_TYPE_STR (char *, VSTRING *)"
/* This argument is followed by an attribute name and a VSTRING pointer.
/* .IP "ATTR_TYPE_HASH (HTABLE *)"
+/* .IP "ATTR_TYPE_NAMEVAL (NVTABLE *)"
/* All further input attributes are processed as string attributes.
/* No specific attribute sequence is enforced.
/* All attributes up to the attribute list terminator are read,
/* but only the first instance of each attribute is stored.
+/* There can be no more than 1024 attributes in a hash table.
/* .sp
/* The attribute string values are stored in the hash table under
/* keys equal to the attribute name (obtained from the input stream).
/* This argument terminates the requested attribute list.
/* .RE
/* BUGS
-/* ATTR_TYPE_HASH accepts attributes with arbitrary names from possibly
-/* untrusted sources. This is unsafe, unless the resulting table is
-/* queried only with known to be good attribute names.
+/* ATTR_TYPE_HASH (ATTR_TYPE_NAMEVAL) accepts attributes with arbitrary
+/* names from possibly untrusted sources.
+/* This is unsafe, unless the resulting table is queried only with
+/* known to be good attribute names.
/* DIAGNOSTICS
/* attr_scan64() and attr_vscan64() return -1 when malformed input is
/* detected (string too long, incomplete line, missing end marker).
{
static VSTRING *base64_buf = 0;
extern int var_line_limit; /* XXX */
- int limit = var_line_limit * 2;
+ int limit = var_line_limit * 4;
int ch;
if (base64_buf == 0)
return (-1);
}
VSTRING_ADDCH(base64_buf, ch);
-#if 0
if (LEN(base64_buf) > limit) {
msg_warn("string length > %d characters from %s while reading %s",
limit, VSTREAM_PATH(fp), context);
return (-1);
}
-#endif
}
VSTRING_TERMINATE(base64_buf);
if (base64_decode(plain_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
/*
* Skip over this attribute. The caller does not ask for it.
*/
- while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF && ch != '\n')
+ while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF)
/* void */ ;
}
STR(name_buf), VSTREAM_PATH(fp));
return (conversions);
}
+ } else if (hash_table->used >= ATTR_HASH_LIMIT) {
+ msg_warn("attribute count exceeds limit %d in input from %s",
+ ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
+ return (conversions);
} else {
htable_enter(hash_table, STR(name_buf),
mystrdup(STR(str_buf)));
--- /dev/null
+/*++
+/* NAME
+/* attr_scan_plain 3
+/* SUMMARY
+/* recover attributes from byte stream
+/* SYNOPSIS
+/* #include <attr.h>
+/*
+/* int attr_scan_plain(fp, flags, type, name, ..., ATTR_TYPE_END)
+/* VSTREAM fp;
+/* int flags;
+/* int type;
+/* char *name;
+/*
+/* int attr_vscan_plain(fp, flags, ap)
+/* VSTREAM fp;
+/* int flags;
+/* va_list ap;
+/* DESCRIPTION
+/* attr_scan_plain() takes zero or more (name, value) request attributes
+/* and recovers the attribute values from the byte stream that was
+/* possibly generated by attr_print_plain().
+/*
+/* attr_vscan_plain() provides an alternative interface that is convenient
+/* for calling from within a variadic function.
+/*
+/* The input stream is formatted as follows, where (item)* stands
+/* for zero or more instances of the specified item, and where
+/* (item1 | item2) stands for choice:
+/*
+/* .in +5
+/* attr-list :== simple-attr* newline
+/* .br
+/* simple-attr :== attr-name "=" attr-value newline
+/* .br
+/* attr-name :== any string without null or "=" or newline.
+/* .br
+/* attr-value :== any string without null or newline.
+/* .br
+/* newline :== the ASCII newline character
+/* .in
+/*
+/* All attribute names and attribute values are sent as plain
+/* strings. Each string must be no longer than 4*var_line_limit
+/* characters. The formatting rules aim to make implementations in PERL
+/* and other languages easy.
+/*
+/* Normally, attributes must be received in the sequence as specified
+/* with the attr_scan_plain() argument list. The input stream may
+/* contain additional attributes at any point in the input stream,
+/* including additional instances of requested attributes.
+/*
+/* Additional input attributes or input attribute instances are silently
+/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified
+/* (see below). This allows for some flexibility in the evolution of
+/* protocols while still providing the option of being strict where
+/* this is desirable.
+/*
+/* Arguments:
+/* .IP fp
+/* Stream to recover the input attributes from.
+/* .IP flags
+/* The bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP ATTR_FLAG_MISSING
+/* Log a warning when the input attribute list terminates before all
+/* requested attributes are recovered. It is always an error when the
+/* input stream ends without the newline attribute list terminator.
+/* .IP ATTR_FLAG_EXTRA
+/* Log a warning and stop attribute recovery when the input stream
+/* contains an attribute that was not requested. This includes the
+/* case of additional instances of a requested attribute.
+/* .IP ATTR_FLAG_MORE
+/* After recovering the requested attributes, leave the input stream
+/* in a state that is usable for more attr_scan_plain() operations
+/* from the same input attribute list.
+/* By default, attr_scan_plain() skips forward past the input attribute
+/* list terminator.
+/* .IP ATTR_FLAG_STRICT
+/* For convenience, this value combines both ATTR_FLAG_MISSING and
+/* ATTR_FLAG_EXTRA.
+/* .IP ATTR_FLAG_NONE
+/* For convenience, this value requests none of the above.
+/* .RE
+/* .IP type
+/* The type argument determines the arguments that follow.
+/* .RS
+/* .IP "ATTR_TYPE_NUM (char *, int *)"
+/* This argument is followed by an attribute name and an integer pointer.
+/* .IP "ATTR_TYPE_LONG (char *, long *)"
+/* This argument is followed by an attribute name and a long pointer.
+/* .IP "ATTR_TYPE_STR (char *, VSTRING *)"
+/* This argument is followed by an attribute name and a VSTRING pointer.
+/* .IP "ATTR_TYPE_HASH (HTABLE *)"
+/* .IP "ATTR_TYPE_NAMEVAL (NVTABLE *)"
+/* All further input attributes are processed as string attributes.
+/* No specific attribute sequence is enforced.
+/* All attributes up to the attribute list terminator are read,
+/* but only the first instance of each attribute is stored.
+/* There can be no more than 1024 attributes in a hash table.
+/* .sp
+/* The attribute string values are stored in the hash table under
+/* keys equal to the attribute name (obtained from the input stream).
+/* Values from the input stream are added to the hash table. Existing
+/* hash table entries are not replaced.
+/* .sp
+/* N.B. This construct must be followed by an ATTR_TYPE_END argument.
+/* .IP ATTR_TYPE_END
+/* This argument terminates the requested attribute list.
+/* .RE
+/* BUGS
+/* ATTR_TYPE_HASH (ATTR_TYPE_NAMEVAL) accepts attributes with arbitrary
+/* names from possibly untrusted sources.
+/* This is unsafe, unless the resulting table is queried only with
+/* known to be good attribute names.
+/* DIAGNOSTICS
+/* attr_scan_plain() and attr_vscan_plain() return -1 when malformed input
+/* is detected (string too long, incomplete line, missing end marker).
+/* Otherwise, the result value is the number of attributes that were
+/* successfully recovered from the input stream (a hash table counts
+/* as the number of entries stored into the table).
+/*
+/* Panic: interface violation. All system call errors are fatal.
+/* SEE ALSO
+/* attr_print_plain(3) send attributes over byte stream.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <htable.h>
+#include <attr.h>
+
+/* Application specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* attr_scan_plain_string - pull a string from the input stream */
+
+static int attr_scan_plain_string(VSTREAM *fp, VSTRING *plain_buf,
+ int terminator, const char *context)
+{
+ extern int var_line_limit; /* XXX */
+ int limit = var_line_limit * 4;
+ int ch;
+
+ VSTRING_RESET(plain_buf);
+ while ((ch = VSTREAM_GETC(fp)) != '\n'
+ && (terminator == 0 || ch != terminator)) {
+ if (ch == VSTREAM_EOF) {
+ msg_warn("%s on %s while reading %s",
+ vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
+ VSTREAM_PATH(fp), context);
+ return (-1);
+ }
+ VSTRING_ADDCH(plain_buf, ch);
+ if (LEN(plain_buf) > limit) {
+ msg_warn("string length > %d characters from %s while reading %s",
+ limit, VSTREAM_PATH(fp), context);
+ return (-1);
+ }
+ }
+ VSTRING_TERMINATE(plain_buf);
+
+ if (msg_verbose)
+ msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)");
+ return (ch);
+}
+
+/* attr_scan_plain_number - pull a number from the input stream */
+
+static int attr_scan_plain_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf,
+ int terminator, const char *context)
+{
+ char junk = 0;
+ int ch;
+
+ if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0)
+ return (-1);
+ if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) {
+ msg_warn("malformed numerical data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(str_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_scan_plain_long_number - pull a number from the input stream */
+
+static int attr_scan_plain_long_number(VSTREAM *fp, unsigned long *ptr,
+ VSTRING *str_buf,
+ int terminator,
+ const char *context)
+{
+ char junk = 0;
+ int ch;
+
+ if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0)
+ return (-1);
+ if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) {
+ msg_warn("malformed numerical data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(str_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_vscan_plain - receive attribute list from stream */
+
+int attr_vscan_plain(VSTREAM *fp, int flags, va_list ap)
+{
+ const char *myname = "attr_scan_plain";
+ static VSTRING *str_buf = 0;
+ static VSTRING *name_buf = 0;
+ int wanted_type = -1;
+ char *wanted_name;
+ unsigned int *number;
+ unsigned long *long_number;
+ VSTRING *string;
+ HTABLE *hash_table;
+ int ch;
+ int conversions;
+
+ /*
+ * Sanity check.
+ */
+ if (flags & ~ATTR_FLAG_ALL)
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+
+ /*
+ * Initialize.
+ */
+ if (str_buf == 0) {
+ str_buf = vstring_alloc(10);
+ name_buf = vstring_alloc(10);
+ }
+
+ /*
+ * Iterate over all (type, name, value) triples.
+ */
+ for (conversions = 0; /* void */ ; conversions++) {
+
+ /*
+ * Determine the next attribute type and attribute name on the
+ * caller's wish list.
+ *
+ * If we're reading into a hash table, we already know that the
+ * attribute value is string-valued, and we get the attribute name
+ * from the input stream instead. This is secure only when the
+ * resulting table is queried with known to be good attribute names.
+ */
+ if (wanted_type != ATTR_TYPE_HASH) {
+ wanted_type = va_arg(ap, int);
+ if (wanted_type == ATTR_TYPE_END) {
+ if ((flags & ATTR_FLAG_MORE) != 0)
+ return (conversions);
+ wanted_name = "(list terminator)";
+ } else if (wanted_type == ATTR_TYPE_HASH) {
+ wanted_name = "(any attribute name or list terminator)";
+ hash_table = va_arg(ap, HTABLE *);
+ if (va_arg(ap, int) !=ATTR_TYPE_END)
+ msg_panic("%s: ATTR_TYPE_HASH not followed by ATTR_TYPE_END",
+ myname);
+ } else {
+ wanted_name = va_arg(ap, char *);
+ }
+ }
+
+ /*
+ * Locate the next attribute of interest in the input stream.
+ */
+ for (;;) {
+
+ /*
+ * Get the name of the next attribute. Hitting EOF is always bad.
+ * Hitting the end-of-input early is OK if the caller is prepared
+ * to deal with missing inputs.
+ */
+ if (msg_verbose)
+ msg_info("%s: wanted attribute: %s",
+ VSTREAM_PATH(fp), wanted_name);
+ if ((ch = attr_scan_plain_string(fp, name_buf, '=',
+ "input attribute name")) == VSTREAM_EOF)
+ return (-1);
+ if (ch == '\n' && LEN(name_buf) == 0) {
+ if (wanted_type == ATTR_TYPE_END
+ || wanted_type == ATTR_TYPE_HASH)
+ return (conversions);
+ if ((flags & ATTR_FLAG_MISSING) != 0)
+ msg_warn("missing attribute %s in input from %s",
+ wanted_name, VSTREAM_PATH(fp));
+ return (conversions);
+ }
+
+ /*
+ * See if the caller asks for this attribute.
+ */
+ if (wanted_type == ATTR_TYPE_HASH
+ || (wanted_type != ATTR_TYPE_END
+ && strcmp(wanted_name, STR(name_buf)) == 0))
+ break;
+ if ((flags & ATTR_FLAG_EXTRA) != 0) {
+ msg_warn("unexpected attribute %s in input from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (conversions);
+ }
+
+ /*
+ * Skip over this attribute. The caller does not ask for it.
+ */
+ while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF)
+ /* void */ ;
+ }
+
+ /*
+ * Do the requested conversion. If the target attribute is a
+ * non-array type, disallow sending a multi-valued attribute, and
+ * disallow sending no value. If the target attribute is an array
+ * type, allow the sender to send a zero-element array (i.e. no value
+ * at all). XXX Need to impose a bound on the number of array
+ * elements.
+ */
+ switch (wanted_type) {
+ case ATTR_TYPE_NUM:
+ if (ch != '=') {
+ msg_warn("missing value for number attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ number = va_arg(ap, unsigned int *);
+ if ((ch = attr_scan_plain_number(fp, number, str_buf, 0,
+ "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_LONG:
+ if (ch != '=') {
+ msg_warn("missing value for number attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ long_number = va_arg(ap, unsigned long *);
+ if ((ch = attr_scan_plain_long_number(fp, long_number, str_buf,
+ 0, "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_STR:
+ if (ch != '=') {
+ msg_warn("missing value for string attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ string = va_arg(ap, VSTRING *);
+ if ((ch = attr_scan_plain_string(fp, string, 0,
+ "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_HASH:
+ if (ch != '=') {
+ msg_warn("missing value for string attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ if ((ch = attr_scan_plain_string(fp, str_buf, 0,
+ "input attribute value")) < 0)
+ return (-1);
+ if (htable_locate(hash_table, STR(name_buf)) != 0) {
+ if ((flags & ATTR_FLAG_EXTRA) != 0) {
+ msg_warn("duplicate attribute %s in input from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (conversions);
+ }
+ } else if (hash_table->used >= ATTR_HASH_LIMIT) {
+ msg_warn("attribute count exceeds limit %d in input from %s",
+ ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
+ return (conversions);
+ } else {
+ htable_enter(hash_table, STR(name_buf),
+ mystrdup(STR(str_buf)));
+ }
+ break;
+ default:
+ msg_panic("%s: unknown type code: %d", myname, wanted_type);
+ }
+ }
+}
+
+/* attr_scan_plain - read attribute list from stream */
+
+int attr_scan_plain(VSTREAM *fp, int flags,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, flags);
+ ret = attr_vscan_plain(fp, flags, ap);
+ va_end(ap);
+ return (ret);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof of concept test program. Mirror image of the attr_scan_plain test
+ * program.
+ */
+#include <msg_vstream.h>
+
+int var_line_limit = 2048;
+
+int main(int unused_argc, char **used_argv)
+{
+ VSTRING *str_val = vstring_alloc(1);
+ HTABLE *table = htable_create(1);
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ int int_val;
+ long long_val;
+ int ret;
+
+ msg_verbose = 1;
+ msg_vstream_init(used_argv[0], VSTREAM_ERR);
+ if ((ret = attr_scan_plain(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ ATTR_TYPE_NUM, ATTR_NAME_NUM, &int_val,
+ ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val,
+ ATTR_TYPE_STR, ATTR_NAME_STR, str_val,
+ ATTR_TYPE_HASH, table,
+ ATTR_TYPE_END)) > 3) {
+ vstream_printf("%s %d\n", ATTR_NAME_NUM, int_val);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
+ vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
+ ht_info_list = htable_list(table);
+ for (ht = ht_info_list; *ht; ht++)
+ vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value);
+ myfree((char *) ht_info_list);
+ } else {
+ vstream_printf("return: %d\n", ret);
+ }
+ if ((ret = attr_scan_plain(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ ATTR_TYPE_NUM, ATTR_NAME_NUM, &int_val,
+ ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val,
+ ATTR_TYPE_STR, ATTR_NAME_STR, str_val,
+ ATTR_TYPE_END)) == 3) {
+ vstream_printf("%s %d\n", ATTR_NAME_NUM, int_val);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
+ vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
+ ht_info_list = htable_list(table);
+ for (ht = ht_info_list; *ht; ht++)
+ vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value);
+ myfree((char *) ht_info_list);
+ } else {
+ vstream_printf("return: %d\n", ret);
+ }
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("write error: %m");
+
+ vstring_free(str_val);
+ htable_free(table, myfree);
+
+ return (0);
+}
+
+#endif
--- /dev/null
+./attr_print_plain: send attr number = 4711
+./attr_print_plain: send attr long_number = 1234
+./attr_print_plain: send attr string = whoopee
+./attr_print_plain: send attr name foo-name value foo-value
+./attr_print_plain: send attr name bar-name value bar-value
+./attr_print_plain: send attr number = 4711
+./attr_print_plain: send attr long_number = 1234
+./attr_print_plain: send attr string = whoopee
+./attr_scan_plain: unknown_stream: wanted attribute: number
+./attr_scan_plain: input attribute name: number
+./attr_scan_plain: input attribute value: 4711
+./attr_scan_plain: unknown_stream: wanted attribute: long_number
+./attr_scan_plain: input attribute name: long_number
+./attr_scan_plain: input attribute value: 1234
+./attr_scan_plain: unknown_stream: wanted attribute: string
+./attr_scan_plain: input attribute name: string
+./attr_scan_plain: input attribute value: whoopee
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan_plain: input attribute name: foo-name
+./attr_scan_plain: input attribute value: foo-value
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan_plain: input attribute name: bar-name
+./attr_scan_plain: input attribute value: bar-value
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan_plain: input attribute name: (end)
+./attr_scan_plain: unknown_stream: wanted attribute: number
+./attr_scan_plain: input attribute name: number
+./attr_scan_plain: input attribute value: 4711
+./attr_scan_plain: unknown_stream: wanted attribute: long_number
+./attr_scan_plain: input attribute name: long_number
+./attr_scan_plain: input attribute value: 1234
+./attr_scan_plain: unknown_stream: wanted attribute: string
+./attr_scan_plain: input attribute name: string
+./attr_scan_plain: input attribute value: whoopee
+./attr_scan_plain: unknown_stream: wanted attribute: (list terminator)
+./attr_scan_plain: input attribute name: (end)
+number 4711
+long_number 1234
+string whoopee
+(hash) foo-name foo-value
+(hash) bar-name bar-value
+number 4711
+long_number 1234
+string whoopee
+(hash) foo-name foo-value
+(hash) bar-name bar-value
--- /dev/null
+/*++
+/* NAME
+/* auto_clnt 3
+/* SUMMARY
+/* client endpoint maintenance
+/* SYNOPSIS
+/* #include <auto_clnt.h>
+/*
+/* AUTO_CLNT *auto_clnt_create(max_idle, max_ttl, open_action, context)
+/* int max_idle;
+/* int max_ttl;
+/* VSTREAM *(open_action)(void *context)
+/* void *context;
+/*
+/* VSTREAM *auto_clnt_access(auto_clnt)
+/* AUTO_CLNT *auto_clnt;
+/*
+/* void auto_clnt_recover(auto_clnt)
+/* AUTO_CLNT *auto_clnt;
+/*
+/* void auto_clnt_free(auto_clnt)
+/* AUTO_CLNT *auto_clnt;
+/* DESCRIPTION
+/* This module maintains local IPC client endpoints that automatically
+/* disconnect after a being idle for a configurable amount of time,
+/* that disconnect after a configurable time to live,
+/* and that transparently handle most server-initiated disconnects.
+/* Server disconnect is detected by read-selecting the client endpoint.
+/* The code assumes that the server has disconnected when the endpoint
+/* becomes readable.
+/*
+/* auto_clnt_create() instantiates a client endpoint.
+/*
+/* auto_clnt_access() returns an open stream to the service specified
+/* to auto_clnt_create(). The stream instance may change between calls.
+/* The result is a null pointer in case of failure.
+/*
+/* auto_clnt_recover() recovers from a server-initiated disconnect
+/* that happened in the middle of an I/O operation.
+/*
+/* auto_clnt_free() destroys of the specified client endpoint.
+/*
+/* Arguments:
+/* .IP max_idle
+/* Idle time after which the client disconnects.
+/* .IP max_ttl
+/* Upper bound on the time that a connection is allowed to persist.
+/* .IP open_action
+/* Application call-back routine that opens a stream or returns a
+/* null pointer upon failure. In case of success, the call-back routine
+/* is expected to set the stream pathname to the server endpoint name.
+/* .IP context
+/* Application context that is passed to the open_action routine.
+/* DIAGNOSTICS
+/* Warnings: communication failure. Fatal error: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <events.h>
+#include <iostuff.h>
+#include <auto_clnt.h>
+
+/* Application-specific. */
+
+ /*
+ * AUTO_CLNT is an opaque structure. None of the access methods can easily
+ * be implemented as a macro, and access is not performance critical anyway.
+ */
+struct AUTO_CLNT {
+ VSTREAM *vstream; /* buffered I/O */
+ int max_idle; /* time before client disconnect */
+ int max_ttl; /* time before client disconnect */
+ VSTREAM *(*open_action) (void *); /* callback */
+ void *context; /* callback context */
+};
+
+static void auto_clnt_close(AUTO_CLNT *);
+
+/* auto_clnt_event - server-initiated disconnect or client-side max_idle */
+
+static void auto_clnt_event(int unused_event, char *context)
+{
+ AUTO_CLNT *auto_clnt = (AUTO_CLNT *) context;
+
+ /*
+ * Sanity check. This routine causes the stream to be closed, so it
+ * cannot be called when the stream is already closed.
+ */
+ if (auto_clnt->vstream == 0)
+ msg_panic("auto_clnt_event: stream is closed");
+
+ auto_clnt_close(auto_clnt);
+}
+
+/* auto_clnt_ttl_event - client-side expiration */
+
+static void auto_clnt_ttl_event(int event, char *context)
+{
+
+ /*
+ * XXX This function is needed only because event_request_timer() cannot
+ * distinguish between requests that specify the same call-back routine
+ * and call-back context. The fix is obvious: specify a request ID along
+ * with the call-back routine, but there is too much code that would have
+ * to be changed.
+ *
+ * XXX Should we be concerned that an overly agressive optimizer will
+ * eliminate this function and replace calls to auto_clnt_ttl_event() by
+ * direct calls to auto_clnt_event()? It should not, because there exists
+ * code that takes the address of both functions.
+ */
+ auto_clnt_event(event, context);
+}
+
+/* auto_clnt_open - connect to service */
+
+static void auto_clnt_open(AUTO_CLNT *auto_clnt)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (auto_clnt->vstream)
+ msg_panic("auto_clnt_open: stream is open");
+
+ /*
+ * Schedule a read event so that we can clean up when the remote side
+ * disconnects, and schedule a timer event so that we can cleanup an idle
+ * connection. Note that both events are handled by the same routine.
+ *
+ * Finally, schedule an event to force disconnection even when the
+ * connection is not idle. This is to prevent one client from clinging on
+ * to a server forever.
+ */
+ auto_clnt->vstream =
+ auto_clnt->open_action(auto_clnt->context);
+
+ if (auto_clnt->vstream != 0) {
+ close_on_exec(vstream_fileno(auto_clnt->vstream), CLOSE_ON_EXEC);
+ event_enable_read(vstream_fileno(auto_clnt->vstream), auto_clnt_event,
+ (char *) auto_clnt);
+ event_request_timer(auto_clnt_event, (char *) auto_clnt,
+ auto_clnt->max_idle);
+ event_request_timer(auto_clnt_ttl_event, (char *) auto_clnt,
+ auto_clnt->max_ttl);
+ }
+}
+
+/* auto_clnt_close - disconnect from service */
+
+static void auto_clnt_close(AUTO_CLNT *auto_clnt)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (auto_clnt->vstream == 0)
+ msg_panic("auto_clnt_close: stream is closed");
+
+ /*
+ * Be sure to disable read and timer events.
+ */
+ if (msg_verbose)
+ msg_info("%s stream disconnect", VSTREAM_PATH(auto_clnt->vstream));
+ event_disable_readwrite(vstream_fileno(auto_clnt->vstream));
+ event_cancel_timer(auto_clnt_event, (char *) auto_clnt);
+ event_cancel_timer(auto_clnt_ttl_event, (char *) auto_clnt);
+ (void) vstream_fclose(auto_clnt->vstream);
+ auto_clnt->vstream = 0;
+}
+
+/* auto_clnt_recover - recover from server-initiated disconnect */
+
+void auto_clnt_recover(AUTO_CLNT *auto_clnt)
+{
+
+ /*
+ * Clean up. Don't re-connect until the caller needs it.
+ */
+ if (auto_clnt->vstream)
+ auto_clnt_close(auto_clnt);
+}
+
+/* auto_clnt_access - access a client stream */
+
+VSTREAM *auto_clnt_access(AUTO_CLNT *auto_clnt)
+{
+
+ /*
+ * Open a stream or restart the idle timer.
+ *
+ * Important! Do not restart the TTL timer!
+ */
+ if (auto_clnt->vstream == 0) {
+ auto_clnt_open(auto_clnt);
+ } else {
+ event_request_timer(auto_clnt_event, (char *) auto_clnt,
+ auto_clnt->max_idle);
+ }
+ return (auto_clnt->vstream);
+}
+
+/* auto_clnt_create - create client stream connection */
+
+AUTO_CLNT *auto_clnt_create(int max_idle, int max_ttl,
+ VSTREAM *(*open_action) (void *), void *context)
+{
+ AUTO_CLNT *auto_clnt;
+
+ /*
+ * Don't open the stream until the caller needs it.
+ */
+ auto_clnt = (AUTO_CLNT *) mymalloc(sizeof(*auto_clnt));
+ auto_clnt->vstream = 0;
+ auto_clnt->max_idle = max_idle;
+ auto_clnt->max_ttl = max_ttl;
+ auto_clnt->open_action = open_action;
+ auto_clnt->context = context;
+ return (auto_clnt);
+}
+
+/* auto_clnt_free - destroy client stream instance */
+
+void auto_clnt_free(AUTO_CLNT *auto_clnt)
+{
+ if (auto_clnt->vstream)
+ auto_clnt_close(auto_clnt);
+ myfree((char *) auto_clnt);
+}
--- /dev/null
+#ifndef _AUTO_CLNT_H_INCLUDED_
+#define _AUTO_CLNT_H_INCLUDED_
+
+/*++
+/* NAME
+/* auto_clnt 3h
+/* SUMMARY
+/* client endpoint maintenance
+/* SYNOPSIS
+/* #include <auto_clnt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+typedef struct AUTO_CLNT AUTO_CLNT;
+
+extern AUTO_CLNT *auto_clnt_create(int, int, VSTREAM *(*) (void *), void *);
+extern VSTREAM *auto_clnt_access(AUTO_CLNT *);
+extern void auto_clnt_recover(AUTO_CLNT *);
+extern void auto_clnt_free(AUTO_CLNT *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
/* char *str;
/* VSTREAM *stream;
/*
-/* long vstream_ftell(stream)
+/* off_t vstream_ftell(stream)
/* VSTREAM *stream;
/*
-/* long vstream_fseek(stream, offset, whence)
+/* off_t vstream_fseek(stream, offset, whence)
/* VSTREAM *stream;
-/* long offset;
+/* off_t offset;
/* int whence;
/*
/* int vstream_fflush(stream)
/* vstream_fseek - change I/O position */
-long vstream_fseek(VSTREAM *stream, long offset, int whence)
+off_t vstream_fseek(VSTREAM *stream, off_t offset, int whence)
{
char *myname = "vstream_fseek";
VBUF *bp = &stream->buf;
/* vstream_ftell - return file offset */
-long vstream_ftell(VSTREAM *stream)
+off_t vstream_ftell(VSTREAM *stream)
{
VBUF *bp = &stream->buf;
* the last read, write or seek operation.
*/
if ((bp->flags & VSTREAM_FLAG_SEEK) == 0) {
- if ((stream->offset = lseek(stream->fd, 0L, SEEK_CUR)) < 0) {
+ if ((stream->offset = lseek(stream->fd, (off_t) 0, SEEK_CUR)) < 0) {
bp->flags |= VSTREAM_FLAG_NSEEK;
return (-1);
}
VSTREAM_FN read_fn; /* buffer fill action */
VSTREAM_FN write_fn; /* buffer fill action */
void *context; /* application context */
- long offset; /* cached seek info */
+ off_t offset; /* cached seek info */
char *path; /* give it at least try */
int read_fd; /* read channel (double-buffered) */
int write_fd; /* write channel (double-buffered) */
extern VSTREAM *vstream_fopen(const char *, int, int);
extern int vstream_fclose(VSTREAM *);
-extern long vstream_fseek(VSTREAM *, long, int);
-extern long vstream_ftell(VSTREAM *);
+extern off_t vstream_fseek(VSTREAM *, off_t, int);
+extern off_t vstream_ftell(VSTREAM *);
extern int vstream_fflush(VSTREAM *);
extern int vstream_fputs(const char *, VSTREAM *);
extern VSTREAM *vstream_fdopen(int, int);
/* of at least "len" bytes. The minimal length is 1. The result
/* is a null-terminated string of length zero.
/*
-/* vstring_ctl() gives additional control over vstring behavior.
+/* vstring_ctl() gives additional control over VSTRING behavior.
/* The function takes a VSTRING pointer and a list of zero
/* or more (name,value) pairs. The expected value type
/* depends on the specified name. The value name codes are: