-TDICT
-TDICT_DB
-TDICT_DBM
+-TDICT_DEBUG
-TDICT_ENV
-TDICT_HT
-TDICT_LDAP
--TDICT_DEBUG
-TDICT_MYSQL
-TDICT_NI
-TDICT_NIS
-TQMGR_RECIPIENT
-TQMGR_SCAN
-TQMGR_TRANSPORT
+-TQMQPD_STATE
-TRECIPIENT
-TRECIPIENT_LIST
-TREC_TYPE_NAME
Feature: address quoting and case folding flags for the
pipe(8) mailer.
+
+20010611
+
+ Workaround: some MTAs fall on their face when they receive
+ unexpectedly long lines. From now on, Postfix defaults to
+ breaking long lines at 2048 (like Sendmail so it has got to
+ be right). To get the old, content preserving, behavior
+ specify "smtp_truncate_lines = no". File: smtp/smtp_proto.c.
+
+20010614
+
+ Bugfix: did not really undo 2821 552->452 mapping.
+
+20010628
+
+ Bugfix: postfix-script used a hard-coded maildrop group
+ owner instead of using the install-time specified name
+ stored in /etc/postfix/install.cf. Problem reported by
+ David Terrell @ meat.net.
+
+20010701
+
+ Feature: mail_spool_directory ending in / causes maildir
+ style delivery.
+
+ Bugfix: the FreeBSD kernel parameters kern.ipc.nmbclusters
+ and kern.ipc.maxsockets cannot be set with sysctl commands.
+ File: html/faq.html. Len Conrad @ Go2France.com.
+
+ Cleanup: the virtual delivery agent was poorly integrated
+ so that the SMTP server and queue manager did not reject
+ mail for unknown users. Files: smtpd/smtpd_check.c,
+ *qmgr/qmgr_message.c.
+
+20010705
+
+ Feature: QMQP server for compatibility with the ezmlm list
+ manager. Files: util/netstring.[hc], qmqpd/qmqpd*.c.
+
+20010706
+
+ Feature: QMQP stress test message generator program. Files:
+ smtpstone/qmqp-source.c, smtpstone/qmqp-sink.c.
+
+20010708
+
+ Bugfix: with disable_dns=yes, the SMTP client treated all
+ host lookup errors as permanent. File: smtp/smtp_addr.c.
src/lmtp src/trivial-rewrite src/qmgr src/smtp src/bounce src/pipe \
src/showq src/postalias src/postcat src/postconf src/postdrop \
src/postkick src/postlock src/postlog src/postmap src/postsuper \
- src/nqmgr src/spawn src/flush src/virtual # proto man html
+ src/nqmgr src/qmqpd src/spawn src/flush src/virtual # proto man html
default: update
--- /dev/null
+Postfix QMQP server support
+===========================
+
+Postfix has preliminary server support for the QMQP protocol, so
+that Postfix can be used as a backend for the Ezmlm-idx mailing
+list manager. This support includes qmqp-source and qmqp-sink
+programs for protocol stress testing.
+
+Turning on the QMQP service
+===========================
+
+To enable QMQP server support on an existing Postfix system you
+have to add the following line to /etc/postfix/master.cf:
+
+628 inet n - n - - qmqpd
+
+
+QMQP server access control
+==========================
+
+By default, the QMQP server does not accept mail from any client.
+This is because the QMQP server relays mail to any destination
+(the "protocol" has no provision to reject specific recipients).
+
+To authorize QMQP clients, edit /etc/postfix/main.cf and specify
+a list of client patterns.
+
+qmqp_authorized_clients = client, client, ...
+
+A list pattern specifies a host name, a domain name, an internet
+address, or a network/mask pattern, where the mask specifies the
+number of bits in the network part. When a pattern specifies a
+file name, its contents are substituted for the file name; when a
+pattern is a type:name table specification, table lookup is used
+instead.
+
+Patterns are separated by whitespace and/or commas. In order to
+reverse the result, precede a non-file name pattern with an
+exclamation point (!).
+Incompatible changes with snapshot-20010707
+===========================================
+
+The SMTP client by default breaks lines > 2048 characters, in order
+to avoid problems with mail delivery to fragile SMTP server software.
+To get the old behavior, specify "smtp_break_lines = no" in the
+Postfix main.cf file.
+
+Major changes with snapshot-20010707
+====================================
+
+QMQP server support, so that Postfix can be used as a backend mailer
+for the Ezmlm-idx mailing list manager. The service is disabled by
+default. To enable, follow instructions in the README_QMQP file.
+
+You can now reject unknown virtual(8) recipients at the SMTP port
+by specifying a "domain.name whatever" entry in the tables specified
+with virtual_mailbox_maps, similar to Postfix virtual(5) domains.
+[virtual(8) is the Postfix virtual delivery agent, virtual(5) is
+the Postfix virtual map. The two implement virtual domains in a
+very different manner.]
+
+Specify "mail_spool_directory = /var/mail/" (note the trailing "/"
+character) to enable maildir format for /var/mail/username.
+
Incompatible changes with snapshot-20010610
===========================================
# (yes) (yes) (yes) (never) (50)
# ==========================================================================
smtp inet n - n - - smtpd
+#628 inet n - n - - qmqpd
pickup fifo n n n 60 1 pickup
cleanup unix - - n - 0 cleanup
qmgr fifo n - n 300 1 qmgr
-*** postfix-script-nosgid Wed Mar 24 11:20:49 1999
---- postfix-script-sgid Wed Mar 24 11:20:53 1999
+*** postfix-script-nosgid Thu May 24 17:13:59 2001
+--- postfix-script-sgid Fri Jun 29 10:28:19 2001
***************
-*** 174,181 ****
+*** 177,184 ****
test -d maildrop || {
$WARN creating missing Postfix maildrop directory
mkdir maildrop || exit 1
}
test -d pid || {
$WARN creating missing Postfix pid directory
---- 174,182 ----
+--- 177,185 ----
test -d maildrop || {
$WARN creating missing Postfix maildrop directory
mkdir maildrop || exit 1
! chmod 1730 maildrop
chown $mail_owner maildrop
-+ chgrp maildrop maildrop
++ (. $config_directory/install.cf; chgrp $setgid maildrop)
}
test -d pid || {
$WARN creating missing Postfix pid directory
mkdir maildrop || exit 1
chmod 1730 maildrop
chown $mail_owner maildrop
- chgrp maildrop maildrop
+ (. $config_directory/install.cf; chgrp $setgid maildrop)
}
test -d pid || {
$WARN creating missing Postfix pid directory
--- /dev/null
+# DO NOT EDIT THIS FILE. EDIT THE MAIN.CF FILE INSTEAD. THE STUFF
+# HERE JUST SERVES AS AN EXAMPLE.
+#
+# This file contains example settings of Postfix configuration parameters
+# that control the QMQP server program.
+
+# The qmqpd_authorized_clients parameter specifies what clients are
+# allowed to connect to the QMQP server port.
+#
+# By default, no client is allowed to use the service. This is
+# because the QMQP server will relay mail to any destination.
+#
+# Specify a list of client patterns. A list pattern specifies a host
+# name, a domain name, an internet address, or a network/mask pattern,
+# where the mask specifies the number of bits in the network part.
+# When a pattern specifies a file name, its contents are substituted
+# for the file name; when a pattern is a type:name table specification,
+# table lookup is used instead.
+#
+# Patterns are separated by whitespace and/or commas. In order to
+# reverse the result, precede a non-file name pattern with an
+# exclamation point (!).
+#
+#qmqpd_authorized_clients =
+
+# The qmqpd_error_delay parameter specifies how long the QMQP server
+# will pause before sending a negative reply to the client. The
+# purpose is to slow down confused or malicious clients.
+#
+# By default, the QMQP server pauses for 5 seconds.
+#
+#qmqpd_error_delay = 5s
+
+# The qmqpd_timeout parameter specifies a time limit for network I/O
+# operations. If a read or write operation blocks for more than
+# $qmqpd_timeout seconds the QMQP server gives up and disconnects.
+#
+# By default, the QMQP server runs out of patience after 300 seconds.
+#
+#qmqpd_timeout = 300s
#
#smtp_bind_address=111.222.333.444
+# The smtp_break_lines parameter controls whether the SMTP client
+# will break lines longer than $line_length_limit characters.
+#
+# By default, line breaking is turned on, because some fragile SMTP
+# server implementations cannot receive mail with long lines.
+#
+#smtp_break_lines = yes
+
# The smtp_skip_4xx_greeting parameter controls what happens when
# an SMTP server greets us with a 4XX status code (go away, try
# again later).
DAEMONS = bounce.8.html cleanup.8.html defer.8.html error.8.html local.8.html \
lmtp.8.html master.8.html pickup.8.html pipe.8.html qmgr.8.html \
showq.8.html smtp.8.html smtpd.8.html trivial-rewrite.8.html \
- nqmgr.8.html spawn.8.html flush.8.html virtual.8.html
+ nqmgr.8.html spawn.8.html flush.8.html virtual.8.html qmqpd.8.html
COMMANDS= mailq.1.html newaliases.1.html postalias.1.html postcat.1.html \
postconf.1.html postfix.1.html postkick.1.html postlock.1.html \
postlog.1.html postdrop.1.html postmap.1.html sendmail.1.html \
qmgr.8.html: ../src/qmgr/qmgr.c
srctoman $? | $(AWK) | nroff -man | uniq | man2html | postlink >$@
+qmqpd.8.html: ../src/qmqpd/qmqpd.c
+ srctoman $? | $(AWK) | nroff -man | uniq | man2html | postlink >$@
+
showq.8.html: ../src/showq/showq.c
srctoman $? | $(AWK) | nroff -man | uniq | man2html | postlink >$@
<li><a href="#broken_transport">Mail delivery fails with: "unknown
mail transport error"</a>
+<li><a href="#msql_limit">Too many connections</a>
+
+<li><a href="#reiser_bugs">write queue file: No such file or directory</a>
+
+<li><a href="#reiser_bugs">write queue file: Unknown error 4294967289</a>
+
</ul>
<p>
<hr>
-<a name="nosuid"><h1>sendmail has set-uid root file permissions, or is run from a
+<a name="nosuid"><h3>sendmail has set-uid root file permissions, or is run from a
set-uid root process</h3></a>
Traditionally, the UNIX <b>sendmail</b> command is installed with
<p>
-To set kernel parameters at boot time, add the following lines to
-the <b>/boot/loader.conf</b> file (this is specific to FreeBSD 4.x):
+To set the following kernel parameters at boot time, add the
+following lines to the <b>/boot/loader.conf</b> file (this is
+specific to FreeBSD 4.x):
<p>
<blockquote>
<pre>
kern.ipc.maxsockets="5000"
-kern.maxfiles="16384"
-kern.maxfilesperproc="16384"
kern.ipc.nmbclusters="65536"
</pre>
</blockquote>
<p>
-To set kernel parameters at run time execute the following commands
-as root (this is specific to FreeBSD 4.x):
+These parameters cannot be set at run time (verified with FreeBSD
+4.2).
+
+<p>
+
+To set the following kernel parameters at run time execute the
+following commands as root (this is specific to FreeBSD 4.x):
<p>
<blockquote>
<pre>
-# sysctl -w kern.ipc.maxsockets=5000
# sysctl -w kern.maxfiles=16384
# sysctl -w kern.maxfilesperproc=16384
-# sysctl -w kern.ipc.nmbclusters=65536
</pre>
</blockquote>
+<p>
+
+These parameters cannot be set from <b>/boot/loader.conf</b>
+(verified with FreeBSD 4.2).
+
+<p>
+
+Other kernel parameters such as <b>kern.maxproc</b> can be increased
+only by recompiling the kernel with a different <b>maxusers</b>
+setting in the kernel configuration file (verified with FreeBSD 4.2).
+
<hr>
<a name="moby-linux"><h3>Running hundreds of Postfix processes on Linux</h3></a>
<hr>
+<a name="msql_limit"><h3>Too many connections</h3></a>
+
+This message is produced by the MYQSL server. You need to increase
+the number of connections that it can handle. Things to bear in
+mind: the <b>virtual</b> and <b>canonical</b> maps are accessed by
+every <b>smtpd</b> and <b>cleanup</b> process.
+
+<hr>
+
+<a name="reiser_bugs"><h3>write queue file: No such file or directory</h3></a>
+
+<h3>write queue file: Unknown error 4294967289</h3>
+
+Reiserfs reports the wrong error code when a message exceeds the
+<b>message_size_limit</b> setting. As a result, the Postfix SMTP
+server reports a "queue file write error" to the SMTP client, rather
+than reporting a "file too large" condition. The client will keep
+sending the same email again and again until the mail is too old.
+
+<hr>
+
<a href="index.html">Up one level</a> | Postfix FAQ
</body>
found in <b>services</b>(4).
<b>Authentication</b> <b>controls</b>
- <b>lmtp</b><i>_</i><b>enable</b><i>_</i><b>sasl</b><i>_</i><b>auth</b>
+ <b>lmtp</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
support.
The <b>q</b> flag affects only entire addresses,
not the partial address information from the
- <b>$user</b>, <b>extension</b> or <b>mailbox</b> command-line
+ <b>$user</b>, <b>$extension</b> or <b>$mailbox</b> command-line
macros.
<b>u</b> Fold the command-line <b>$recipient</b> address
--- /dev/null
+<html> <head> </head> <body> <pre>
+
+QMQPD(8) QMQPD(8)
+
+<b>NAME</b>
+ qmqpd - Postfix QMQP server
+
+<b>SYNOPSIS</b>
+ <b>qmqpd</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The Postfix QMQP server receives one message per connec-
+ tion. Each message is piped through the <a href="cleanup.8.html"><b>cleanup</b>(8)</a> dae-
+ mon, and is placed into the <b>incoming</b> queue as one single
+ queue file. The program expects to be run from the <a href="master.8.html"><b>mas-</b></a>
+ <a href="master.8.html"><b>ter</b>(8)</a> process manager.
+
+ The QMQP server implements one access policy: only explic-
+ itly authorized client hosts are allowed to use the ser-
+ vice.
+
+<b>SECURITY</b>
+ The QMQP server is moderately security-sensitive. It talks
+ to QMQP clients and to DNS servers on the network. The
+ QMQP server can be run chrooted at fixed low privilege.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8).
+
+<b>BUGS</b>
+ The QMQP protocol provides only one server reply per mes-
+ sage delivery. It is therefore not possible to reject
+ individual recipients.
+
+ The QMQP protocol requires the server to receive the
+ entire message before replying. If a message is malformed,
+ or if any netstring component is longer than acceptable,
+ Postfix replies immediately and closes the connection. It
+ is left up to the client to handle the situation.
+
+<b>CONFIGURATION</b> <b>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>
+ command after a configuration change.
+
+<b>Miscellaneous</b>
+ <b>always</b><i>_</i><b>bcc</b>
+ Address to send a copy of each message that enters
+ the system.
+
+ <b>debug</b><i>_</i><b>peer</b><i>_</i><b>level</b>
+ Increment in verbose logging level when a remote
+ host matches a pattern in the <b>debug</b><i>_</i><b>peer</b><i>_</i><b>list</b>
+ parameter.
+
+ <b>debug</b><i>_</i><b>peer</b><i>_</i><b>list</b>
+ List of domain or network patterns. When a remote
+ host matches a pattern, increase the verbose log-
+ ging level by the amount specified in the
+ <b>debug</b><i>_</i><b>peer</b><i>_</i><b>level</b> parameter.
+
+ <b>hopcount</b><i>_</i><b>limit</b>
+ Limit the number of <b>Received:</b> message headers.
+
+ <b>qmqpd</b><i>_</i><b>authorized</b><i>_</i><b>clients</b>
+ A list of domain or network patterns that specifies
+ what clients are allowed to use the service.
+
+ <b>qmqpd</b><i>_</i><b>timeout</b>
+ Limit the time to send a server response and to
+ receive a client request.
+
+ <b>soft</b><i>_</i><b>bounce</b>
+ Change hard (D) reject responses into soft (Z)
+ reject responses. This can be useful for testing
+ purposes.
+
+<b>Content</b> <b>inspection</b> <b>controls</b>
+ <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
+ result back into Postfix. This parameter uses the
+ same syntax as the right-hand side of a Postfix
+ transport table.
+
+<b>Resource</b> <b>controls</b>
+ <b>line</b><i>_</i><b>length</b><i>_</i><b>limit</b>
+ Limit the amount of memory in bytes used for the
+ handling of partial input lines, and the length of
+ sender and recipient addresses that are received
+ from client.
+
+ <b>message</b><i>_</i><b>size</b><i>_</i><b>limit</b>
+ Limit the total size in bytes of a message, includ-
+ ing on-disk storage for sender and recipient
+ address information.
+
+<b>Tarpitting</b>
+ <b>qmqpd</b><i>_</i><b>error</b><i>_</i><b>sleep</b><i>_</i><b>time</b>
+ Time to wait in seconds before informing the client
+ of a problem. This slows down run-away errors.
+
+<b>SEE</b> <b>ALSO</b>
+ http://cr.yp.to/proto/qmqp.html, QMQP protocol
+ <a href="cleanup.8.html">cleanup(8)</a> message canonicalization
+ <a href="master.8.html">master(8)</a> process manager
+ syslogd(8) system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this
+ software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ 1
+
+</pre> </body> </html>
<b>smtp</b><i>_</i><b>never</b><i>_</i><b>send</b><i>_</i><b>ehlo</b>
Never send EHLO at the start of a connection.
+ <b>smtp</b><i>_</i><b>break</b><i>_</i><b>lines</b>
+ Break lines > <b>$line</b><i>_</i><b>length</b><i>_</i><b>limit</b> into multiple
+ shorter lines. Some SMTP servers misbehave on long
+ lines.
+
<b>smtp</b><i>_</i><b>skip</b><i>_</i><b>4xx</b><i>_</i><b>greeting</b>
Skip servers that greet us with a 4xx status code.
DAEMONS = man8/bounce.8 man8/defer.8 man8/cleanup.8 man8/error.8 man8/local.8 \
man8/lmtp.8 man8/master.8 man8/pickup.8 man8/pipe.8 man8/qmgr.8 \
man8/showq.8 man8/smtp.8 man8/smtpd.8 man8/trivial-rewrite.8 \
- man8/nqmgr.8 man8/spawn.8 man8/flush.8 man8/virtual.8
+ man8/nqmgr.8 man8/spawn.8 man8/flush.8 man8/virtual.8 man8/qmqpd.8
COMMANDS= man1/postalias.1 man1/postcat.1 man1/postconf.1 man1/postfix.1 \
man1/postkick.1 man1/postlock.1 man1/postlog.1 man1/postdrop.1 \
man1/postmap.1 man1/sendmail.1 man1/mailq.1 man1/newaliases.1 \
man8/qmgr.8: ../src/qmgr/qmgr.c
../mantools/srctoman $? >$@
+man8/qmqpd.8: ../src/qmqpd/qmqpd.c
+ ../mantools/srctoman $? >$@
+
man8/showq.8: ../src/showq/showq.c
../mantools/srctoman $? >$@
The TCP port to be used when connecting to a LMTP server. Used as
backup if the \fBlmtp\fR service is not found in \fBservices\fR(4).
.SH "Authentication controls"
-.IP \fBlmtp_enable_sasl_auth\fR
+.IP \fBlmtp_sasl_auth_enable\fR
Enable per-session authentication as per RFC 2554 (SASL).
By default, Postfix is built without SASL support.
.IP \fBlmtp_sasl_password_maps\fR
recipients by the Postfix \fBsendmail\fR mail submission command.
.sp
The \fBq\fR flag affects only entire addresses, not the partial
-address information from the \fB$user\fR, \fBextension\fR or
-\fBmailbox\fR command-line macros.
+address information from the \fB$user\fR, \fB$extension\fR or
+\fB$mailbox\fR command-line macros.
.IP \fBu\fR
Fold the command-line \fB$recipient\fR address localpart (text to
the left of the right-most \fB@\fR character) to lower case.
--- /dev/null
+.TH QMQPD 8
+.ad
+.fi
+.SH NAME
+qmqpd
+\-
+Postfix QMQP server
+.SH SYNOPSIS
+.na
+.nf
+\fBqmqpd\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix QMQP server receives one message per connection.
+Each message is piped through the \fBcleanup\fR(8)
+daemon, and is placed into the \fBincoming\fR queue as one
+single queue file. The program expects to be run from the
+\fBmaster\fR(8) process manager.
+
+The QMQP server implements one access policy: only explicitly
+authorized client hosts are allowed to use the service.
+.SH SECURITY
+.na
+.nf
+.ad
+.fi
+The QMQP server is moderately security-sensitive. It talks to QMQP
+clients and to DNS servers on the network. The QMQP server can be
+run chrooted at fixed low privilege.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8).
+.SH BUGS
+.ad
+.fi
+The QMQP protocol provides only one server reply per message
+delivery. It is therefore not possible to reject individual
+recipients.
+
+The QMQP protocol requires the server to receive the entire
+message before replying. If a message is malformed, or if any
+netstring component is longer than acceptable, Postfix replies
+immediately and closes the connection. It is left up to the
+client to handle the situation.
+.SH CONFIGURATION PARAMETERS
+.na
+.nf
+.ad
+.fi
+The following \fBmain.cf\fR parameters are especially relevant to
+this program. See the Postfix \fBmain.cf\fR file for syntax details
+and for default values. Use the \fBpostfix reload\fR command after
+a configuration change.
+.SH Miscellaneous
+.ad
+.fi
+.IP \fBalways_bcc\fR
+Address to send a copy of each message that enters the system.
+.IP \fBdebug_peer_level\fR
+Increment in verbose logging level when a remote host matches a
+pattern in the \fBdebug_peer_list\fR parameter.
+.IP \fBdebug_peer_list\fR
+List of domain or network patterns. When a remote host matches
+a pattern, increase the verbose logging level by the amount
+specified in the \fBdebug_peer_level\fR parameter.
+.IP \fBhopcount_limit\fR
+Limit the number of \fBReceived:\fR message headers.
+.IP \fBqmqpd_authorized_clients\fR
+A list of domain or network patterns that specifies what
+clients are allowed to use the service.
+.IP \fBqmqpd_timeout\fR
+Limit the time to send a server response and to receive a client
+request.
+.IP \fBsoft_bounce\fR
+Change hard (D) reject responses into soft (Z) reject responses.
+This can be useful for testing purposes.
+.SH "Content inspection controls"
+.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.
+This parameter uses the same syntax as the right-hand side of
+a Postfix transport table.
+.SH "Resource controls"
+.ad
+.fi
+.IP \fBline_length_limit\fR
+Limit the amount of memory in bytes used for the handling of
+partial input lines, and the length of sender and recipient
+addresses that are received from client.
+.IP \fBmessage_size_limit\fR
+Limit the total size in bytes of a message, including on-disk
+storage for sender and recipient address information.
+.SH Tarpitting
+.ad
+.fi
+.IP \fBqmqpd_error_sleep_time\fR
+Time to wait in seconds before informing the client of
+a problem. This slows down run-away errors.
+.SH SEE ALSO
+.na
+.nf
+http://cr.yp.to/proto/qmqp.html, QMQP protocol
+cleanup(8) message canonicalization
+master(8) process manager
+syslogd(8) system logging
+.SH LICENSE
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH AUTHOR(S)
+.na
+.nf
+Wietse Venema
+IBM T.J. Watson Research
+P.O. Box 704
+Yorktown Heights, NY 10598, USA
Always send EHLO at the start of a connection.
.IP \fBsmtp_never_send_ehlo\fR
Never send EHLO at the start of a connection.
+.IP \fBsmtp_break_lines\fR
+Break lines > \fB$line_length_limit\fR into multiple shorter lines.
+Some SMTP servers misbehave on long lines.
.IP \fBsmtp_skip_4xx_greeting\fR
Skip servers that greet us with a 4xx status code.
.IP \fBsmtp_skip_5xx_greeting\fR
s/[<bB>]*pickup[</bB>]*(8)/<a href="pickup.8.html">&<\/a>/
s/[<bB>]*pipe[</bB>]*(8)/<a href="pipe.8.html">&<\/a>/
s/[<bB>]*qmgr[</bB>]*(8)/<a href="qmgr.8.html">&<\/a>/
+ s/[<bB>]*qmqpd[</bB>]*(8)/<a href="qmqpd.8.html">&<\/a>/
s/[<bB>]*showq[</bB>]*(8)/<a href="showq.8.html">&<\/a>/
s/[<bB>]*smtp[</bB>]*(8)/<a href="smtp.8.html">&<\/a>/
s/[<bB>]*smtpd[</bB>]*(8)/<a href="smtpd.8.html">&<\/a>/
recipient_list.h record.h resolve_clnt.h resolve_local.h \
rewrite_clnt.h sent.h smtp_stream.h split_addr.h string_list.h \
sys_exits.h timed_ipc.h tok822.h xtext.h bounce_log.h flush_clnt.h \
- mbox_conf.h mbox_open.h abounce.h
+ mbox_conf.h mbox_open.h abounce.h qmqp_proto.h
TESTSRC = rec2stream.c stream2rec.c recdump.c
WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \
-Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \
#define DEF_SMTP_RAND_ADDR 1
extern bool var_smtp_rand_addr;
+#define VAR_SMTP_BREAK_LINES "smtp_break_lines"
+#define DEF_SMTP_BREAK_LINES 1
+extern bool var_smtp_break_lines;
+
/*
* SMTP server. The soft error limit determines how many errors an SMTP
* client may make before we start to slow down; the hard error limit
#define DEF_SYSLOG_NAME "postfix"
extern char *var_syslog_name;
+ /*
+ * QMQPD
+ */
+#define VAR_QMQPD_CLIENTS "qmqpd_authorized_clients"
+#define DEF_QMQPD_CLIENTS ""
+extern char *var_qmqpd_clients;
+
+#define VAR_QMTPD_TMOUT "qmqpd_timeout"
+#define DEF_QMTPD_TMOUT "300s"
+extern int var_qmqpd_timeout;
+
+#define VAR_QMTPD_ERR_SLEEP "qmqpd_error_delay"
+#define DEF_QMTPD_ERR_SLEEP "5s"
+extern int var_qmqpd_err_sleep;
+
/* LICENSE
/* .ad
/* .fi
/* mail_queue_rename() renames a queue file. A non-zero result
/* means the operation failed.
/*
-/* mail_queue_remove() renames the named queue file. A non-zero result
+/* mail_queue_remove() removes the named queue file. A non-zero result
/* means the operation failed.
/*
/* mail_queue_name_ok() validates a mail queue name and returns
* Version of this program.
*/
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "Snapshot-20010610"
+#define DEF_MAIL_VERSION "Snapshot-20010707"
extern char *var_mail_version;
/* LICENSE
--- /dev/null
+/*++
+/* NAME
+/* qmqpd 3h
+/* SUMMARY
+/* QMQP protocol
+/* SYNOPSIS
+/* include <qmqpd_proto.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * QMQP protocol status codes.
+ */
+#define QMQP_STAT_OK 'K' /* success */
+#define QMQP_STAT_RETRY 'Z' /* recoverable error */
+#define QMQP_STAT_HARD 'D' /* unrecoverable error */
+
+/* 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
+/*--*/
/* The TCP port to be used when connecting to a LMTP server. Used as
/* backup if the \fBlmtp\fR service is not found in \fBservices\fR(4).
/* .SH "Authentication controls"
-/* .IP \fBlmtp_enable_sasl_auth\fR
+/* .IP \fBlmtp_sasl_auth_enable\fR
/* Enable per-session authentication as per RFC 2554 (SASL).
/* By default, Postfix is built without SASL support.
/* .IP \fBlmtp_sasl_password_maps\fR
*/
case LMTP_STATE_RCPT:
if (!mail_from_rejected) {
-#ifndef notRFC821_SYNTAX
+#ifdef notdef
if (resp->code == 552)
resp->code = 452;
#endif
SET_USER_ATTR(usr_attr, mbox_pwd, state.level);
/*
- * Deliver to mailbox or to external command.
+ * Deliver to mailbox, maildir or to external command.
*/
#define LAST_CHAR(s) (s[strlen(s) - 1])
path = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0);
status = deliver_maildir(state, usr_attr, path);
myfree(path);
+ } else if (*var_mail_spool_dir && LAST_CHAR(var_mail_spool_dir) == '/') {
+ path = concatenate(var_mail_spool_dir, state.msg_attr.user,
+ "/", (char *) 0);
+ status = deliver_maildir(state, usr_attr, path);
+ myfree(path);
} else
status = deliver_mailbox_file(state, usr_attr);
/* recipients by the Postfix \fBsendmail\fR mail submission command.
/* .sp
/* The \fBq\fR flag affects only entire addresses, not the partial
-/* address information from the \fB$user\fR, \fBextension\fR or
-/* \fBmailbox\fR command-line macros.
+/* address information from the \fB$user\fR, \fB$extension\fR or
+/* \fB$mailbox\fR command-line macros.
/* .IP \fBu\fR
/* Fold the command-line \fB$recipient\fR address localpart (text to
/* the left of the right-most \fB@\fR character) to lower case.
--- /dev/null
+../../.indent.pro
\ No newline at end of file
--- /dev/null
+been_here_xt 2 0
+bounce_append 5 0
+cleanup_out_format 1 0
+defer_append 5 0
+mail_command 1 0
+mail_print 1 0
+msg_error 0 0
+msg_fatal 0 0
+msg_info 0 0
+msg_panic 0 0
+msg_warn 0 0
+opened 4 0
+post_mail_fprintf 1 0
+qmgr_message_bounce 2 0
+rec_fprintf 2 0
+sent 4 0
+smtp_cmd 1 0
+smtp_mesg_fail 2 0
+smtp_printf 1 0
+smtp_rcpt_fail 3 0
+smtp_site_fail 2 0
+udp_syslog 1 0
+vstream_fprintf 1 0
+vstream_printf 0 0
+vstring_sprintf 1 0
--- /dev/null
+SHELL = /bin/sh
+SRCS = qmqpd.c qmqpd_state.c qmqpd_peer.c
+OBJS = qmqpd.o qmqpd_state.o qmqpd_peer.o
+HDRS =
+TESTSRC =
+WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \
+ -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \
+ -Wunused
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG= qmqpd_token qmqpd_check
+PROG = qmqpd
+INC_DIR = ../../include
+LIBS = ../../lib/libmaster.a ../../lib/libglobal.a ../../lib/libdns.a ../../lib/libutil.a
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+$(PROG): $(OBJS) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
+
+Makefile: Makefile.in
+ (set -e; echo "# DO NOT EDIT"; $(OPTS) $(SHELL) ../../makedefs && cat $?) >$@
+
+test: $(TESTPROG)
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+SMTPD_CHECK_OBJ = qmqpd_state.o qmqpd_peer.o
+
+qmqpd_token: qmqpd_token.c $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS)
+
+qmqpd_check: qmqpd_check.c $(SMTPD_CHECK_OBJ) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ qmqpd_check.c $(SMTPD_CHECK_OBJ) \
+ $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir printfck
+ cp *.h printfck
+ sed '1,/^# do not edit/!d' Makefile >printfck/Makefile
+ set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done
+ cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o`
+
+lint:
+ lint $(DEFS) $(SRCS) $(LINTFIX)
+
+clean:
+ rm -f *.o *core $(PROG) $(TESTPROG) junk *.db *.out *.tmp
+ rm -rf printfck
+
+tidy: clean
+
+depend: $(MAKES)
+ (sed '1,/^# do not edit/!d' Makefile.in; \
+ set -e; for i in [a-z][a-z0-9]*.c; do \
+ $(CC) -E $(DEFS) $(INCL) $$i | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \
+ -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' -e 'p' -e '}'; \
+ done) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in
+ @$(EXPORT) make -f Makefile.in Makefile 1>&2
+
+tests:
+
+# do not edit below this line - it is generated by 'make depend'
+qmqpd.o: qmqpd.c
+qmqpd.o: ../../include/sys_defs.h
+qmqpd.o: ../../include/msg.h
+qmqpd.o: ../../include/mymalloc.h
+qmqpd.o: ../../include/vstring.h
+qmqpd.o: ../../include/vbuf.h
+qmqpd.o: ../../include/vstream.h
+qmqpd.o: ../../include/netstring.h
+qmqpd.o: ../../include/dict.h
+qmqpd.o: ../../include/argv.h
+qmqpd.o: ../../include/mail_params.h
+qmqpd.o: ../../include/record.h
+qmqpd.o: ../../include/rec_type.h
+qmqpd.o: ../../include/mail_proto.h
+qmqpd.o: ../../include/iostuff.h
+qmqpd.o: ../../include/cleanup_user.h
+qmqpd.o: ../../include/mail_date.h
+qmqpd.o: ../../include/mail_conf.h
+qmqpd.o: ../../include/debug_peer.h
+qmqpd.o: ../../include/mail_stream.h
+qmqpd.o: ../../include/namadr_list.h
+qmqpd.o: ../../include/quote_822_local.h
+qmqpd.o: ../../include/mail_server.h
+qmqpd.o: qmqpd.h
+qmqpd_peer.o: qmqpd_peer.c
+qmqpd_peer.o: ../../include/sys_defs.h
+qmqpd_peer.o: ../../include/msg.h
+qmqpd_peer.o: ../../include/mymalloc.h
+qmqpd_peer.o: ../../include/valid_hostname.h
+qmqpd_peer.o: ../../include/stringops.h
+qmqpd_peer.o: ../../include/vstring.h
+qmqpd_peer.o: ../../include/vbuf.h
+qmqpd_peer.o: qmqpd.h
+qmqpd_peer.o: ../../include/vstream.h
+qmqpd_peer.o: ../../include/mail_stream.h
+qmqpd_state.o: qmqpd_state.c
+qmqpd_state.o: ../../include/sys_defs.h
+qmqpd_state.o: ../../include/mymalloc.h
+qmqpd_state.o: ../../include/vstream.h
+qmqpd_state.o: ../../include/vbuf.h
+qmqpd_state.o: ../../include/vstring.h
+qmqpd_state.o: ../../include/mail_stream.h
+qmqpd_state.o: ../../include/cleanup_user.h
+qmqpd_state.o: qmqpd.h
--- /dev/null
+/*++
+/* NAME
+/* qmqpd 8
+/* SUMMARY
+/* Postfix QMQP server
+/* SYNOPSIS
+/* \fBqmqpd\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The Postfix QMQP server receives one message per connection.
+/* Each message is piped through the \fBcleanup\fR(8)
+/* daemon, and is placed into the \fBincoming\fR queue as one
+/* single queue file. The program expects to be run from the
+/* \fBmaster\fR(8) process manager.
+/*
+/* The QMQP server implements one access policy: only explicitly
+/* authorized client hosts are allowed to use the service.
+/* SECURITY
+/* .ad
+/* .fi
+/* The QMQP server is moderately security-sensitive. It talks to QMQP
+/* clients and to DNS servers on the network. The QMQP server can be
+/* run chrooted at fixed low privilege.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8).
+/* BUGS
+/* The QMQP protocol provides only one server reply per message
+/* delivery. It is therefore not possible to reject individual
+/* recipients.
+/*
+/* The QMQP protocol requires the server to receive the entire
+/* message before replying. If a message is malformed, or if any
+/* netstring component is longer than acceptable, Postfix replies
+/* immediately and closes the connection. It is left up to the
+/* client to handle the situation.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially relevant to
+/* this program. See the Postfix \fBmain.cf\fR file for syntax details
+/* and for default values. Use the \fBpostfix reload\fR command after
+/* a configuration change.
+/* .SH Miscellaneous
+/* .ad
+/* .fi
+/* .IP \fBalways_bcc\fR
+/* Address to send a copy of each message that enters the system.
+/* .IP \fBdebug_peer_level\fR
+/* Increment in verbose logging level when a remote host matches a
+/* pattern in the \fBdebug_peer_list\fR parameter.
+/* .IP \fBdebug_peer_list\fR
+/* List of domain or network patterns. When a remote host matches
+/* a pattern, increase the verbose logging level by the amount
+/* specified in the \fBdebug_peer_level\fR parameter.
+/* .IP \fBhopcount_limit\fR
+/* Limit the number of \fBReceived:\fR message headers.
+/* .IP \fBqmqpd_authorized_clients\fR
+/* A list of domain or network patterns that specifies what
+/* clients are allowed to use the service.
+/* .IP \fBqmqpd_timeout\fR
+/* Limit the time to send a server response and to receive a client
+/* request.
+/* .IP \fBsoft_bounce\fR
+/* Change hard (D) reject responses into soft (Z) reject responses.
+/* This can be useful for testing purposes.
+/* .SH "Content inspection controls"
+/* .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.
+/* This parameter uses the same syntax as the right-hand side of
+/* a Postfix transport table.
+/* .SH "Resource controls"
+/* .ad
+/* .fi
+/* .IP \fBline_length_limit\fR
+/* Limit the amount of memory in bytes used for the handling of
+/* partial input lines, and the length of sender and recipient
+/* addresses that are received from client.
+/* .IP \fBmessage_size_limit\fR
+/* Limit the total size in bytes of a message, including on-disk
+/* storage for sender and recipient address information.
+/* .SH Tarpitting
+/* .ad
+/* .fi
+/* .IP \fBqmqpd_error_sleep_time\fR
+/* Time to wait in seconds before informing the client of
+/* a problem. This slows down run-away errors.
+/* SEE ALSO
+/* http://cr.yp.to/proto/qmqp.html, QMQP protocol
+/* cleanup(8) message canonicalization
+/* master(8) process manager
+/* syslogd(8) system logging
+/* 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 <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <netstring.h>
+#include <dict.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_proto.h>
+#include <cleanup_user.h>
+#include <mail_date.h>
+#include <mail_conf.h>
+#include <debug_peer.h>
+#include <mail_stream.h>
+#include <namadr_list.h>
+#include <quote_822_local.h>
+
+/* Single-threaded server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific */
+
+#include <qmqpd.h>
+
+ /*
+ * Tunable parameters. Make sure that there is some bound on the length of a
+ * netstring, so that the mail system stays in control even when a malicious
+ * client sends netstrings of unreasonable length. The recipient count limit
+ * is enforced by the message size limit.
+ */
+int var_qmqpd_timeout;
+int var_qmqpd_err_sleep;
+char *var_always_bcc;
+char *var_filter_xport;
+char *var_qmqpd_clients;
+
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+#define DO_LOG 1
+#define DONT_LOG 0
+
+ /*
+ * Access control. This service should be exposed only to explicitly
+ * authorized clients. There is no default authorization.
+ */
+static NAMADR_LIST *qmqpd_clients;
+
+/* qmqpd_open_file - open a queue file */
+
+static void qmqpd_open_file(QMQPD_STATE *state)
+{
+
+ /*
+ * Connect to the cleanup server. Log client name/address with queue ID.
+ */
+ state->dest = mail_stream_service(MAIL_CLASS_PRIVATE, MAIL_SERVICE_CLEANUP);
+ if (state->dest == 0
+ || mail_print(state->dest->stream, "%d", CLEANUP_FLAG_FILTER) != 0)
+ msg_fatal("unable to connect to the %s %s service",
+ MAIL_CLASS_PRIVATE, MAIL_SERVICE_CLEANUP);
+ state->cleanup = state->dest->stream;
+ state->queue_id = mystrdup(state->dest->id);
+ msg_info("%s: client=%s", state->queue_id, state->namaddr);
+
+ /*
+ * Record the time of arrival. Optionally, enable content filtering (not
+ * bloody likely, but present for the sake of consistency with all other
+ * Postfix points of entrance).
+ */
+ rec_fprintf(state->cleanup, REC_TYPE_TIME, "%ld", state->time);
+ if (*var_filter_xport)
+ rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", var_filter_xport);
+}
+
+/* qmqpd_read_content - receive message content */
+
+static void qmqpd_read_content(QMQPD_STATE *state)
+{
+ state->where = "receiving message content";
+ netstring_get(state->client, state->message, var_message_limit);
+}
+
+/* qmqpd_copy_sender - copy envelope sender */
+
+static void qmqpd_copy_sender(QMQPD_STATE *state)
+{
+ state->where = "receiving sender address";
+ netstring_get(state->client, state->buf, var_line_limit);
+ if (state->err == CLEANUP_STAT_OK
+ && REC_PUT_BUF(state->cleanup, REC_TYPE_FROM, state->buf) < 0)
+ state->err = CLEANUP_STAT_WRITE;
+ state->sender = mystrndup(STR(state->buf), LEN(state->buf));
+}
+
+/* qmqpd_copy_recipients - copy message recipients */
+
+static void qmqpd_copy_recipients(QMQPD_STATE *state)
+{
+ int ch;
+
+ /*
+ * Remember the first recipient. We are done when we read the over-all
+ * netstring terminator.
+ *
+ * XXX This approach violates abstractions, but it is a heck of a lot more
+ * convenient than counting the over-all byte count down to zero, like
+ * qmail does.
+ */
+ state->where = "receiving recipient address";
+ while ((ch = VSTREAM_GETC(state->client)) != ',') {
+ vstream_ungetc(state->client, ch);
+ netstring_get(state->client, state->buf, var_line_limit);
+ if (state->err == CLEANUP_STAT_OK
+ && REC_PUT_BUF(state->cleanup, REC_TYPE_RCPT, state->buf) < 0)
+ state->err = CLEANUP_STAT_WRITE;
+ state->rcpt_count++;
+ if (state->recipient == 0)
+ state->recipient = mystrndup(STR(state->buf), LEN(state->buf));
+ }
+
+ /*
+ * Append the optional recipient who is copied on all mail.
+ */
+ if (*var_always_bcc)
+ rec_fputs(state->cleanup, REC_TYPE_RCPT, var_always_bcc);
+}
+
+/* qmqpd_next_line - get line from buffer, return last char, newline, or -1 */
+
+static int qmqpd_next_line(VSTRING *message, char **start, int *len,
+ char **next)
+{
+ char *beyond = STR(message) + LEN(message);
+ char *enough = *next + var_line_limit;
+ char *cp;
+
+ /*
+ * Stop at newline or at some limit. Don't look beyond the end of the
+ * buffer.
+ */
+#define UCHARPTR(x) ((unsigned char *) (x))
+
+ for (cp = *start = *next; /* void */ ; cp++) {
+ if (cp >= beyond)
+ return ((*len = (*next = cp) - *start) > 0 ? UCHARPTR(cp)[-1] : -1);
+ if (*cp == '\n')
+ return ((*len = cp - *start), (*next = cp + 1), '\n');
+ if (cp >= enough)
+ return ((*len = cp - *start), (*next = cp), UCHARPTR(cp)[-1]);
+ }
+}
+
+/* qmqpd_write_content - write the message content segment */
+
+static void qmqpd_write_content(QMQPD_STATE *state)
+{
+ char *start;
+ char *next;
+ int len;
+ int rec_type;
+ int first = 1;
+ int ch;
+
+ /*
+ * Start the message content segment. Prepend our own Received: header to
+ * the message content. List the recipient only when a message has one
+ * recipient. Otherwise, don't list the recipient to avoid revealing Bcc:
+ * recipients that are supposed to be invisible.
+ */
+ rec_fputs(state->cleanup, REC_TYPE_MESG, "");
+ rec_fprintf(state->cleanup, REC_TYPE_NORM, "Received: from %s (%s [%s])",
+ state->name, state->name, state->addr);
+ if (state->rcpt_count == 1 && state->recipient) {
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\tby %s (%s) with %s id %s",
+ var_myhostname, var_mail_name,
+ state->protocol, state->queue_id);
+ quote_822_local(state->buf, state->recipient);
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\tfor <%s>; %s", STR(state->buf), mail_date(state->time));
+ } else {
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\tby %s (%s) with %s",
+ var_myhostname, var_mail_name, state->protocol);
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\tid %s; %s", state->queue_id, mail_date(state->time));
+ }
+#ifdef RECEIVED_ENVELOPE_FROM
+ quote_822_local(state->buf, state->sender);
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\t(envelope-from <%s>)", STR(state->buf));
+#endif
+
+ /*
+ * Write the message content.
+ *
+ * XXX Force an empty record when the queue file content begins with
+ * whitespace, so that it won't be considered as being part of our own
+ * Received: header. What an ugly Kluge.
+ *
+ * XXX Deal with UNIX-style From_ lines at the start of message content just
+ * in case.
+ */
+ for (next = STR(state->message); /* void */ ; /* void */ ) {
+ if ((ch = qmqpd_next_line(state->message, &start, &len, &next)) < 0)
+ break;
+ if (ch == '\n')
+ rec_type = REC_TYPE_NORM;
+ else
+ rec_type = REC_TYPE_CONT;
+ if (first) {
+ if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) {
+ rec_fprintf(state->cleanup, rec_type,
+ "Mailbox-Line: %*s", len, start);
+ continue;
+ }
+ first = 0;
+ if (len > 0 && ISSPACE(start[0]))
+ rec_put(state->cleanup, REC_TYPE_NORM, "", 0);
+ }
+ if (rec_put(state->cleanup, rec_type, start, len) < 0) {
+ state->err = CLEANUP_STAT_WRITE;
+ return;
+ }
+ }
+}
+
+/* qmqpd_close_file - close queue file */
+
+static void qmqpd_close_file(QMQPD_STATE *state)
+{
+
+ /*
+ * Send the end-of-segment markers.
+ */
+ if (state->err == CLEANUP_STAT_OK)
+ if (rec_fputs(state->cleanup, REC_TYPE_XTRA, "") < 0
+ || rec_fputs(state->cleanup, REC_TYPE_END, "") < 0
+ || vstream_fflush(state->cleanup))
+ state->err = CLEANUP_STAT_WRITE;
+
+ /*
+ * Finish the queue file or finish the cleanup conversation.
+ */
+ if (state->err == 0)
+ state->err = mail_stream_finish(state->dest);
+ else
+ mail_stream_cleanup(state->dest);
+ state->dest = 0;
+}
+
+/* qmqpd_reply - send status to client and optionally log message */
+
+static void qmqpd_reply(QMQPD_STATE *state, int log_message,
+ int status_code, const char *fmt,...)
+{
+ va_list ap;
+
+ /*
+ * Optionally change hard errors into retryable ones. Send the reply and
+ * optionally log it. Always insert a delay before reporting a problem.
+ * This slows down software run-away conditions.
+ */
+ if (status_code == QMQPD_STAT_HARD && var_soft_bounce)
+ status_code = QMQPD_STAT_RETRY;
+ VSTRING_RESET(state->buf);
+ VSTRING_ADDCH(state->buf, status_code);
+ va_start(ap, fmt);
+ vstring_vsprintf_append(state->buf, fmt, ap);
+ va_end(ap);
+ NETSTRING_PUT_BUF(state->client, state->buf);
+ if (log_message)
+ (status_code == QMQPD_STAT_OK ? msg_info : msg_warn) ("%s: %s: %s",
+ state->queue_id, state->namaddr, STR(state->buf) + 1);
+ if (status_code != QMQPD_STAT_OK)
+ sleep(var_qmqpd_err_sleep);
+ netstring_fflush(state->client);
+}
+
+/* qmqpd_send_status - send mail transaction completion status */
+
+static int qmqpd_send_status(QMQPD_STATE *state)
+{
+
+ /*
+ * One message may suffer from multiple errors, so complain only about
+ * the most severe error.
+ */
+ state->where = "sending completion status";
+
+ if (state->err == CLEANUP_STAT_OK) {
+ qmqpd_reply(state, DONT_LOG, QMQPD_STAT_OK,
+ "Ok: queued as %s", state->queue_id);
+ } else if ((state->err & CLEANUP_STAT_BAD) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY,
+ "Error: internal error %d", state->err);
+ } else if ((state->err & CLEANUP_STAT_SIZE) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD,
+ "Error: message too large");
+ } else if ((state->err & CLEANUP_STAT_HOPS) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD,
+ "Error: too many hops");
+ } else if ((state->err & CLEANUP_STAT_CONT) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD,
+ "Error: content rejected");
+ } else if ((state->err & CLEANUP_STAT_WRITE) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY,
+ "Error: queue file write error");
+ } else if ((state->err & CLEANUP_STAT_RCPT) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD,
+ "Error: no recipients specified");
+ } else {
+ msg_panic("qmqpd_send_status: unknown status %d", state->err);
+ }
+}
+
+/* qmqpd_receive - receive QMQP message+sender+recipients */
+
+static int qmqpd_receive(QMQPD_STATE *state)
+{
+
+ /*
+ * Open a queue file. This must be first so that we can simplify the
+ * error logging and always include the queue ID information.
+ */
+ qmqpd_open_file(state);
+
+ /*
+ * Read and ignore the over-all netstring length indicator.
+ */
+ state->where = "receiving QMQP packet header";
+ (void) netstring_get_length(state->client);
+
+ /*
+ * XXX Read the message content into memory, because Postfix expects to
+ * store the sender before storing the message content. Fixing that
+ * requires changes to pickup, cleanup, qmgr, and perhaps elsewhere, so
+ * that will have to happen later when I have more time. However, QMQP is
+ * used for mailing list distribution, so the bulk of the volume is
+ * expected to be not message content but recipients, and recipients are
+ * not accumulated in memory.
+ */
+ qmqpd_read_content(state);
+
+ /*
+ * Read and write the envelope sender.
+ */
+ qmqpd_copy_sender(state);
+
+ /*
+ * Read and write the envelope recipients, including the optional big
+ * brother recipient.
+ */
+ qmqpd_copy_recipients(state);
+
+ /*
+ * Start the message content segment, prepend our own Received: header,
+ * and write the message content.
+ */
+ qmqpd_write_content(state);
+
+ /*
+ * Close the queue file.
+ */
+ qmqpd_close_file(state);
+
+ /*
+ * Report the completion status to the client.
+ */
+ qmqpd_send_status(state);
+}
+
+/* qmqpd_proto - speak the QMQP "protocol" */
+
+static void qmqpd_proto(QMQPD_STATE *state)
+{
+ int status;
+
+ netstring_setup(state->client, var_qmqpd_timeout);
+
+ switch (status = vstream_setjmp(state->client)) {
+
+ default:
+ msg_panic("qmqpd_proto: unknown status %d", status);
+
+ case NETSTRING_ERR_EOF:
+ state->reason = "lost connection";
+ break;
+
+ case NETSTRING_ERR_TIME:
+ state->reason = "read/write timeout";
+ break;
+
+ case NETSTRING_ERR_FORMAT:
+ state->reason = "netstring format error";
+ if (vstream_setjmp(state->client) == 0)
+ if (state->reason && state->where)
+ qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, "%s while %s",
+ state->reason, state->where);
+ break;
+
+ case NETSTRING_ERR_SIZE:
+ state->reason = "netstring length exceeds storage limit";
+ if (vstream_setjmp(state->client) == 0)
+ if (state->reason && state->where)
+ qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, "%s while %s",
+ state->reason, state->where);
+ break;
+
+ case 0:
+ qmqpd_receive(state);
+ break;
+ }
+
+ /*
+ * Log abnormal session termination. Indicate the last recognized state
+ * before things went wrong.
+ */
+ if (state->reason && state->where)
+ msg_info("%s: %s: %s while %s",
+ state->queue_id, state->namaddr, state->reason, state->where);
+}
+
+/* qmqpd_service - service one client */
+
+static void qmqpd_service(VSTREAM *stream, char *unused_service, char **argv)
+{
+ QMQPD_STATE *state;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * This routine runs when a client has connected to our network port.
+ * Look up and sanitize the peer name and initialize some connection-
+ * specific state.
+ */
+ state = qmqpd_state_alloc(stream);
+
+ /*
+ * See if we need to turn on verbose logging for this client.
+ */
+ debug_peer_check(state->name, state->addr);
+
+ /*
+ * See if we want to talk to this client at all. In all cases, log the
+ * connection event.
+ */
+ if (namadr_list_match(qmqpd_clients, state->name, state->addr) == 0) {
+ msg_info("refused connect from %s", state->namaddr);
+ qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD,
+ "Error: %s is not authorized to use this service",
+ state->namaddr);
+ }
+
+ /*
+ * Provide the QMQP service.
+ */
+ else {
+ msg_info("connect from %s", state->namaddr);
+ qmqpd_proto(state);
+ msg_info("disconnect from %s", state->namaddr);
+ }
+
+ /*
+ * After the client has gone away, clean up whatever we have set up at
+ * connection time.
+ */
+ debug_peer_restore();
+ qmqpd_state_free(state);
+}
+
+/* pre_accept - see if tables have changed */
+
+static void pre_accept(char *unused_name, char **unused_argv)
+{
+ if (dict_changed()) {
+ msg_info("lookup table has changed -- exiting");
+ exit(0);
+ }
+}
+
+/* pre_jail_init - pre-jail initialization */
+
+static void pre_jail_init(char *unused_name, char **unused_argv)
+{
+ debug_peer_init();
+ qmqpd_clients = namadr_list_init(var_qmqpd_clients);
+}
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ static CONFIG_TIME_TABLE time_table[] = {
+ VAR_QMTPD_TMOUT, DEF_QMTPD_TMOUT, &var_qmqpd_timeout, 1, 0,
+ VAR_QMTPD_ERR_SLEEP, DEF_QMTPD_ERR_SLEEP, &var_qmqpd_err_sleep, 0, 0,
+ 0,
+ };
+ static CONFIG_STR_TABLE str_table[] = {
+ VAR_ALWAYS_BCC, DEF_ALWAYS_BCC, &var_always_bcc, 0, 0,
+ VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0,
+ VAR_QMQPD_CLIENTS, DEF_QMQPD_CLIENTS, &var_qmqpd_clients, 0, 0,
+ 0,
+ };
+
+ /*
+ * Pass control to the single-threaded service skeleton.
+ */
+ single_server_main(argc, argv, qmqpd_service,
+ MAIL_SERVER_TIME_TABLE, time_table,
+ MAIL_SERVER_STR_TABLE, str_table,
+ MAIL_SERVER_PRE_INIT, pre_jail_init,
+ MAIL_SERVER_PRE_ACCEPT, pre_accept,
+ 0);
+}
--- /dev/null
+/*++
+/* NAME
+/* qmqpd 3h
+/* SUMMARY
+/* Postfix QMQP server
+/* SYNOPSIS
+/* include "qmqpd.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <time.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_stream.h>
+
+ /*
+ * Per-session state.
+ */
+typedef struct {
+ int err; /* error flags */
+ VSTREAM *client; /* client connection */
+ VSTRING *message; /* message buffer */
+ VSTRING *buf; /* line buffer */
+ time_t time; /* start of session */
+ char *name; /* client name */
+ char *addr; /* client IP address */
+ char *namaddr; /* name[addr] */
+ char *queue_id; /* queue file ID */
+ VSTREAM *cleanup; /* cleanup server */
+ MAIL_STREAM *dest; /* cleanup server */
+ int rcpt_count; /* recipient count */
+ char *reason; /* exception name */
+ char *sender; /* sender address */
+ char *recipient; /* recipient address */
+ char *protocol; /* protocol name */
+ char *where; /* protocol state */
+} QMQPD_STATE;
+
+ /*
+ * QMQP protocol status codes.
+ */
+#define QMQPD_STAT_OK 'K'
+#define QMQPD_STAT_RETRY 'Z'
+#define QMQPD_STAT_HARD 'D'
+
+ /*
+ * qmqpd_state.c
+ */
+QMQPD_STATE *qmqpd_state_alloc(VSTREAM *);
+void qmqpd_state_free(QMQPD_STATE *);
+
+ /*
+ * qmqpd_peer.c
+ */
+void qmqpd_peer_init(QMQPD_STATE *);
+void qmqpd_peer_reset(QMQPD_STATE *);
+
+/* 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
+/*--*/
--- /dev/null
+/*++
+/* NAME
+/* qmqpd_peer 3
+/* SUMMARY
+/* look up peer name/address information
+/* SYNOPSIS
+/* #include "qmqpd.h"
+/*
+/* void qmqpd_peer_init(state)
+/* QMQPD_STATE *state;
+/*
+/* void qmqpd_peer_reset(state)
+/* QMQPD_STATE *state;
+/* DESCRIPTION
+/* The qmqpd_peer_init() routine attempts to produce a printable
+/* version of the peer name and address of the specified socket.
+/* Where information is unavailable, the name and/or address
+/* are set to "unknown".
+/*
+/* qmqpd_peer_init() updates the following fields:
+/* .IP name
+/* The client hostname. An unknown name is represented by the
+/* string "unknown".
+/* .IP addr
+/* Printable representation of the client address.
+/* .IP namaddr
+/* String of the form: "name[addr]".
+/* .PP
+/* qmqpd_peer_reset() releases memory allocate by qmqpd_peer_init().
+/* 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 <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h> /* strerror() */
+#include <errno.h>
+#include <netdb.h>
+#include <string.h>
+
+ /*
+ * Older systems don't have h_errno. Even modern systems don't have
+ * hstrerror().
+ */
+#ifdef NO_HERRNO
+
+static int h_errno = TRY_AGAIN;
+
+#define HSTRERROR(err) "Host not found"
+
+#else
+
+#define HSTRERROR(err) (\
+ err == TRY_AGAIN ? "Host not found, try again" : \
+ err == HOST_NOT_FOUND ? "Host not found" : \
+ err == NO_DATA ? "Host name has no address" : \
+ err == NO_RECOVERY ? "Name server failure" : \
+ strerror(errno) \
+ )
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <valid_hostname.h>
+#include <stringops.h>
+
+/* Global library. */
+
+
+/* Application-specific. */
+
+#include "qmqpd.h"
+
+/* qmqpd_peer_init - initialize peer information */
+
+void qmqpd_peer_init(QMQPD_STATE *state)
+{
+ struct sockaddr_in sin;
+ SOCKADDR_SIZE len = sizeof(sin);
+ struct hostent *hp;
+ int i;
+
+ /*
+ * Look up the peer address information.
+ */
+ if (getpeername(vstream_fileno(state->client),
+ (struct sockaddr *) & sin, &len) >= 0) {
+ errno = 0;
+ }
+
+ /*
+ * If peer went away, give up.
+ */
+ if (errno == ECONNRESET || errno == ECONNABORTED) {
+ state->name = mystrdup("unknown");
+ state->addr = mystrdup("unknown");
+ }
+
+ /*
+ * Look up and "verify" the client hostname.
+ */
+ else if (errno == 0 && sin.sin_family == AF_INET) {
+ state->addr = mystrdup(inet_ntoa(sin.sin_addr));
+ hp = gethostbyaddr((char *) &(sin.sin_addr),
+ sizeof(sin.sin_addr), AF_INET);
+ if (hp == 0) {
+ state->name = mystrdup("unknown");
+ } else if (!valid_hostname(hp->h_name, DONT_GRIPE)) {
+ state->name = mystrdup("unknown");
+ } else {
+ state->name = mystrdup(hp->h_name); /* hp->name is clobbered!! */
+
+ /*
+ * Reject the hostname if it does not list the peer address.
+ */
+#define REJECT_PEER_NAME(state) { \
+ myfree(state->name); \
+ state->name = mystrdup("unknown"); \
+ }
+
+ hp = gethostbyname(state->name); /* clobbers hp->name!! */
+ if (hp == 0) {
+ msg_warn("%s: hostname %s verification failed: %s",
+ state->addr, state->name, HSTRERROR(h_errno));
+ REJECT_PEER_NAME(state);
+ } else if (hp->h_length != sizeof(sin.sin_addr)) {
+ msg_warn("%s: hostname %s verification failed: bad address size %d",
+ state->addr, state->name, hp->h_length);
+ REJECT_PEER_NAME(state);
+ } else {
+ for (i = 0; /* void */ ; i++) {
+ if (hp->h_addr_list[i] == 0) {
+ msg_warn("%s: address not listed for hostname %s",
+ state->addr, state->name);
+ REJECT_PEER_NAME(state);
+ break;
+ }
+ if (memcmp(hp->h_addr_list[i],
+ (char *) &sin.sin_addr,
+ sizeof(sin.sin_addr)) == 0)
+ break; /* keep peer name */
+ }
+ }
+ }
+ }
+
+ /*
+ * If it's not Internet, assume the client is local, and avoid using the
+ * naming service because that can hang when the machine is disconnected.
+ */
+ else {
+ state->name = mystrdup("localhost");
+ state->addr = mystrdup("127.0.0.1"); /* XXX bogus. */
+ }
+
+ /*
+ * Do the name[addr] formatting for pretty reports.
+ */
+ state->namaddr =
+ concatenate(state->name, "[", state->addr, "]", (char *) 0);
+}
+
+/* qmqpd_peer_reset - destroy peer information */
+
+void qmqpd_peer_reset(QMQPD_STATE *state)
+{
+ myfree(state->name);
+ myfree(state->addr);
+ myfree(state->namaddr);
+}
--- /dev/null
+/*++
+/* NAME
+/* qmqpd_state 3
+/* SUMMARY
+/* Postfix QMQP server
+/* SYNOPSIS
+/* #include "qmqpd.h"
+/*
+/* QMQPD_STATE *qmqpd_state_alloc(stream)
+/* VSTREAM *stream;
+/*
+/* void qmqpd_state_free(state)
+/* QMQPD_STATE *state;
+/* DESCRIPTION
+/* qmqpd_state_alloc() creates and initializes session context.
+/*
+/* qmqpd_state_free() destroys session context.
+/*
+/* Arguments:
+/* .IP stream
+/* Stream connected to peer. The stream is not copied.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* 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 <time.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_stream.h>
+#include <cleanup_user.h>
+
+/* Application-specific. */
+
+#include <qmqpd.h>
+
+/* qmqpd_state_alloc - allocate and initialize session state */
+
+QMQPD_STATE *qmqpd_state_alloc(VSTREAM *stream)
+{
+ QMQPD_STATE *state;
+
+ state = (QMQPD_STATE *) mymalloc(sizeof(*state));
+ state->err = CLEANUP_STAT_OK;
+ state->client = stream;
+ state->message = vstring_alloc(1000);
+ state->buf = vstring_alloc(100);
+ state->time = time((time_t *) 0);
+ qmqpd_peer_init(state);
+ state->queue_id = 0;
+ state->cleanup = 0;
+ state->dest = 0;
+ state->rcpt_count = 0;
+ state->reason = 0;
+ state->sender = 0;
+ state->recipient = 0;
+ state->protocol = "QMQP";
+ state->where = "initializing client connection";
+ return (state);
+}
+
+/* qmqpd_state_free - destroy session state */
+
+void qmqpd_state_free(QMQPD_STATE *state)
+{
+ vstring_free(state->message);
+ vstring_free(state->buf);
+ qmqpd_peer_reset(state);
+ if (state->queue_id)
+ myfree(state->queue_id);
+ if (state->dest)
+ mail_stream_cleanup(state->dest);
+ if (state->sender)
+ myfree(state->sender);
+ if (state->recipient)
+ myfree(state->recipient);
+ myfree((char *) state);
+}
/* Always send EHLO at the start of a connection.
/* .IP \fBsmtp_never_send_ehlo\fR
/* Never send EHLO at the start of a connection.
+/* .IP \fBsmtp_break_lines\fR
+/* Break lines > \fB$line_length_limit\fR into multiple shorter lines.
+/* Some SMTP servers misbehave on long lines.
/* .IP \fBsmtp_skip_4xx_greeting\fR
/* Skip servers that greet us with a 4xx status code.
/* .IP \fBsmtp_skip_5xx_greeting\fR
bool var_smtp_sasl_enable;
char *var_smtp_bind_addr;
bool var_smtp_rand_addr;
+bool var_smtp_break_lines;
/*
* Global variables. smtp_errno is set by the address lookup routines and by
VAR_SMTP_NEVER_EHLO, DEF_SMTP_NEVER_EHLO, &var_smtp_never_ehlo,
VAR_SMTP_SASL_ENABLE, DEF_SMTP_SASL_ENABLE, &var_smtp_sasl_enable,
VAR_SMTP_RAND_ADDR, DEF_SMTP_RAND_ADDR, &var_smtp_rand_addr,
+ VAR_SMTP_BREAK_LINES, DEF_SMTP_BREAK_LINES, &var_smtp_break_lines,
0,
};
#include <ctype.h>
#include <string.h>
#include <unistd.h>
+#include <errno.h>
#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif
+ /*
+ * Older systems don't have h_errno. Even modern systems don't have
+ * hstrerror().
+ */
+#ifdef NO_HERRNO
+
+static int h_errno = TRY_AGAIN;
+
+#define HSTRERROR(err) "Host not found"
+
+#else
+
+#define HSTRERROR(err) (\
+ err == TRY_AGAIN ? "Host not found, try again" : \
+ err == HOST_NOT_FOUND ? "Host not found" : \
+ err == NO_DATA ? "Host name has no address" : \
+ err == NO_RECOVERY ? "Name server failure" : \
+ strerror(errno) \
+ )
+#endif
+
/* Utility library. */
#include <msg.h>
if (var_disable_dns) {
memset((char *) &fixed, 0, sizeof(fixed));
if ((hp = gethostbyname(host)) == 0) {
- vstring_sprintf(why, "%s: host not found", host);
- smtp_errno = SMTP_FAIL;
+ vstring_sprintf(why, "%s: %s", host, HSTRERROR(h_errno));
+ smtp_errno = (h_errno == TRY_AGAIN ? SMTP_RETRY : SMTP_FAIL);
} else if (hp->h_addrtype != AF_INET) {
vstring_sprintf(why, "%s: host not found", host);
msg_warn("%s: unknown address family %d for %s",
char *save;
char *dest;
char *cp;
- int found_myself;
+ int found_myself = 0;
/*
* First try to deliver to the indicated destination, then try to deliver
*/
case SMTP_STATE_RCPT:
if (!mail_from_rejected) {
-#ifndef notRFC821_SYNTAX
+#ifdef notdef
if (resp->code == 552)
resp->code = 452;
#endif
if (prev_type != REC_TYPE_CONT)
if (vstring_str(state->scratch)[0] == '.')
smtp_fputc('.', session->stream);
+ if (var_smtp_break_lines)
+ rec_type = REC_TYPE_NORM;
if (rec_type == REC_TYPE_CONT)
smtp_fwrite(vstring_str(state->scratch),
VSTRING_LEN(state->scratch),
switch (priority) {
case SASL_LOG_ERR:
case SASL_LOG_WARNING:
- msg_warn("%s", message);
+ msg_warn("SASL authentication problem: %s", message);
break;
case SASL_LOG_INFO:
if (msg_verbose)
- msg_info("%s", message);
+ msg_info("SASL authentication info: %s", message);
break;
}
return (SASL_OK);
char *var_canonical_maps;
char *var_rcpt_canon_maps;
char *var_virtual_maps;
+char *var_virt_mailbox_maps;
char *var_relocated_maps;
char *var_alias_maps;
char *var_local_rcpt_maps;
VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0,
VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0,
VAR_VIRTUAL_MAPS, DEF_VIRTUAL_MAPS, &var_virtual_maps, 0, 0,
+ VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0,
VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0,
VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0,
VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0,
static MAPS *rcpt_canon_maps;
static MAPS *canonical_maps;
static MAPS *virtual_maps;
+static MAPS *virt_mailbox_maps;
static MAPS *relocated_maps;
/*
DICT_FLAG_LOCK);
virtual_maps = maps_create(VAR_VIRTUAL_MAPS, var_virtual_maps,
DICT_FLAG_LOCK);
+ virt_mailbox_maps = maps_create(VAR_VIRT_MAILBOX_MAPS, var_virt_mailbox_maps,
+ DICT_FLAG_LOCK);
relocated_maps = maps_create(VAR_RELOCATED_MAPS, var_relocated_maps,
DICT_FLAG_LOCK);
*
* We could eliminate the code duplication and implement the soft_bounce
* safety net only in the code below. But then the safety net would cover
- * the UCE restrictions only. This would be at odds with the documentation
+ * the UCE restrictions only. This would be at odds with documentation
* which says soft_bounce changes all 5xx replies into 4xx ones.
*/
if (var_soft_bounce && STR(error_text)[0] == '5')
*/
if (resolve_local(domain)
|| (*var_virtual_maps
- && check_maps_find(state, recipient, virtual_maps, domain, 0)))
+ && check_maps_find(state, recipient, virtual_maps, domain, 0))
+ || (*var_virt_mailbox_maps
+ && check_maps_find(state, recipient, virt_mailbox_maps, domain, 0)))
return (SMTPD_CHECK_OK);
/*
domain += 1;
if (resolve_local(domain)
|| (*var_virtual_maps
- && check_maps_find(state, recipient, virtual_maps, domain, 0)))
+ && check_maps_find(state, recipient, virtual_maps, domain, 0))
+ || (*var_virt_mailbox_maps
+ && check_maps_find(state, recipient, virt_mailbox_maps, domain, 0)))
return (SMTPD_CHECK_OK);
if (msg_verbose)
domain += 1;
if (resolve_local(domain)
|| (*var_virtual_maps
- && check_maps_find(state, reply_name, virtual_maps, domain, 0)))
+ && check_maps_find(state, reply_name, virtual_maps, domain, 0))
+ || (*var_virt_mailbox_maps
+ && check_maps_find(state, reply_name, virt_mailbox_maps, domain, 0)))
return (SMTPD_CHECK_DUNNO);
if (domain[0] == '#')
return (SMTPD_CHECK_DUNNO);
if (NOMATCH(rcpt_canon_maps, STR(reply.recipient))
&& NOMATCH(canonical_maps, STR(reply.recipient))
&& NOMATCH(relocated_maps, STR(reply.recipient))
+ && NOMATCH(virt_mailbox_maps, STR(reply.recipient))
+ && NOMATCH(virtual_maps, STR(reply.recipient))) {
+ (void) smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
+ "%d <%s>: User unknown", 550, recipient);
+ SMTPD_CHECK_RCPT_RETURN(STR(error_text));
+ }
+ }
+
+ /*
+ * Reject mail to unknown addresses in Postfix-style virtual domains.
+ */
+ if (*var_virt_mailbox_maps
+ && (check_maps_find(state, recipient, virt_mailbox_maps, domain, 0))) {
+ if (NOMATCH(rcpt_canon_maps, STR(reply.recipient))
+ && NOMATCH(canonical_maps, STR(reply.recipient))
+ && NOMATCH(relocated_maps, STR(reply.recipient))
+ && NOMATCH(virt_mailbox_maps, STR(reply.recipient))
&& NOMATCH(virtual_maps, STR(reply.recipient))) {
(void) smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
"%d <%s>: User unknown", 550, recipient);
if (NOMATCH(rcpt_canon_maps, STR(reply.recipient))
&& NOMATCH(canonical_maps, STR(reply.recipient))
&& NOMATCH(relocated_maps, STR(reply.recipient))
+ && NOMATCH(virt_mailbox_maps, STR(reply.recipient))
&& NOMATCH(virtual_maps, STR(reply.recipient))
&& NOMATCH(local_rcpt_maps, STR(reply.recipient))) {
(void) smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
char *var_rcpt_canon_maps;
char *var_canonical_maps;
char *var_virtual_maps;
+char *var_virt_mailbox_maps;
char *var_relocated_maps;
char *var_local_rcpt_maps;
VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps,
VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps,
VAR_VIRTUAL_MAPS, DEF_VIRTUAL_MAPS, &var_virtual_maps,
+ VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps,
VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps,
VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps,
0,
resp = 0;
break;
}
+ if (strcasecmp(args->argv[0], VAR_VIRT_MAILBOX_MAPS) == 0) {
+ UPDATE_STRING(var_virt_mailbox_maps, args->argv[1]);
+ UPDATE_MAPS(virt_mailbox_maps, VAR_VIRT_MAILBOX_MAPS,
+ var_virt_mailbox_maps, DICT_FLAG_LOCK);
+ resp = 0;
+ break;
+ }
if (strcasecmp(args->argv[0], "local_recipient_maps") == 0) {
UPDATE_STRING(var_local_rcpt_maps, args->argv[1]);
UPDATE_MAPS(local_rcpt_maps, VAR_LOCAL_RCPT_MAPS,
* If peer went away, give up.
*/
if (errno == ECONNRESET || errno == ECONNABORTED) {
- msg_info("errno %d %m", errno);
state->name = mystrdup("unknown");
state->addr = mystrdup("unknown");
state->peer_code = 5;
switch (priority) {
case SASL_LOG_ERR:
case SASL_LOG_WARNING:
- msg_warn("%s", message);
+ msg_warn("SASL authentication problem: %s", message);
break;
case SASL_LOG_INFO:
if (msg_verbose)
- msg_info("%s", message);
+ msg_info("SASL authentication info: %s", message);
break;
}
return SASL_OK;
SHELL = /bin/sh
-SRCS = smtp-source.c smtp-sink.c
-OBJS = smtp-source.o smtp-sink.o
+SRCS = smtp-source.c smtp-sink.c qmqp-source.c qmqp-sink.c
+OBJS = smtp-source.o smtp-sink.o qmqp-source.o qmqp-sink.o
HDRS =
TESTSRC =
WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \
CFLAGS = $(DEBUG) $(OPT) $(DEFS)
TESTPROG=
INC_DIR = ../../include
-PROG = smtp-source smtp-sink
+PROG = smtp-source smtp-sink qmqp-source qmqp-sink
LIBS = ../../lib/libglobal.a ../../lib/libutil.a
.c.o:; $(CC) $(CFLAGS) -c $*.c
smtp-source: smtp-source.o $(LIBS)
$(CC) $(CFLAGS) -o $@ smtp-source.o $(LIBS) $(SYSLIBS)
+qmqp-sink: qmqp-sink.o $(LIBS)
+ $(CC) $(CFLAGS) -o $@ qmqp-sink.o $(LIBS) $(SYSLIBS)
+
+qmqp-source: qmqp-source.o $(LIBS)
+ $(CC) $(CFLAGS) -o $@ qmqp-source.o $(LIBS) $(SYSLIBS)
+
test: $(TESTPROG)
-update: ../../bin/smtp-source ../../bin/smtp-sink
+update: ../../bin/smtp-source ../../bin/smtp-sink ../../bin/qmqp-source
../../bin/smtp-source: smtp-source
cp $? $@
../../bin/smtp-sink: smtp-sink
cp $? $@
+../../bin/qmqp-source: qmqp-source
+ cp $? $@
+
+../../bin/qmqp-sink: qmqp-sink
+ cp $? $@
+
printfck: $(OBJS) $(PROG)
rm -rf printfck
mkdir printfck
@$(EXPORT) make -f Makefile.in Makefile 1>&2
# do not edit below this line - it is generated by 'make depend'
+qmqp-sink.o: qmqp-sink.c
+qmqp-sink.o: ../../include/sys_defs.h
+qmqp-sink.o: ../../include/msg.h
+qmqp-sink.o: ../../include/vstring.h
+qmqp-sink.o: ../../include/vbuf.h
+qmqp-sink.o: ../../include/vstream.h
+qmqp-sink.o: ../../include/listen.h
+qmqp-sink.o: ../../include/iostuff.h
+qmqp-sink.o: ../../include/events.h
+qmqp-sink.o: ../../include/mymalloc.h
+qmqp-sink.o: ../../include/msg_vstream.h
+qmqp-sink.o: ../../include/netstring.h
+qmqp-sink.o: ../../include/qmqp_proto.h
+qmqp-source.o: qmqp-source.c
+qmqp-source.o: ../../include/sys_defs.h
+qmqp-source.o: ../../include/msg.h
+qmqp-source.o: ../../include/msg_vstream.h
+qmqp-source.o: ../../include/vstream.h
+qmqp-source.o: ../../include/vbuf.h
+qmqp-source.o: ../../include/vstring.h
+qmqp-source.o: ../../include/get_hostname.h
+qmqp-source.o: ../../include/split_at.h
+qmqp-source.o: ../../include/connect.h
+qmqp-source.o: ../../include/iostuff.h
+qmqp-source.o: ../../include/mymalloc.h
+qmqp-source.o: ../../include/events.h
+qmqp-source.o: ../../include/find_inet.h
+qmqp-source.o: ../../include/netstring.h
+qmqp-source.o: ../../include/mail_date.h
+qmqp-source.o: ../../include/qmqp_proto.h
smtp-sink.o: smtp-sink.c
smtp-sink.o: ../../include/sys_defs.h
smtp-sink.o: ../../include/msg.h
--- /dev/null
+/*++
+/* NAME
+/* qmqp-sink 8
+/* SUMMARY
+/* multi-threaded QMQP test server
+/* SYNOPSIS
+/* .fi
+/* \fBqmqp-sink\fR [\fB-cv\fR] [\fB-x \fItime\fR]
+/* [\fBinet:\fR][\fIhost\fR]:\fIport\fR \fIbacklog\fR
+/*
+/* \fBqmqp-sink\fR [\fB-cv\fR]
+/* \fBunix:\fR\fIpathname\fR \fIbacklog\fR
+/* DESCRIPTION
+/* \fIqmqp-sink\fR listens on the named host (or address) and port.
+/* It receives messages from the network and throws them away.
+/* The purpose is to measure QMQP client performance, not protocol
+/* compliance.
+/* Connections can be accepted on IPV4 endpoints or UNIX-domain sockets.
+/* IPV4 is the default.
+/* This program is the complement of the \fIqmqp-source\fR program.
+/* .IP \fB-c\fR
+/* Display a running counter that is updated whenever a delivery
+/* is completed.
+/* .IP \fB-v\fR
+/* Increase verbosity. Specify \fB-v -v\fR to see some of the QMQP
+/* conversation.
+/* .IP "\fB-x \fItime\fR
+/* Terminate after \fItime\fR seconds. This is to facilitate memory
+/* leak testing.
+/* SEE ALSO
+/* qmqp-source, QMQP test message generator
+/* 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 <sys/socket.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <listen.h>
+#include <events.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <msg_vstream.h>
+#include <netstring.h>
+
+/* Global library. */
+
+#include <qmqp_proto.h>
+
+/* Application-specific. */
+
+typedef struct {
+ VSTREAM *stream; /* client connection */
+ int count; /* bytes to go */
+} SINK_STATE;
+
+static int var_tmout;
+static VSTRING *buffer;
+static void disconnect(SINK_STATE *);
+static int count;
+static int counter;
+
+/* send_reply - finish conversation */
+
+static void send_reply(SINK_STATE *state)
+{
+ vstring_sprintf(buffer, "%cOk", QMQP_STAT_OK);
+ NETSTRING_PUT_BUF(state->stream, buffer);
+ netstring_fflush(state->stream);
+ if (count) {
+ counter++;
+ vstream_printf("%d\r", counter);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ disconnect(state);
+}
+
+/* read_data - read over-all netstring data */
+
+static void read_data(int unused_event, char *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+ int fd = vstream_fileno(state->stream);
+ int count;
+
+ /*
+ * Refill the VSTREAM buffer, if necessary.
+ */
+ if (VSTREAM_GETC(state->stream) == VSTREAM_EOF)
+ netstring_except(state->stream, vstream_ftimeout(state->stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+ state->count--;
+
+ /*
+ * Flush the VSTREAM buffer. As documented, vstream_fseek() discards
+ * unread input.
+ */
+ if ((count = vstream_peek(state->stream)) > 0) {
+ state->count -= count;
+ if (state->count <= 0) {
+ send_reply(state);
+ return;
+ }
+ vstream_fseek(state->stream, 0L, 0);
+ }
+
+ /*
+ * Do not block while waiting for the arrival of more data.
+ */
+ event_disable_readwrite(fd);
+ event_enable_read(fd, read_data, context);
+}
+
+/* read_length - read over-all netstring length */
+
+static void read_length(int event, char *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+
+ switch (vstream_setjmp(state->stream)) {
+
+ default:
+ msg_panic("unknown error reading input");
+
+ case NETSTRING_ERR_TIME:
+ msg_panic("attempt to read non-readable socket");
+ /* NOTREACHED */
+
+ case NETSTRING_ERR_EOF:
+ msg_warn("lost connection");
+ disconnect(state);
+ return;
+
+ case NETSTRING_ERR_FORMAT:
+ msg_warn("netstring format error");
+ disconnect(state);
+ return;
+
+ case NETSTRING_ERR_SIZE:
+ msg_warn("netstring size error");
+ disconnect(state);
+ return;
+
+ /*
+ * Include the netstring terminator in the read byte count. This
+ * violates abstractions.
+ */
+ case 0:
+ state->count = netstring_get_length(state->stream) + 1;
+ read_data(event, context);
+ return;
+ }
+}
+
+/* disconnect - handle disconnection events */
+
+static void disconnect(SINK_STATE *state)
+{
+ event_disable_readwrite(vstream_fileno(state->stream));
+ vstream_fclose(state->stream);
+ myfree((char *) state);
+}
+
+/* connect_event - handle connection events */
+
+static void connect_event(int unused_event, char *context)
+{
+ int sock = CAST_CHAR_PTR_TO_INT(context);
+ struct sockaddr sa;
+ SOCKADDR_SIZE len = sizeof(sa);
+ SINK_STATE *state;
+ int fd;
+
+ if ((fd = accept(sock, &sa, &len)) >= 0) {
+ if (msg_verbose)
+ msg_info("connect (%s)",
+#ifdef AF_LOCAL
+ sa.sa_family == AF_LOCAL ? "AF_LOCAL" :
+#else
+ sa.sa_family == AF_UNIX ? "AF_UNIX" :
+#endif
+ sa.sa_family == AF_INET ? "AF_INET" :
+#ifdef AF_INET6
+ sa.sa_family == AF_INET6 ? "AF_INET6" :
+#endif
+ "unknown protocol family");
+ non_blocking(fd, NON_BLOCKING);
+ state = (SINK_STATE *) mymalloc(sizeof(*state));
+ state->stream = vstream_fdopen(fd, O_RDWR);
+ netstring_setup(state->stream, var_tmout);
+ event_enable_read(fd, read_length, (char *) state);
+ }
+}
+
+/* terminate - voluntary exit */
+
+static void terminate(int unused_event, char *unused_context)
+{
+ exit(0);
+}
+
+/* usage - explain */
+
+static void usage(char *myname)
+{
+ msg_fatal("usage: %s [-cv] [-x time] [host]:port backlog", myname);
+}
+
+int main(int argc, char **argv)
+{
+ int sock;
+ int backlog;
+ int ch;
+ int ttl;
+
+ /*
+ * Initialize diagnostics.
+ */
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "cvx:")) > 0) {
+ switch (ch) {
+ case 'c':
+ count++;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'x':
+ if ((ttl = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ event_request_timer(terminate, (char *) 0, ttl);
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc - optind != 2)
+ usage(argv[0]);
+ if ((backlog = atoi(argv[optind + 1])) <= 0)
+ usage(argv[0]);
+
+ /*
+ * Initialize.
+ */
+ buffer = vstring_alloc(1024);
+ if (strncmp(argv[optind], "unix:", 5) == 0) {
+ sock = unix_listen(argv[optind] + 5, backlog, BLOCKING);
+ } else {
+ if (strncmp(argv[optind], "inet:", 5) == 0)
+ argv[optind] += 5;
+ sock = inet_listen(argv[optind], backlog, BLOCKING);
+ }
+
+ /*
+ * Start the event handler.
+ */
+ event_enable_read(sock, connect_event, CAST_INT_TO_CHAR_PTR(sock));
+ for (;;)
+ event_loop(-1);
+}
--- /dev/null
+/*++
+/* NAME
+/* qmqp-source 8
+/* SUMMARY
+/* multi-threaded QMQP test generator
+/* SYNOPSIS
+/* .fi
+/* \fBqmqp-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR]
+/*
+/* \fBqmqp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR
+/* DESCRIPTION
+/* qmqp-source connects to the named host and TCP port (default 628)
+/* and sends one or more messages to it, either sequentially
+/* or in parallel. The program speaks the QMQP protocol.
+/* Connections can be made to UNIX-domain and IPV4 servers.
+/* IPV4 is the default.
+/*
+/* Options:
+/* .IP -c
+/* Display a running counter that is incremented each time
+/* a delivery completes.
+/* .IP "-C count"
+/* When a host sends RESET instead of SYN|ACK, try \fIcount\fR times
+/* before giving up. The default count is 1. Specify a larger count in
+/* order to work around a problem with TCP/IP stacks that send RESET
+/* when the listen queue is full.
+/* .IP "-f from"
+/* Use the specified sender address (default: <foo@myhostname>).
+/* .IP "-l length"
+/* Send \fIlength\fR bytes as message payload. The length
+/* includes the message headers.
+/* .IP "-m message_count"
+/* Send the specified number of messages (default: 1).
+/* .IP "-r recipient_count"
+/* Send the specified number of recipients per transaction (default: 1).
+/* Recipient names are generated by prepending a number to the
+/* recipient address.
+/* .IP "-s session_count"
+/* Run the specified number of QMQP sessions in parallel (default: 1).
+/* .IP "-t to"
+/* Use the specified recipient address (default: <foo@myhostname>).
+/* .IP "-R interval"
+/* Wait for a random period of time 0 <= n <= interval between messages.
+/* Suspending one thread does not affect other delivery threads.
+/* .IP "-w interval"
+/* Wait a fixed time between messages.
+/* Suspending one thread does not affect other delivery threads.
+/* 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 <sys/socket.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <get_hostname.h>
+#include <split_at.h>
+#include <connect.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <find_inet.h>
+#include <iostuff.h>
+#include <netstring.h>
+
+/* Global library. */
+
+#include <mail_date.h>
+#include <qmqp_proto.h>
+
+/* Application-specific. */
+
+ /*
+ * Per-session data structure with state.
+ *
+ * This software can maintain multiple parallel connections to the same QMQP
+ * server. However, it makes no more than one connection request at a time
+ * to avoid overwhelming the server with SYN packets and having to back off.
+ * Back-off would screw up the benchmark. Pending connection requests are
+ * kept in a linear list.
+ */
+typedef struct SESSION {
+ int xfer_count; /* # of xfers in session */
+ int rcpt_done; /* # of recipients done */
+ int rcpt_count; /* # of recipients to go */
+ VSTREAM *stream; /* open connection */
+ int connect_count; /* # of connect()s to retry */
+ struct SESSION *next; /* connect() queue linkage */
+} SESSION;
+
+static SESSION *last_session; /* connect() queue tail */
+
+static VSTRING *buffer;
+static int var_line_limit = 10240;
+static int var_timeout = 300;
+static const char *var_myhostname;
+static int session_count;
+static int message_count = 1;
+static struct sockaddr_in sin;
+
+#undef sun
+static struct sockaddr_un sun;
+static struct sockaddr *sa;
+static int sa_length;
+static int recipients = 1;
+static char *defaddr;
+static char *recipient;
+static char *sender;
+static int message_length = 1024;
+static int count = 0;
+static int counter = 0;
+static int connect_count = 1;
+static int random_delay = 0;
+static int fixed_delay = 0;
+static const char *mydate;
+static int mypid;
+
+static void enqueue_connect(SESSION *);
+static void start_connect(SESSION *);
+static void connect_done(int, char *);
+
+static void send_data(SESSION *);
+static void receive_reply(int, char *);
+
+static VSTRING *message_buffer;
+static VSTRING *sender_buffer;
+static VSTRING *recipient_buffer;
+
+/* Silly little macros. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* random_interval - generate a random value in 0 .. (small) interval */
+
+static int random_interval(int interval)
+{
+ return (rand() % (interval + 1));
+}
+
+/* socket_error - look up and reset the last socket error */
+
+static int socket_error(int sock)
+{
+ int error;
+ SOCKOPT_SIZE error_len;
+
+ /*
+ * Some Solaris 2 versions have getsockopt() itself return the error,
+ * instead of returning it via the parameter list.
+ */
+ error = 0;
+ error_len = sizeof(error);
+ if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *) &error, &error_len) < 0)
+ return (-1);
+ if (error) {
+ errno = error;
+ return (-1);
+ }
+
+ /*
+ * No problems.
+ */
+ return (0);
+}
+
+/* exception_text - translate exceptions from the netstring module */
+
+static char *exception_text(int except)
+{
+ ;
+
+ switch (except) {
+ case NETSTRING_ERR_EOF:
+ return ("lost connection");
+ case NETSTRING_ERR_TIME:
+ return ("timeout");
+ case NETSTRING_ERR_FORMAT:
+ return ("netstring format error");
+ case NETSTRING_ERR_SIZE:
+ return ("netstring size exceeds limit");
+ default:
+ msg_panic("exception_text: unknown exception %d", except);
+ }
+ /* NOTREACHED */
+}
+
+/* startup - connect to server but do not wait */
+
+static void startup(SESSION *session)
+{
+ if (message_count-- <= 0) {
+ myfree((char *) session);
+ session_count--;
+ return;
+ }
+ enqueue_connect(session);
+}
+
+/* start_event - invoke startup from timer context */
+
+static void start_event(int unused_event, char *context)
+{
+ SESSION *session = (SESSION *) context;
+
+ startup(session);
+}
+
+/* start_another - start another session */
+
+static void start_another(SESSION *session)
+{
+ if (random_delay > 0) {
+ event_request_timer(start_event, (char *) session,
+ random_interval(random_delay));
+ } else if (fixed_delay > 0) {
+ event_request_timer(start_event, (char *) session, fixed_delay);
+ } else {
+ startup(session);
+ }
+}
+
+/* enqueue_connect - queue a connection request */
+
+static void enqueue_connect(SESSION *session)
+{
+ session->next = 0;
+ if (last_session == 0) {
+ last_session = session;
+ start_connect(session);
+ } else {
+ last_session->next = session;
+ last_session = session;
+ }
+}
+
+/* dequeue_connect - connection request completed */
+
+static void dequeue_connect(SESSION *session)
+{
+ if (session == last_session) {
+ if (session->next != 0)
+ msg_panic("dequeue_connect: queue ends after last");
+ last_session = 0;
+ } else {
+ if (session->next == 0)
+ msg_panic("dequeue_connect: queue ends before last");
+ start_connect(session->next);
+ }
+}
+
+/* fail_connect - handle failed startup */
+
+static void fail_connect(SESSION *session)
+{
+ if (session->connect_count-- == 1)
+ msg_fatal("connect: %m");
+ msg_warn("connect: %m");
+ event_disable_readwrite(vstream_fileno(session->stream));
+ vstream_fclose(session->stream);
+ session->stream = 0;
+#ifdef MISSING_USLEEP
+ doze(10);
+#else
+ usleep(10);
+#endif
+ start_connect(session);
+}
+
+/* start_connect - start TCP handshake */
+
+static void start_connect(SESSION *session)
+{
+ int fd;
+
+ /*
+ * Some systems don't set the socket error when connect() fails early
+ * (loopback) so we must deal with the error immediately, rather than
+ * retrieving it later with getsockopt(). We can't use MSG_PEEK to
+ * distinguish between server disconnect and connection refused.
+ */
+ if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
+ msg_fatal("socket: %m");
+ (void) non_blocking(fd, NON_BLOCKING);
+ session->stream = vstream_fdopen(fd, O_RDWR);
+ event_enable_write(fd, connect_done, (char *) session);
+ netstring_setup(session->stream, var_timeout);
+ if (connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS)
+ fail_connect(session);
+}
+
+/* connect_done - send message sender info */
+
+static void connect_done(int unused_event, char *context)
+{
+ SESSION *session = (SESSION *) context;
+ int fd = vstream_fileno(session->stream);
+
+ /*
+ * Try again after some delay when the connection failed, in case they
+ * run a Mickey Mouse protocol stack.
+ */
+ if (socket_error(fd) < 0) {
+ fail_connect(session);
+ } else {
+ dequeue_connect(session);
+ non_blocking(fd, BLOCKING);
+ event_disable_readwrite(fd);
+ send_data(session);
+ }
+}
+
+/* send_data - send message+sender+recipients */
+
+static void send_data(SESSION *session)
+{
+ int fd = vstream_fileno(session->stream);
+ int except;
+
+ /*
+ * Prepare for disaster.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending message", exception_text(except));
+
+ /*
+ * Send the message content, by wrapping three netstrings into an
+ * over-all netstring.
+ *
+ * XXX This should be done more carefully to avoid blocking when sending
+ * large messages over slow networks.
+ */
+ netstring_put_multi(session->stream,
+ STR(message_buffer), LEN(message_buffer),
+ STR(sender_buffer), LEN(sender_buffer),
+ STR(recipient_buffer), LEN(recipient_buffer),
+ 0);
+ netstring_fflush(session->stream);
+
+ /*
+ * Wake me up when the server replies or when something bad happens.
+ */
+ event_enable_read(fd, receive_reply, (char *) session);
+}
+
+/* receive_reply - read server reply */
+
+static void receive_reply(int unused_event, char *context)
+{
+ SESSION *session = (SESSION *) context;
+ int except;
+
+ /*
+ * Prepare for disaster.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while receiving server reply", exception_text(except));
+
+ /*
+ * Receive and process the server reply.
+ */
+ netstring_get(session->stream, buffer, var_line_limit);
+ if (msg_verbose)
+ vstream_printf("<< %.*s\n", LEN(buffer), STR(buffer));
+ if (STR(buffer)[0] != QMQP_STAT_OK)
+ msg_fatal("%s error: %.*s",
+ STR(buffer)[0] == QMQP_STAT_RETRY ? "recoverable" :
+ STR(buffer)[0] == QMQP_STAT_HARD ? "unrecoverable" :
+ "unknown", LEN(buffer) - 1, STR(buffer) + 1);
+
+ /*
+ * Update the optional running counter.
+ */
+ if (count) {
+ counter++;
+ vstream_printf("%d\r", counter);
+ vstream_fflush(VSTREAM_OUT);
+ }
+
+ /*
+ * Finish this session. QMQP sends only one message per session.
+ */
+ event_disable_readwrite(vstream_fileno(session->stream));
+ vstream_fclose(session->stream);
+ session->stream = 0;
+ start_another(session);
+}
+
+/* usage - explain */
+
+static void usage(char *myname)
+{
+ msg_fatal("usage: %s -s sess -l msglen -m msgs -c -C count -f from -t to -R delay -v -w delay host[:port]", myname);
+}
+
+/* main - parse JCL and start the machine */
+
+int main(int argc, char **argv)
+{
+ SESSION *session;
+ char *host;
+ char *port;
+ char *path;
+ int path_len;
+ int sessions = 1;
+ int ch;
+ int n;
+ int i;
+
+ signal(SIGPIPE, SIG_IGN);
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "cC:f:l:m:r:R:s:t:vw:")) > 0) {
+ switch (ch) {
+ case 'c':
+ count++;
+ break;
+ case 'C':
+ if ((connect_count = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 'f':
+ sender = optarg;
+ break;
+ case 'l':
+ if ((message_length = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 'm':
+ if ((message_count = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 'r':
+ if ((recipients = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 'R':
+ if (fixed_delay > 0 || (random_delay = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 's':
+ if ((sessions = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 't':
+ recipient = optarg;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'w':
+ if (random_delay > 0 || (fixed_delay = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc - optind != 1)
+ usage(argv[0]);
+
+ if (random_delay > 0)
+ srand(getpid());
+
+ /*
+ * Translate endpoint address to internal form.
+ */
+ if (strncmp(argv[optind], "unix:", 5) == 0) {
+ path = argv[optind] + 5;
+ path_len = strlen(path);
+ if (path_len >= (int) sizeof(sun.sun_path))
+ msg_fatal("unix-domain name too long: %s", path);
+ memset((char *) &sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sun.sun_len = path_len + 1;
+#endif
+ memcpy(sun.sun_path, path, path_len);
+ sa = (struct sockaddr *) & sun;
+ sa_length = sizeof(sun);
+ } else {
+ if (strncmp(argv[optind], "inet:", 5) == 0)
+ argv[optind] += 5;
+ if ((port = split_at(host = argv[optind], ':')) == 0 || *port == 0)
+ port = "628";
+ memset((char *) &sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = find_inet_addr(host);
+ sin.sin_port = find_inet_port(port, "tcp");
+ sa = (struct sockaddr *) & sin;
+ sa_length = sizeof(sin);
+ }
+
+ /*
+ * Allocate space for temporary buffer.
+ */
+ buffer = vstring_alloc(100);
+
+ /*
+ * Make sure we have sender and recipient addresses.
+ */
+ var_myhostname = get_hostname();
+ if (sender == 0 || recipient == 0) {
+ vstring_sprintf(buffer, "foo@%s", var_myhostname);
+ defaddr = mystrdup(vstring_str(buffer));
+ if (sender == 0)
+ sender = defaddr;
+ if (recipient == 0)
+ recipient = defaddr;
+ }
+
+ /*
+ * Prepare some results that may be used multiple times: the message
+ * content netstring, the sender netstring, and the recipient netstrings.
+ */
+ mydate = mail_date(time((time_t *) 0));
+ mypid = getpid();
+
+ message_buffer = vstring_alloc(message_length + 200);
+ vstring_sprintf(buffer,
+ "From: <%s>\nTo: <%s>\nDate: %s\nMessage-Id: <%d@%s>\n\n",
+ sender, recipient, mydate, mypid, var_myhostname);
+ for (n = 1; LEN(buffer) < message_length; n++) {
+ for (i = 0; i < n && i < 79; i++)
+ VSTRING_ADDCH(buffer, 'X');
+ VSTRING_ADDCH(buffer, '\n');
+ }
+ netstring_memcpy(message_buffer, STR(buffer), message_length);
+
+ n = strlen(sender);
+ sender_buffer = vstring_alloc(n);
+ netstring_memcpy(sender_buffer, sender, n);
+
+ if (recipients == 1) {
+ n = strlen(recipient);
+ recipient_buffer = vstring_alloc(n);
+ netstring_memcpy(recipient_buffer, recipient, n);
+ } else {
+ recipient_buffer = vstring_alloc(100);
+ for (n = 0; n < recipients; n++) {
+ vstring_sprintf(buffer, "%d%s", n, recipient);
+ netstring_memcat(recipient_buffer, STR(buffer), LEN(buffer));
+ }
+ }
+
+ /*
+ * Start sessions.
+ */
+ while (sessions-- > 0) {
+ session = (SESSION *) mymalloc(sizeof(*session));
+ session->stream = 0;
+ session->xfer_count = 0;
+ session->connect_count = connect_count;
+ session->next = 0;
+ session_count++;
+ startup(session);
+ }
+ for (;;) {
+ event_loop(-1);
+ if (session_count <= 0 && message_count <= 0) {
+ if (count) {
+ VSTREAM_PUTC('\n', VSTREAM_OUT);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ exit(0);
+ }
+ }
+}
/*
/* \fBsmtp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR
/* DESCRIPTION
-/* smtp-source connects to the named host and port (default 25)
-/* and sends one or more little messages to it, either sequentially
+/* smtp-source connects to the named host and TCP port (default port 25)
+/* and sends one or more messages to it, either sequentially
/* or in parallel. The program speaks either SMTP (default) or
/* LMTP. Connections can be made to UNIX-domain and IPV4 servers.
/* IPV4 is the default.
/* .IP -o
/* Old mode: don't send HELO, and don't send message headers.
/* .IP "-l length"
-/* Send \fIlength\fR bytes as message payload.
+/* Send \fIlength\fR bytes as message payload. The length does not
+/* include message headers.
/* .IP -L
/* Speak LMTP rather than SMTP.
/* .IP "-m message_count"
/* .IP "-r recipient_count"
/* Send the specified number of recipients per transaction (default: 1).
/* Recipient names are generated by prepending a number to the
-/* recipient address. The default is one recipient per transaction.
+/* recipient address.
/* .IP "-s session_count"
/* Run the specified number of SMTP sessions in parallel (default: 1).
/* .IP "-t to"
message_data = mymalloc(message_length);
memset(message_data, 'X', message_length);
for (i = 80; i < message_length; i += 80) {
- message_data[i - 80] = "0123456789"[(i/80) % 10];
+ message_data[i - 80] = "0123456789"[(i / 80) % 10];
message_data[i - 2] = '\r';
message_data[i - 1] = '\n';
}
} else {
if (strncmp(argv[optind], "inet:", 5) == 0)
argv[optind] += 5;
- if ((port = split_at(host = argv[optind], ':')) == 0)
+ if ((port = split_at(host = argv[optind], ':')) == 0 || *port == 0)
port = "smtp";
memset((char *) &sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
clean_env.c watchdog.c spawn_command.c duplex_pipe.c sane_rename.c \
sane_link.c unescape.c timed_read.c timed_write.c dict_tcp.c \
hex_quote.c dict_alloc.c rand_sleep.c sane_time.c dict_debug.c \
- sane_socketpair.c myrand.c
+ sane_socketpair.c myrand.c netstring.c
OBJS = argv.o argv_split.o attr.o basename.o binhash.o chroot_uid.o \
close_on_exec.o concatenate.o dict.o dict_db.o dict_dbm.o \
dict_env.o dict_ht.o dict_ldap.o dict_mysql.o dict_ni.o dict_nis.o \
clean_env.o watchdog.o spawn_command.o duplex_pipe.o sane_rename.o \
sane_link.o unescape.o timed_read.o timed_write.o dict_tcp.o \
hex_quote.o dict_alloc.o rand_sleep.o sane_time.o dict_debug.o \
- sane_socketpair.o myrand.o
+ sane_socketpair.o myrand.o netstring.o
HDRS = argv.h attr.h binhash.h chroot_uid.h connect.h dict.h dict_db.h \
dict_dbm.h dict_env.h dict_ht.h dict_ldap.h dict_mysql.h \
dict_ni.h dict_nis.h dict_nisplus.h dir_forest.h events.h \
vbuf.h vbuf_print.h vstream.h vstring.h vstring_vstream.h \
dict_unix.h dict_pcre.h dict_regexp.h mac_expand.h clean_env.h \
watchdog.h spawn_command.h sane_fsops.h dict_tcp.h hex_quote.h \
- sane_time.h sane_socketpair.h myrand.h
+ sane_time.h sane_socketpair.h myrand.h netstring.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 \
-Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \
- -Wunused
+ -Wunused
DEFS = -I. -D$(SYSTYPE)
CFLAGS = $(DEBUG) $(OPT) $(DEFS)
FILES = Makefile $(SRCS) $(HDRS)
name_mask.o: vstring.h
name_mask.o: vbuf.h
name_mask.o: name_mask.h
+netstring.o: netstring.c
+netstring.o: sys_defs.h
+netstring.o: msg.h
+netstring.o: vstream.h
+netstring.o: vbuf.h
+netstring.o: vstring.h
+netstring.o: netstring.h
non_blocking.o: non_blocking.c
non_blocking.o: sys_defs.h
non_blocking.o: msg.h
if (res == 0 && host->stat == STATACTIVE) {
if (!(mysql_query(host->db, query))) {
if ((res = mysql_store_result(host->db)) == 0) {
- msg_warn("%s", mysql_error(host->db));
+ msg_warn("mysql query failed: %s", mysql_error(host->db));
plmysql_down_host(host);
} else {
if (msg_verbose)
msg_info("dict_mysql: successful query from host %s", host->hostname);
}
} else {
- msg_warn("%s", mysql_error(host->db));
+ msg_warn("mysql query failed: %s", mysql_error(host->db));
plmysql_down_host(host);
}
}
host->hostname);
host->stat = STATACTIVE;
} else {
- msg_warn("%s", mysql_error(host->db));
+ msg_warn("connect to mysql server %s: %s",
+ host->hostname, mysql_error(host->db));
plmysql_down_host(host);
}
if (hostname)
if (dict_mysql->pldb == NULL)
msg_fatal("couldn't intialize pldb!\n");
dict_register(name, (DICT *) dict_mysql);
- return (DICT_DEBUG(&dict_mysql->dict));
+ return (DICT_DEBUG (&dict_mysql->dict));
}
/* mysqlname_parse - parse mysql configuration file */
char *mystrndup(const char *str, int len)
{
char *result;
- int slen;
+ char *cp;
- if ((slen = strlen(str)) < len)
- len = slen;
+ if ((cp = memchr(str, 0, len)) != 0)
+ len = cp - str;
result = memcpy(mymalloc(len + 1), str, len);
result[len] = 0;
return (result);
--- /dev/null
+/*++
+/* NAME
+/* netstring 3
+/* SUMMARY
+/* netstring stream I/O support
+/* SYNOPSIS
+/* #include <netstring.h>
+/*
+/* void netstring_setup(stream, timeout)
+/* VSTREAM *stream;
+/* int timeout;
+/*
+/* void netstring_except(stream, exception)
+/* VSTREAM *stream;
+/* int exception;
+/*
+/* VSTRING *netstring_get(stream, buf, limit)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* int limit;
+/*
+/* void netstring_put(stream, data, len)
+/* VSTREAM *stream;
+/* const char *data;
+/* int len;
+/*
+/* void netstring_put_multi(stream, data, len, data, len, ..., 0)
+/* VSTREAM *stream;
+/* const char *data;
+/* int len;
+/*
+/* void NETSTRING_PUT_BUF(stream, buf)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/*
+/* void netstring_fflush(stream)
+/* VSTREAM *stream;
+/*
+/* VSTRING *netstring_memcpy(buf, data, len)
+/* VSTRING *buf;
+/* const char *data;
+/* int len;
+/*
+/* VSTRING *netstring_memcat(buf, data, len)
+/* VSTRING *buf;
+/* const char *src;
+/* int len;
+/* AUXILIARY ROUTINES
+/* int netstring_get_length(stream)
+/* VSTREAM *stream;
+/*
+/* VSTRING *netstring_get_data(stream, buf, len)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* int len;
+/*
+/* void netstring_get_terminator(stream)
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* This module reads and writes netstrings with error detection:
+/* timeouts, unexpected end-of-file, or format errors. Netstring
+/* is a data format designed by Daniel Bernstein.
+/*
+/* netstring_setup() arranges for a time limit on the netstring
+/* read and write operations described below.
+/* This routine alters the behavior of streams as follows:
+/* .IP \(bu
+/* The read/write timeout is set to the specified value.
+/* .IP \(bu
+/* The stream is configured to enable exception handling.
+/* .PP
+/* netstring_except() raises the specified exception on the
+/* named stream. See the DIAGNOSTICS section below.
+/*
+/* netstring_get() reads a netstring from the specified stream
+/* and extracts its content. The limit specifies a maximal size.
+/* Specify zero to disable the size limit. The result is not null
+/* terminated. The result value is the buf argument.
+/*
+/* netstring_put() encapsulates the specified string as a netstring
+/* and sends the result to the specified stream.
+/* The stream output buffer is not flushed.
+/*
+/* netstring_put_multi() encapsulates the content of multiple strings
+/* as one netstring and sends the result to the specified stream. The
+/* argument list must be terminated with a null data pointer.
+/* The stream output buffer is not flushed.
+/*
+/* NETSTRING_PUT_BUF() is a macro that provides a VSTRING-based
+/* wrapper for the netstring_put() routine.
+/*
+/* netstring_fflush() flushes the output buffer of the specified
+/* stream and handles any errors.
+/*
+/* netstring_memcpy() encapsulates the specified data as a netstring
+/* and copies the result over the specified buffer. The result
+/* value is the buffer.
+/*
+/* netstring_memcat() encapsulates the specified data as a netstring
+/* and appends the result to the specified buffer. The result
+/* value is the buffer.
+/*
+/* The following routines provide low-level access to a netstring
+/* stream.
+/*
+/* netstring_get_length() reads a length field from the specified
+/* stream, and absorbs the netstring length field terminator.
+/*
+/* netstring_get_data() reads the specified number of bytes from the
+/* specified stream into the specified buffer, and absorbs the
+/* netstring terminator. The result value is the buf argument.
+/*
+/* netstring_get_terminator() reads the netstring terminator from
+/* the specified stream.
+/* DIAGNOSTICS
+/* .fi
+/* .ad
+/* In case of error, a vstream_longjmp() call is performed to the
+/* context specified with vstream_setjmp().
+/* Error codes passed along with vstream_longjmp() are:
+/* .IP NETSTRING_ERR_EOF
+/* An I/O error happened, or the peer has disconnected unexpectedly.
+/* .IP NETSTRING_ERR_TIME
+/* The time limit specified to netstring_setup() was exceeded.
+/* .IP NETSTRING_ERR_FORMAT
+/* The input contains an unexpected character value.
+/* .IP NETSTRING_ERR_SIZE
+/* The input is larger than acceptable.
+/* BUGS
+/* The timeout deadline affects all I/O on the named stream, not
+/* just the I/O done on behalf of this module.
+/*
+/* The timeout deadline overwrites any previously set up state on
+/* the named stream.
+/*
+/* netstrings are not null terminated, which makes printing them
+/* a bit awkward.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* SEE ALSO
+/* http://cr.yp.to/proto/netstrings.txt, netstring definition
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <netstring.h>
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* netstring_setup - initialize netstring stream */
+
+void netstring_setup(VSTREAM *stream, int timeout)
+{
+ vstream_control(stream,
+ VSTREAM_CTL_TIMEOUT, timeout,
+ VSTREAM_CTL_EXCEPT,
+ VSTREAM_CTL_END);
+}
+
+/* netstring_except - process netstring stream exception */
+
+void netstring_except(VSTREAM *stream, int exception)
+{
+ vstream_longjmp(stream, exception);
+}
+
+/* netstring_get_length - read netstring length + terminator */
+
+int netstring_get_length(VSTREAM *stream)
+{
+ char *myname = "netstring_get_length";
+ int len = 0;
+ int ch;
+
+ for (;;) {
+ switch (ch = VSTREAM_GETC(stream)) {
+ case VSTREAM_EOF:
+ netstring_except(stream, vstream_ftimeout(stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+ case ':':
+ if (msg_verbose > 1)
+ msg_info("%s: read netstring length %d", myname, len);
+ return (len);
+ default:
+ if (!ISDIGIT(ch))
+ netstring_except(stream, NETSTRING_ERR_FORMAT);
+ len = len * 10 + ch - '0';
+ break;
+ }
+ }
+}
+
+/* netstring_get_data - read netstring payload + terminator */
+
+VSTRING *netstring_get_data(VSTREAM *stream, VSTRING *buf, int len)
+{
+ char *myname = "netstring_get_data";
+
+ /*
+ * Allocate buffer space.
+ */
+ VSTRING_RESET(buf);
+ VSTRING_SPACE(buf, len);
+
+ /*
+ * Read the payload and absorb the terminator.
+ */
+ if (vstream_fread(stream, STR(buf), len) != len)
+ netstring_except(stream, vstream_ftimeout(stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+ if (msg_verbose > 1)
+ msg_info("%s: read netstring data %.*s",
+ myname, len < 30 ? len : 30, STR(buf));
+ netstring_get_terminator(stream);
+
+ /*
+ * Position the buffer.
+ */
+ VSTRING_AT_OFFSET(buf, len);
+ return (buf);
+}
+
+/* netstring_get_terminator - absorb netstring terminator */
+
+void netstring_get_terminator(VSTREAM *stream)
+{
+ if (VSTREAM_GETC(stream) != ',')
+ netstring_except(stream, NETSTRING_ERR_FORMAT);
+}
+
+/* netstring_get - read string from netstring stream */
+
+VSTRING *netstring_get(VSTREAM *stream, VSTRING *buf, int limit)
+{
+ int len;
+
+ len = netstring_get_length(stream);
+ if (limit && len > limit)
+ netstring_except(stream, NETSTRING_ERR_SIZE);
+ netstring_get_data(stream, buf, len);
+}
+
+/* netstring_put - send string as netstring */
+
+void netstring_put(VSTREAM *stream, const char *data, int len)
+{
+ char *myname = "netstring_put";
+
+ if (msg_verbose > 1)
+ msg_info("%s: write netstring len %d data %.*s",
+ myname, len, len < 30 ? len : 30, data);
+ vstream_fprintf(stream, "%d:", len);
+ vstream_fwrite(stream, data, len);
+ VSTREAM_PUTC(',', stream);
+}
+
+/* netstring_put_multi - send multiple strings as one netstring */
+
+void netstring_put_multi(VSTREAM *stream,...)
+{
+ char *myname = "netstring_put_multi";
+ int total;
+ char *data;
+ int data_len;
+ va_list ap;
+
+ /*
+ * Figure out the total result size.
+ */
+ va_start(ap, stream);
+ for (total = 0; (data = va_arg(ap, char *)) != 0; total += data_len)
+ if ((data_len = va_arg(ap, int)) < 0)
+ msg_panic("netstring_put_multi: bad data length %d", data_len);
+ va_end(ap);
+
+ /*
+ * Debugging support.
+ */
+ if (msg_verbose > 1) {
+ va_start(ap, stream);
+ data = va_arg(ap, char *);
+ data_len = va_arg(ap, int);
+ msg_info("%s: write netstring len %d data %.*s",
+ myname, total, data_len < 30 ? data_len : 30, data);
+ va_end(ap);
+ }
+
+ /*
+ * Send the length, content and terminator.
+ */
+ vstream_fprintf(stream, "%d:", total);
+ va_start(ap, stream);
+ while ((data = va_arg(ap, char *)) != 0) {
+ data_len = va_arg(ap, int);
+ if (data_len > 0)
+ if (vstream_fwrite(stream, data, data_len) != data_len)
+ netstring_except(stream, vstream_ftimeout(stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+ va_end(ap);
+ }
+ vstream_fwrite(stream, ",", 1);
+}
+
+/* netstring_fflush - flush netstring stream */
+
+void netstring_fflush(VSTREAM *stream)
+{
+ if (vstream_fflush(stream) == VSTREAM_EOF)
+ netstring_except(stream, vstream_ftimeout(stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+}
+
+/* netstring_memcpy - copy data as in-memory netstring */
+
+VSTRING *netstring_memcpy(VSTRING *buf, const char *src, int len)
+{
+ vstring_sprintf(buf, "%d:", len);
+ vstring_memcat(buf, src, len);
+ VSTRING_ADDCH(buf, ',');
+ return (buf);
+}
+
+/* netstring_memcat - append data as in-memory netstring */
+
+VSTRING *netstring_memcat(VSTRING *buf, const char *src, int len)
+{
+ vstring_sprintf_append(buf, "%d:", len);
+ vstring_memcat(buf, src, len);
+ VSTRING_ADDCH(buf, ',');
+ return (buf);
+}
--- /dev/null
+#ifndef _NETSTRING_H_INCLUDED_
+#define _NETSTRING_H_INCLUDED_
+
+/*++
+/* NAME
+/* netstring 3h
+/* SUMMARY
+/* netstring stream I/O support
+/* SYNOPSIS
+/* #include <netstring.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+#define NETSTRING_ERR_EOF 1 /* unexpected disconnect */
+#define NETSTRING_ERR_TIME 2 /* time out */
+#define NETSTRING_ERR_FORMAT 3 /* format error */
+#define NETSTRING_ERR_SIZE 4 /* netstring too large */
+
+extern void netstring_except(VSTREAM *, int);
+extern void netstring_setup(VSTREAM *, int);
+extern int netstring_get_length(VSTREAM *);
+extern VSTRING *netstring_get_data(VSTREAM *, VSTRING *, int);
+extern void netstring_get_terminator(VSTREAM *);
+extern VSTRING *netstring_get(VSTREAM *, VSTRING *, int);
+extern void netstring_put(VSTREAM *, const char *, int);
+extern void netstring_put_multi(VSTREAM *,...);
+extern void netstring_fflush(VSTREAM *);
+extern VSTRING *netstring_memcpy(VSTRING *, const char *, int);
+extern VSTRING *netstring_memcat(VSTRING *, const char *, int);
+
+#define NETSTRING_PUT_BUF(str, buf) \
+ netstring_put((str), vstring_str(buf), VSTRING_LEN(buf))
+
+/* 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
/*
/* VSTREAM_PUTCHAR(c) is an alias for VSTREAM_PUTC(c, VSTREAM_OUT).
/*
-/* vstream_unget() pushes back a character onto the specified stream
+/* vstream_ungetc() pushes back a character onto the specified stream
/* and returns the character, or VSTREAM_EOF in case of problems.
/* It is an error to push back before reading (or immediately after
/* changing the stream offset via vstream_fseek()). Upon successful
-/* return, vstream_unget() clears the end-of-file stream flag.
+/* return, vstream_ungetc() clears the end-of-file stream flag.
/*
/* vstream_fputs() appends the given null-terminated string to the
/* specified buffered stream. The result is 0 in case of success,
/* const char *src;
/* int len;
/*
+/* VSTRING *vstring_memcpy(vp, src, len)
+/* VSTRING *vp;
+/* const char *src;
+/* int len;
+/*
+/* VSTRING *vstring_memcat(vp, src, len)
+/* VSTRING *vp;
+/* const char *src;
+/* int len;
+/*
/* VSTRING *vstring_sprintf(vp, format, ...)
/* VSTRING *vp;
/* const char *format;
/* vstring_strncat() copies at most \fIlen\fR characters. Otherwise it is
/* identical to vstring_strcat().
/*
+/* vstring_memcpy() copies \fIlen\fR bytes to a variable-length string.
+/* \fIsrc\fP provides the data to be copied; \fIvp\fP is the
+/* target and result value. The result is not null-terminated.
+/*
+/* vstring_memcat() appends \fIlen\fR bytes to a variable-length string.
+/* \fIsrc\fP provides the data to be copied; \fIvp\fP is the
+/* target and result value. The result is not null-terminated.
+/*
/* vstring_sprintf() produces a formatted string according to its
/* \fIformat\fR argument. See vstring_vsprintf() for details.
/*
return (vp);
}
+/* vstring_memcpy - copy buffer of limited length */
+
+VSTRING *vstring_memcpy(VSTRING *vp, const char *src, int len)
+{
+ VSTRING_RESET(vp);
+
+ VSTRING_SPACE(vp, len);
+ memcpy(vstring_str(vp), src, len);
+ VSTRING_AT_OFFSET(vp, len);
+ return (vp);
+}
+
+/* vstring_memcat - append buffer of limited length */
+
+VSTRING *vstring_memcat(VSTRING *vp, const char *src, int len)
+{
+ VSTRING_SPACE(vp, len);
+ memcpy(vstring_end(vp), src, len);
+ len += VSTRING_LEN(vp);
+ VSTRING_AT_OFFSET(vp, len);
+ return (vp);
+}
+
/* vstring_export - VSTRING to bare string */
char *vstring_export(VSTRING *vp)
extern VSTRING *vstring_strncpy(VSTRING *, const char *, int);
extern VSTRING *vstring_strcat(VSTRING *, const char *);
extern VSTRING *vstring_strncat(VSTRING *, const char *, int);
+extern VSTRING *vstring_memcpy(VSTRING *, const char *, int);
+extern VSTRING *vstring_memcat(VSTRING *, const char *, int);
extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf(VSTRING *, const char *,...);
extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf_append(VSTRING *, const char *,...);
extern char *vstring_export(VSTRING *);