From 7e8044da83b0c5c0cd0070fe60ee90c540f9fbb7 Mon Sep 17 00:00:00 2001 From: Wietse Venema Date: Sat, 7 Jul 2001 00:00:00 -0500 Subject: [PATCH] snapshot-20010707 --- postfix/.indent.pro | 3 +- postfix/HISTORY | 48 +++ postfix/Makefile.in | 2 +- postfix/README_QMQP | 39 ++ postfix/RELEASE_NOTES | 25 ++ postfix/conf/master.cf | 1 + postfix/conf/postfix-script-diff | 10 +- postfix/conf/postfix-script-sgid | 2 +- postfix/conf/sample-qmqpd.cf | 40 ++ postfix/conf/sample-smtp.cf | 8 + postfix/html/Makefile.in | 5 +- postfix/html/faq.html | 58 ++- postfix/html/lmtp.8.html | 2 +- postfix/html/pipe.8.html | 2 +- postfix/html/qmqpd.8.html | 122 ++++++ postfix/html/smtp.8.html | 5 + postfix/man/Makefile.in | 5 +- postfix/man/man8/lmtp.8 | 2 +- postfix/man/man8/pipe.8 | 4 +- postfix/man/man8/qmqpd.8 | 120 ++++++ postfix/man/man8/smtp.8 | 3 + postfix/mantools/postlink | 1 + postfix/src/global/Makefile.in | 2 +- postfix/src/global/mail_params.h | 19 + postfix/src/global/mail_queue.c | 2 +- postfix/src/global/mail_version.h | 2 +- postfix/src/global/qmqp_proto.h | 27 ++ postfix/src/lmtp/lmtp.c | 2 +- postfix/src/lmtp/lmtp_proto.c | 2 +- postfix/src/local/mailbox.c | 7 +- postfix/src/pipe/pipe.c | 4 +- postfix/src/qmqpd/.indent.pro | 1 + postfix/src/qmqpd/.printfck | 25 ++ postfix/src/qmqpd/Makefile.in | 113 +++++ postfix/src/qmqpd/qmqpd.c | 645 ++++++++++++++++++++++++++++ postfix/src/qmqpd/qmqpd.h | 78 ++++ postfix/src/qmqpd/qmqpd_peer.c | 183 ++++++++ postfix/src/qmqpd/qmqpd_state.c | 96 +++++ postfix/src/smtp/smtp.c | 5 + postfix/src/smtp/smtp_addr.c | 26 +- postfix/src/smtp/smtp_connect.c | 2 +- postfix/src/smtp/smtp_proto.c | 4 +- postfix/src/smtp/smtp_sasl_glue.c | 4 +- postfix/src/smtpd/smtpd.c | 2 + postfix/src/smtpd/smtpd_check.c | 44 +- postfix/src/smtpd/smtpd_peer.c | 1 - postfix/src/smtpd/smtpd_sasl_glue.c | 4 +- postfix/src/smtpstone/Makefile.in | 50 ++- postfix/src/smtpstone/qmqp-sink.c | 287 +++++++++++++ postfix/src/smtpstone/qmqp-source.c | 592 +++++++++++++++++++++++++ postfix/src/smtpstone/smtp-source.c | 13 +- postfix/src/util/Makefile.in | 15 +- postfix/src/util/dict_mysql.c | 9 +- postfix/src/util/mymalloc.c | 6 +- postfix/src/util/netstring.c | 350 +++++++++++++++ postfix/src/util/netstring.h | 54 +++ postfix/src/util/vstream.c | 4 +- postfix/src/util/vstring.c | 41 ++ postfix/src/util/vstring.h | 2 + 59 files changed, 3162 insertions(+), 68 deletions(-) create mode 100644 postfix/README_QMQP mode change 100755 => 100644 postfix/conf/postfix-script-sgid create mode 100644 postfix/conf/sample-qmqpd.cf create mode 100644 postfix/html/qmqpd.8.html create mode 100644 postfix/man/man8/qmqpd.8 create mode 100644 postfix/src/global/qmqp_proto.h create mode 120000 postfix/src/qmqpd/.indent.pro create mode 100644 postfix/src/qmqpd/.printfck create mode 100644 postfix/src/qmqpd/Makefile.in create mode 100644 postfix/src/qmqpd/qmqpd.c create mode 100644 postfix/src/qmqpd/qmqpd.h create mode 100644 postfix/src/qmqpd/qmqpd_peer.c create mode 100644 postfix/src/qmqpd/qmqpd_state.c create mode 100644 postfix/src/smtpstone/qmqp-sink.c create mode 100644 postfix/src/smtpstone/qmqp-source.c create mode 100644 postfix/src/util/netstring.c create mode 100644 postfix/src/util/netstring.h diff --git a/postfix/.indent.pro b/postfix/.indent.pro index c35b4962f..3dcb109b1 100644 --- a/postfix/.indent.pro +++ b/postfix/.indent.pro @@ -24,10 +24,10 @@ -TDICT -TDICT_DB -TDICT_DBM +-TDICT_DEBUG -TDICT_ENV -TDICT_HT -TDICT_LDAP --TDICT_DEBUG -TDICT_MYSQL -TDICT_NI -TDICT_NIS @@ -93,6 +93,7 @@ -TQMGR_RECIPIENT -TQMGR_SCAN -TQMGR_TRANSPORT +-TQMQPD_STATE -TRECIPIENT -TRECIPIENT_LIST -TREC_TYPE_NAME diff --git a/postfix/HISTORY b/postfix/HISTORY index fe46e52a6..17a0dfc47 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -5261,3 +5261,51 @@ Apologies for any names omitted. 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. diff --git a/postfix/Makefile.in b/postfix/Makefile.in index 7046d4050..62d3af31d 100644 --- a/postfix/Makefile.in +++ b/postfix/Makefile.in @@ -6,7 +6,7 @@ DIRS = src/util src/global src/dns src/master src/postfix src/smtpstone \ 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 diff --git a/postfix/README_QMQP b/postfix/README_QMQP new file mode 100644 index 000000000..666f79e41 --- /dev/null +++ b/postfix/README_QMQP @@ -0,0 +1,39 @@ +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 (!). diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index 7a25fef57..71b51f4ac 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -1,3 +1,28 @@ +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 =========================================== diff --git a/postfix/conf/master.cf b/postfix/conf/master.cf index 75073b080..fc4417323 100644 --- a/postfix/conf/master.cf +++ b/postfix/conf/master.cf @@ -69,6 +69,7 @@ # (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 diff --git a/postfix/conf/postfix-script-diff b/postfix/conf/postfix-script-diff index 538659fb9..1881b7380 100644 --- a/postfix/conf/postfix-script-diff +++ b/postfix/conf/postfix-script-diff @@ -1,7 +1,7 @@ -*** 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 @@ -10,13 +10,13 @@ } 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 diff --git a/postfix/conf/postfix-script-sgid b/postfix/conf/postfix-script-sgid old mode 100755 new mode 100644 index 98207c5e8..43f5b2b62 --- a/postfix/conf/postfix-script-sgid +++ b/postfix/conf/postfix-script-sgid @@ -179,7 +179,7 @@ check) 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 diff --git a/postfix/conf/sample-qmqpd.cf b/postfix/conf/sample-qmqpd.cf new file mode 100644 index 000000000..03d26ee3c --- /dev/null +++ b/postfix/conf/sample-qmqpd.cf @@ -0,0 +1,40 @@ +# 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 diff --git a/postfix/conf/sample-smtp.cf b/postfix/conf/sample-smtp.cf index ccaf5ec64..b1ffef725 100644 --- a/postfix/conf/sample-smtp.cf +++ b/postfix/conf/sample-smtp.cf @@ -65,6 +65,14 @@ smtp_never_send_ehlo = no # #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). diff --git a/postfix/html/Makefile.in b/postfix/html/Makefile.in index cead447a0..44a59f239 100644 --- a/postfix/html/Makefile.in +++ b/postfix/html/Makefile.in @@ -5,7 +5,7 @@ SHELL = /bin/sh 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 \ @@ -68,6 +68,9 @@ pipe.8.html: ../src/pipe/pipe.c 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 >$@ diff --git a/postfix/html/faq.html b/postfix/html/faq.html index 355a5a855..827439ecc 100644 --- a/postfix/html/faq.html +++ b/postfix/html/faq.html @@ -94,6 +94,12 @@ domains with "relay access denied"
  • Mail delivery fails with: "unknown mail transport error" +
  • Too many connections + +
  • write queue file: No such file or directory + +
  • write queue file: Unknown error 4294967289 +

    @@ -1033,7 +1039,7 @@ Berkeley DB library version.


    -

    sendmail has set-uid root file permissions, or is run from a +

    sendmail has set-uid root file permissions, or is run from a set-uid root process

    Traditionally, the UNIX sendmail command is installed with @@ -1126,36 +1132,49 @@ run out of file handles; after that, it will run out of sockets.

    -To set kernel parameters at boot time, add the following lines to -the /boot/loader.conf file (this is specific to FreeBSD 4.x): +To set the following kernel parameters at boot time, add the +following lines to the /boot/loader.conf file (this is +specific to FreeBSD 4.x):

     kern.ipc.maxsockets="5000"
    -kern.maxfiles="16384"
    -kern.maxfilesperproc="16384"
     kern.ipc.nmbclusters="65536"
     

    -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). + +

    + +To set the following kernel parameters at run time execute the +following commands as root (this is specific to FreeBSD 4.x):

    -# sysctl -w kern.ipc.maxsockets=5000
     # sysctl -w kern.maxfiles=16384
     # sysctl -w kern.maxfilesperproc=16384
    -# sysctl -w kern.ipc.nmbclusters=65536
     
    +

    + +These parameters cannot be set from /boot/loader.conf +(verified with FreeBSD 4.2). + +

    + +Other kernel parameters such as kern.maxproc can be increased +only by recompiling the kernel with a different maxusers +setting in the kernel configuration file (verified with FreeBSD 4.2). +


    Running hundreds of Postfix processes on Linux

    @@ -3367,6 +3386,27 @@ files, and to mount the Postfix queue file system with the
    +

    Too many connections

    + +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 virtual and canonical maps are accessed by +every smtpd and cleanup process. + +
    + +

    write queue file: No such file or directory

    + +

    write queue file: Unknown error 4294967289

    + +Reiserfs reports the wrong error code when a message exceeds the +message_size_limit 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. + +
    + Up one level | Postfix FAQ diff --git a/postfix/html/lmtp.8.html b/postfix/html/lmtp.8.html index df2d81ed6..a4428fa0b 100644 --- a/postfix/html/lmtp.8.html +++ b/postfix/html/lmtp.8.html @@ -108,7 +108,7 @@ LMTP(8) LMTP(8) found in services(4). Authentication controls - lmtp_enable_sasl_auth + lmtp_sasl_auth_enable Enable per-session authentication as per RFC 2554 (SASL). By default, Postfix is built without SASL support. diff --git a/postfix/html/pipe.8.html b/postfix/html/pipe.8.html index 9fb5b181c..372fce1b6 100644 --- a/postfix/html/pipe.8.html +++ b/postfix/html/pipe.8.html @@ -78,7 +78,7 @@ PIPE(8) PIPE(8) The q flag affects only entire addresses, not the partial address information from the - $user, extension or mailbox command-line + $user, $extension or $mailbox command-line macros. u Fold the command-line $recipient address diff --git a/postfix/html/qmqpd.8.html b/postfix/html/qmqpd.8.html new file mode 100644 index 000000000..7bed7101b --- /dev/null +++ b/postfix/html/qmqpd.8.html @@ -0,0 +1,122 @@ +
    +
    +QMQPD(8)                                                 QMQPD(8)
    +
    +NAME
    +       qmqpd - Postfix QMQP server
    +
    +SYNOPSIS
    +       qmqpd [generic Postfix daemon options]
    +
    +DESCRIPTION
    +       The  Postfix  QMQP server receives one message per connec-
    +       tion.  Each message is piped through the  cleanup(8)  dae-
    +       mon,  and  is placed into the incoming queue as one single
    +       queue file.  The program expects to be run from  the  mas-
    +       ter(8) process manager.
    +
    +       The QMQP server implements one access policy: only explic-
    +       itly authorized client hosts are allowed to use  the  ser-
    +       vice.
    +
    +SECURITY
    +       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 syslogd(8).
    +
    +BUGS
    +       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.
    +
    +CONFIGURATION PARAMETERS
    +       The  following  main.cf parameters are especially relevant
    +       to this program. See the Postfix main.cf file  for  syntax
    +       details  and  for  default  values. Use the postfix reload
    +       command after a configuration change.
    +
    +Miscellaneous
    +       always_bcc
    +              Address to send a copy of each message that  enters
    +              the system.
    +
    +       debug_peer_level
    +              Increment  in  verbose  logging level when a remote
    +              host  matches  a  pattern  in  the  debug_peer_list
    +              parameter.
    +
    +       debug_peer_list
    +              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
    +              debug_peer_level parameter.
    +
    +       hopcount_limit
    +              Limit the number of Received: message headers.
    +
    +       qmqpd_authorized_clients
    +              A list of domain or network patterns that specifies
    +              what clients are allowed to use the service.
    +
    +       qmqpd_timeout
    +              Limit  the  time  to  send a server response and to
    +              receive a client request.
    +
    +       soft_bounce
    +              Change hard (D)  reject  responses  into  soft  (Z)
    +              reject  responses.   This can be useful for testing
    +              purposes.
    +
    +Content inspection controls
    +       content_filter
    +              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.
    +
    +Resource controls
    +       line_length_limit
    +              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.
    +
    +       message_size_limit
    +              Limit the total size in bytes of a message, includ-
    +              ing  on-disk  storage  for  sender  and   recipient
    +              address information.
    +
    +Tarpitting
    +       qmqpd_error_sleep_time
    +              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
    +       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
    +
    +                                                                1
    +
    +
    diff --git a/postfix/html/smtp.8.html b/postfix/html/smtp.8.html index 4dc5f5276..3d06c3a05 100644 --- a/postfix/html/smtp.8.html +++ b/postfix/html/smtp.8.html @@ -117,6 +117,11 @@ SMTP(8) SMTP(8) smtp_never_send_ehlo Never send EHLO at the start of a connection. + smtp_break_lines + Break lines > $line_length_limit into multiple + shorter lines. Some SMTP servers misbehave on long + lines. + smtp_skip_4xx_greeting Skip servers that greet us with a 4xx status code. diff --git a/postfix/man/Makefile.in b/postfix/man/Makefile.in index 489162160..a59eb1fe9 100644 --- a/postfix/man/Makefile.in +++ b/postfix/man/Makefile.in @@ -5,7 +5,7 @@ SHELL = /bin/sh 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 \ @@ -65,6 +65,9 @@ man8/pipe.8: ../src/pipe/pipe.c 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 $? >$@ diff --git a/postfix/man/man8/lmtp.8 b/postfix/man/man8/lmtp.8 index 51e54e05d..b00dc9d35 100644 --- a/postfix/man/man8/lmtp.8 +++ b/postfix/man/man8/lmtp.8 @@ -104,7 +104,7 @@ Do not wait for the server response after sending QUIT. 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 diff --git a/postfix/man/man8/pipe.8 b/postfix/man/man8/pipe.8 index c947ab058..65e566e48 100644 --- a/postfix/man/man8/pipe.8 +++ b/postfix/man/man8/pipe.8 @@ -80,8 +80,8 @@ The result is compatible with the address parsing of command-line 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. diff --git a/postfix/man/man8/qmqpd.8 b/postfix/man/man8/qmqpd.8 new file mode 100644 index 000000000..f2b0c31a4 --- /dev/null +++ b/postfix/man/man8/qmqpd.8 @@ -0,0 +1,120 @@ +.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 diff --git a/postfix/man/man8/smtp.8 b/postfix/man/man8/smtp.8 index 3fd07aa09..f05a893c1 100644 --- a/postfix/man/man8/smtp.8 +++ b/postfix/man/man8/smtp.8 @@ -111,6 +111,9 @@ postmaster with transcripts of SMTP sessions with protocol errors. 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 diff --git a/postfix/mantools/postlink b/postfix/mantools/postlink index d9c57a9c1..3e228063c 100755 --- a/postfix/mantools/postlink +++ b/postfix/mantools/postlink @@ -19,6 +19,7 @@ exec sed ' s/[]*pickup[]*(8)/&<\/a>/ s/[]*pipe[]*(8)/&<\/a>/ s/[]*qmgr[]*(8)/&<\/a>/ + s/[]*qmqpd[]*(8)/&<\/a>/ s/[]*showq[]*(8)/&<\/a>/ s/[]*smtp[]*(8)/&<\/a>/ s/[]*smtpd[]*(8)/&<\/a>/ diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in index 053ea4e92..b14c251a3 100644 --- a/postfix/src/global/Makefile.in +++ b/postfix/src/global/Makefile.in @@ -54,7 +54,7 @@ HDRS = been_here.h bounce.h canon_addr.h cleanup_user.h clnt_stream.h \ 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 \ diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index 452fc443c..1cfaf8755 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -714,6 +714,10 @@ extern char *var_smtp_bind_addr; #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 @@ -1252,6 +1256,21 @@ extern char *var_virt_mailbox_lock; #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 diff --git a/postfix/src/global/mail_queue.c b/postfix/src/global/mail_queue.c index 9e8e64323..54916c8b5 100644 --- a/postfix/src/global/mail_queue.c +++ b/postfix/src/global/mail_queue.c @@ -76,7 +76,7 @@ /* 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 diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index eac4c3b73..acd597ae9 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -15,7 +15,7 @@ * 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 diff --git a/postfix/src/global/qmqp_proto.h b/postfix/src/global/qmqp_proto.h new file mode 100644 index 000000000..8ad9e2aad --- /dev/null +++ b/postfix/src/global/qmqp_proto.h @@ -0,0 +1,27 @@ +/*++ +/* NAME +/* qmqpd 3h +/* SUMMARY +/* QMQP protocol +/* SYNOPSIS +/* include +/* 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 +/*--*/ diff --git a/postfix/src/lmtp/lmtp.c b/postfix/src/lmtp/lmtp.c index 0b538f8fb..e943fa855 100644 --- a/postfix/src/lmtp/lmtp.c +++ b/postfix/src/lmtp/lmtp.c @@ -88,7 +88,7 @@ /* 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 diff --git a/postfix/src/lmtp/lmtp_proto.c b/postfix/src/lmtp/lmtp_proto.c index f35d8249b..7cf98f77c 100644 --- a/postfix/src/lmtp/lmtp_proto.c +++ b/postfix/src/lmtp/lmtp_proto.c @@ -498,7 +498,7 @@ static int lmtp_loop(LMTP_STATE *state, int send_state, int recv_state) */ case LMTP_STATE_RCPT: if (!mail_from_rejected) { -#ifndef notRFC821_SYNTAX +#ifdef notdef if (resp->code == 552) resp->code = 452; #endif diff --git a/postfix/src/local/mailbox.c b/postfix/src/local/mailbox.c index 854590c11..e49c9137c 100644 --- a/postfix/src/local/mailbox.c +++ b/postfix/src/local/mailbox.c @@ -276,7 +276,7 @@ int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) 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]) @@ -286,6 +286,11 @@ int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) 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); diff --git a/postfix/src/pipe/pipe.c b/postfix/src/pipe/pipe.c index d32777c3d..6503905db 100644 --- a/postfix/src/pipe/pipe.c +++ b/postfix/src/pipe/pipe.c @@ -70,8 +70,8 @@ /* 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. diff --git a/postfix/src/qmqpd/.indent.pro b/postfix/src/qmqpd/.indent.pro new file mode 120000 index 000000000..5c837eca6 --- /dev/null +++ b/postfix/src/qmqpd/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/postfix/src/qmqpd/.printfck b/postfix/src/qmqpd/.printfck new file mode 100644 index 000000000..66016ed45 --- /dev/null +++ b/postfix/src/qmqpd/.printfck @@ -0,0 +1,25 @@ +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 diff --git a/postfix/src/qmqpd/Makefile.in b/postfix/src/qmqpd/Makefile.in new file mode 100644 index 000000000..117cf9ea4 --- /dev/null +++ b/postfix/src/qmqpd/Makefile.in @@ -0,0 +1,113 @@ +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 diff --git a/postfix/src/qmqpd/qmqpd.c b/postfix/src/qmqpd/qmqpd.c new file mode 100644 index 000000000..281e472c2 --- /dev/null +++ b/postfix/src/qmqpd/qmqpd.c @@ -0,0 +1,645 @@ +/*++ +/* 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 +#include +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Single-threaded server skeleton. */ + +#include + +/* Application-specific */ + +#include + + /* + * 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); +} diff --git a/postfix/src/qmqpd/qmqpd.h b/postfix/src/qmqpd/qmqpd.h new file mode 100644 index 000000000..9bccc7496 --- /dev/null +++ b/postfix/src/qmqpd/qmqpd.h @@ -0,0 +1,78 @@ +/*++ +/* NAME +/* qmqpd 3h +/* SUMMARY +/* Postfix QMQP server +/* SYNOPSIS +/* include "qmqpd.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include + + /* + * 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 +/*--*/ diff --git a/postfix/src/qmqpd/qmqpd_peer.c b/postfix/src/qmqpd/qmqpd_peer.c new file mode 100644 index 000000000..9f0e9f2f5 --- /dev/null +++ b/postfix/src/qmqpd/qmqpd_peer.c @@ -0,0 +1,183 @@ +/*++ +/* 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 +#include +#include +#include +#include /* strerror() */ +#include +#include +#include + + /* + * 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 +#include +#include +#include + +/* 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); +} diff --git a/postfix/src/qmqpd/qmqpd_state.c b/postfix/src/qmqpd/qmqpd_state.c new file mode 100644 index 000000000..f4eaf27f6 --- /dev/null +++ b/postfix/src/qmqpd/qmqpd_state.c @@ -0,0 +1,96 @@ +/*++ +/* 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 +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include + +/* 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); +} diff --git a/postfix/src/smtp/smtp.c b/postfix/src/smtp/smtp.c index 32c41a8d3..b884e7b82 100644 --- a/postfix/src/smtp/smtp.c +++ b/postfix/src/smtp/smtp.c @@ -95,6 +95,9 @@ /* 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 @@ -250,6 +253,7 @@ char *var_smtp_sasl_passwd; 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 @@ -426,6 +430,7 @@ int main(int argc, char **argv) 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, }; diff --git a/postfix/src/smtp/smtp_addr.c b/postfix/src/smtp/smtp_addr.c index f04fcacbd..9be6cd8b9 100644 --- a/postfix/src/smtp/smtp_addr.c +++ b/postfix/src/smtp/smtp_addr.c @@ -79,11 +79,33 @@ #include #include #include +#include #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 @@ -158,8 +180,8 @@ static DNS_RR *smtp_addr_one(DNS_RR *addr_list, char *host, unsigned pref, VSTRI 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", diff --git a/postfix/src/smtp/smtp_connect.c b/postfix/src/smtp/smtp_connect.c index 6b9c58f6e..8c9470520 100644 --- a/postfix/src/smtp/smtp_connect.c +++ b/postfix/src/smtp/smtp_connect.c @@ -394,7 +394,7 @@ SMTP_SESSION *smtp_connect(char *destination, VSTRING *why) 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 diff --git a/postfix/src/smtp/smtp_proto.c b/postfix/src/smtp/smtp_proto.c index 2f6ffc44e..b26095fd8 100644 --- a/postfix/src/smtp/smtp_proto.c +++ b/postfix/src/smtp/smtp_proto.c @@ -505,7 +505,7 @@ int smtp_xfer(SMTP_STATE *state) */ case SMTP_STATE_RCPT: if (!mail_from_rejected) { -#ifndef notRFC821_SYNTAX +#ifdef notdef if (resp->code == 552) resp->code = 452; #endif @@ -642,6 +642,8 @@ int smtp_xfer(SMTP_STATE *state) 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), diff --git a/postfix/src/smtp/smtp_sasl_glue.c b/postfix/src/smtp/smtp_sasl_glue.c index 6336e2e5e..0a4e28fe3 100644 --- a/postfix/src/smtp/smtp_sasl_glue.c +++ b/postfix/src/smtp/smtp_sasl_glue.c @@ -139,11 +139,11 @@ static int smtp_sasl_log(void *unused_context, int priority, 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); diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index daf311184..5353a8e60 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -339,6 +339,7 @@ bool var_disable_vrfy_cmd; 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; @@ -1551,6 +1552,7 @@ int main(int argc, char **argv) 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, diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c index cea04c04a..75e7694f4 100644 --- a/postfix/src/smtpd/smtpd_check.c +++ b/postfix/src/smtpd/smtpd_check.c @@ -323,6 +323,7 @@ static MAPS *local_rcpt_maps; static MAPS *rcpt_canon_maps; static MAPS *canonical_maps; static MAPS *virtual_maps; +static MAPS *virt_mailbox_maps; static MAPS *relocated_maps; /* @@ -467,6 +468,8 @@ void smtpd_check_init(void) 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); @@ -565,7 +568,7 @@ static int smtpd_check_reject(SMTPD_STATE *state, int error_class, * * 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') @@ -898,7 +901,9 @@ static int permit_auth_destination(SMTPD_STATE *state, char *recipient) */ 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); /* @@ -1032,7 +1037,9 @@ static int permit_mx_backup(SMTPD_STATE *state, const char *recipient) 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) @@ -1174,7 +1181,9 @@ static int reject_unknown_address(SMTPD_STATE *state, char *addr, 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); @@ -2066,6 +2075,23 @@ char *smtpd_check_rcptmap(SMTPD_STATE *state, char *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))) { + (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); @@ -2082,6 +2108,7 @@ char *smtpd_check_rcptmap(SMTPD_STATE *state, char *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, @@ -2175,6 +2202,7 @@ char *var_alias_maps; 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; @@ -2195,6 +2223,7 @@ static STRING_TABLE string_table[] = { 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, @@ -2503,6 +2532,13 @@ main(int argc, char **argv) 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, diff --git a/postfix/src/smtpd/smtpd_peer.c b/postfix/src/smtpd/smtpd_peer.c index e6ef0ab35..81f88431a 100644 --- a/postfix/src/smtpd/smtpd_peer.c +++ b/postfix/src/smtpd/smtpd_peer.c @@ -119,7 +119,6 @@ void smtpd_peer_init(SMTPD_STATE *state) * 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; diff --git a/postfix/src/smtpd/smtpd_sasl_glue.c b/postfix/src/smtpd/smtpd_sasl_glue.c index caa3ec04f..716b7873d 100644 --- a/postfix/src/smtpd/smtpd_sasl_glue.c +++ b/postfix/src/smtpd/smtpd_sasl_glue.c @@ -118,11 +118,11 @@ static int smtpd_sasl_log(void *unused_context, int priority, 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; diff --git a/postfix/src/smtpstone/Makefile.in b/postfix/src/smtpstone/Makefile.in index 5cf7920e0..454a44c13 100644 --- a/postfix/src/smtpstone/Makefile.in +++ b/postfix/src/smtpstone/Makefile.in @@ -1,6 +1,6 @@ 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 \ @@ -10,7 +10,7 @@ DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) 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 @@ -26,9 +26,15 @@ smtp-sink: smtp-sink.o $(LIBS) 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 $? $@ @@ -36,6 +42,12 @@ update: ../../bin/smtp-source ../../bin/smtp-sink ../../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 @@ -61,6 +73,36 @@ depend: $(MAKES) @$(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 diff --git a/postfix/src/smtpstone/qmqp-sink.c b/postfix/src/smtpstone/qmqp-sink.c new file mode 100644 index 000000000..53b39584c --- /dev/null +++ b/postfix/src/smtpstone/qmqp-sink.c @@ -0,0 +1,287 @@ +/*++ +/* 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 +#include +#include +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* 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); +} diff --git a/postfix/src/smtpstone/qmqp-source.c b/postfix/src/smtpstone/qmqp-source.c new file mode 100644 index 000000000..a06ab7591 --- /dev/null +++ b/postfix/src/smtpstone/qmqp-source.c @@ -0,0 +1,592 @@ +/*++ +/* 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: ). +/* .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: ). +/* .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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* 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); + } + } +} diff --git a/postfix/src/smtpstone/smtp-source.c b/postfix/src/smtpstone/smtp-source.c index 50fe6ec9a..9e69fa122 100644 --- a/postfix/src/smtpstone/smtp-source.c +++ b/postfix/src/smtpstone/smtp-source.c @@ -9,8 +9,8 @@ /* /* \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. @@ -32,7 +32,8 @@ /* .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" @@ -40,7 +41,7 @@ /* .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" @@ -781,7 +782,7 @@ int main(int argc, char **argv) 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'; } @@ -848,7 +849,7 @@ int main(int argc, char **argv) } 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; diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index 244bae4ad..a2dd61bb2 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -23,7 +23,7 @@ SRCS = argv.c argv_split.c attr.c basename.c binhash.c chroot_uid.c \ 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 \ @@ -48,7 +48,7 @@ OBJS = argv.o argv_split.o attr.o basename.o binhash.o chroot_uid.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 \ @@ -64,12 +64,12 @@ HDRS = argv.h attr.h binhash.h chroot_uid.h connect.h dict.h dict_db.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) @@ -770,6 +770,13 @@ name_mask.o: stringops.h 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 diff --git a/postfix/src/util/dict_mysql.c b/postfix/src/util/dict_mysql.c index 28fc70156..f778e928d 100644 --- a/postfix/src/util/dict_mysql.c +++ b/postfix/src/util/dict_mysql.c @@ -274,14 +274,14 @@ static MYSQL_RES *plmysql_query(PLMYSQL *PLDB, 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); } } @@ -323,7 +323,8 @@ static void plmysql_connect_single(HOST *host, char *dbname, char *username, cha 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) @@ -366,7 +367,7 @@ DICT *dict_mysql_open(const char *name, int unused_open_flags, int dict_flags) 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 */ diff --git a/postfix/src/util/mymalloc.c b/postfix/src/util/mymalloc.c index e20531c06..04c1fb49a 100644 --- a/postfix/src/util/mymalloc.c +++ b/postfix/src/util/mymalloc.c @@ -173,10 +173,10 @@ char *mystrdup(const char *str) 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); diff --git a/postfix/src/util/netstring.c b/postfix/src/util/netstring.c new file mode 100644 index 000000000..63c6faf3b --- /dev/null +++ b/postfix/src/util/netstring.c @@ -0,0 +1,350 @@ +/*++ +/* NAME +/* netstring 3 +/* SUMMARY +/* netstring stream I/O support +/* SYNOPSIS +/* #include +/* +/* 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 +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* 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); +} diff --git a/postfix/src/util/netstring.h b/postfix/src/util/netstring.h new file mode 100644 index 000000000..6b82d90e4 --- /dev/null +++ b/postfix/src/util/netstring.h @@ -0,0 +1,54 @@ +#ifndef _NETSTRING_H_INCLUDED_ +#define _NETSTRING_H_INCLUDED_ + +/*++ +/* NAME +/* netstring 3h +/* SUMMARY +/* netstring stream I/O support +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * 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 diff --git a/postfix/src/util/vstream.c b/postfix/src/util/vstream.c index e2ec94ddc..c5bdbfdde 100644 --- a/postfix/src/util/vstream.c +++ b/postfix/src/util/vstream.c @@ -175,11 +175,11 @@ /* /* 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, diff --git a/postfix/src/util/vstring.c b/postfix/src/util/vstring.c index 356b114b2..400b5f0af 100644 --- a/postfix/src/util/vstring.c +++ b/postfix/src/util/vstring.c @@ -67,6 +67,16 @@ /* 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; @@ -174,6 +184,14 @@ /* 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. /* @@ -392,6 +410,29 @@ VSTRING *vstring_strncat(VSTRING *vp, const char *src, int len) 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) diff --git a/postfix/src/util/vstring.h b/postfix/src/util/vstring.h index 6ae8947e7..b1b81bb8a 100644 --- a/postfix/src/util/vstring.h +++ b/postfix/src/util/vstring.h @@ -38,6 +38,8 @@ extern VSTRING *vstring_strcpy(VSTRING *, const char *); 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 *);