SMTP WELLKNOWN extension
authorJeremy Harris <jgh146exb@wizmail.org>
Thu, 30 May 2024 15:20:52 +0000 (16:20 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Thu, 30 May 2024 15:32:57 +0000 (16:32 +0100)
25 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
doc/doc-txt/experimental-spec.txt
doc/doc-txt/id-wellknown.txt [new file with mode: 0644]
src/src/EDITME
src/src/acl.c
src/src/auths/xtextencode.c
src/src/config.h.defaults
src/src/exim.c
src/src/expand.c
src/src/globals.c
src/src/globals.h
src/src/macro_predef.c
src/src/macros.h
src/src/readconf.c
src/src/smtp_in.c
src/util/mailtest [new file with mode: 0755]
test/aux-fixed/4040/acme-response [new file with mode: 0644]
test/aux-fixed/4040/sub/acme-response [new file with mode: 0644]
test/confs/4040 [new file with mode: 0644]
test/log/4040 [new file with mode: 0644]
test/rejectlog/4040 [new file with mode: 0644]
test/runtest
test/scripts/4040-wellknown/4040 [new file with mode: 0644]
test/scripts/4040-wellknown/REQUIRES [new file with mode: 0644]

index cea68381016f86644d5071c06e78d1a8f20d0b13..ef02705404dbf2f5881f68c2a1a2fbe0f058f8f2 100644 (file)
@@ -11585,6 +11585,19 @@ literal question mark).
 .cindex "&%utf8_localpart_from_alabel%& expansion item"
 These convert EAI mail name components between UTF-8 and a-label forms.
 For information on internationalisation support see &<<SECTi18nMTA>>&.
 .cindex "&%utf8_localpart_from_alabel%& expansion item"
 These convert EAI mail name components between UTF-8 and a-label forms.
 For information on internationalisation support see &<<SECTi18nMTA>>&.
+
+
+.new
+.vitem &*${xtextd:*&<&'string'&>&*}*&
+.cindex "text forcing in strings"
+.cindex "string" "xtext decoding"
+.cindex "xtext"
+.cindex "&%xtextd%& expansion item"
+This performs xtext decoding of the string (per RFC 3461 section 4).
+.wen
+
+
+
 .endlist
 
 
 .endlist
 
 
@@ -14836,6 +14849,7 @@ listed in more than one group.
 .row &%acl_smtp_rcpt%&               "ACL for RCPT"
 .row &%acl_smtp_starttls%&           "ACL for STARTTLS"
 .row &%acl_smtp_vrfy%&               "ACL for VRFY"
 .row &%acl_smtp_rcpt%&               "ACL for RCPT"
 .row &%acl_smtp_starttls%&           "ACL for STARTTLS"
 .row &%acl_smtp_vrfy%&               "ACL for VRFY"
+.row &%acl_smtp_wellknown%&          "ACL for WELLKNOWN"
 .row &%av_scanner%&                  "specify virus scanner"
 .row &%check_rfc2047_length%&        "check length of RFC 2047 &""encoded &&&
                                       words""&"
 .row &%av_scanner%&                  "specify virus scanner"
 .row &%check_rfc2047_length%&        "check length of RFC 2047 &""encoded &&&
                                       words""&"
@@ -15002,6 +15016,7 @@ See also the &'Policy controls'& section above.
 .row &%prdr_enable%&                 "advertise PRDR to all hosts"
 .row &%smtputf8_advertise_hosts%&    "advertise SMTPUTF8 to these hosts"
 .row &%tls_advertise_hosts%&         "advertise TLS to these hosts"
 .row &%prdr_enable%&                 "advertise PRDR to all hosts"
 .row &%smtputf8_advertise_hosts%&    "advertise SMTPUTF8 to these hosts"
 .row &%tls_advertise_hosts%&         "advertise TLS to these hosts"
+.row &%wellknown_advertise_hosts%&   "advertise WELLKNOWN to these hosts"
 .endtable
 
 
 .endtable
 
 
@@ -15242,6 +15257,13 @@ received. See chapter &<<CHAPACL>>& for further details.
 This option defines the ACL that is run when an SMTP VRFY command is
 received. See chapter &<<CHAPACL>>& for further details.
 
 This option defines the ACL that is run when an SMTP VRFY command is
 received. See chapter &<<CHAPACL>>& for further details.
 
+.new
+.option acl_smtp_wellknown main string&!! unset
+.cindex "WELLKNOWN, ACL for"
+This option defines the ACL that is run when an SMTP WELLKNOWN command is
+received. See section &<<SECTWELLKNOWNACL>>& for further details.
+.wen
+
 .option add_environment main "string list" empty
 .cindex "environment" "set values"
 This option adds individual environment variables that the
 .option add_environment main "string list" empty
 .cindex "environment" "set values"
 This option adds individual environment variables that the
@@ -18913,6 +18935,14 @@ absolute and untainted.
 See also &%bounce_message_file%&.
 
 
 See also &%bounce_message_file%&.
 
 
+.new
+.option wellknown_advertise_hosts main boolean unset
+.cindex WELLKNOWN advertisement
+.cindex "ESMTP extensions" WELLKNOWN
+This option enables the advertising of the SMTP WELLKNOWN extension.
+See also the &%acl_smtp_wellknown%& ACL (&<<SECTWELLKNOWNACL>>&).
+.wen
+
 .option write_rejectlog main boolean true
 .cindex "reject log" "disabling"
 If this option is set false, Exim no longer writes anything to the reject log.
 .option write_rejectlog main boolean true
 .cindex "reject log" "disabling"
 If this option is set false, Exim no longer writes anything to the reject log.
@@ -30645,6 +30675,7 @@ options in the main part of the configuration. These options are:
 .cindex "RCPT" "ACL for"
 .cindex "STARTTLS, ACL for"
 .cindex "VRFY" "ACL for"
 .cindex "RCPT" "ACL for"
 .cindex "STARTTLS, ACL for"
 .cindex "VRFY" "ACL for"
+.cindex "WELLKNOWN" "ACL for"
 .cindex "SMTP" "connection, ACL for"
 .cindex "non-SMTP messages" "ACLs for"
 .cindex "MIME content scanning" "ACL for"
 .cindex "SMTP" "connection, ACL for"
 .cindex "non-SMTP messages" "ACLs for"
 .cindex "MIME content scanning" "ACL for"
@@ -30671,6 +30702,7 @@ options in the main part of the configuration. These options are:
 .irow &%acl_smtp_rcpt%&        "ACL for RCPT"
 .irow &%acl_smtp_starttls%&    "ACL for STARTTLS"
 .irow &%acl_smtp_vrfy%&        "ACL for VRFY"
 .irow &%acl_smtp_rcpt%&        "ACL for RCPT"
 .irow &%acl_smtp_starttls%&    "ACL for STARTTLS"
 .irow &%acl_smtp_vrfy%&        "ACL for VRFY"
+.irow &%acl_smtp_wellknown%&   "ACL for WELLKNOWN"
 .endtable
 
 For example, if you set
 .endtable
 
 For example, if you set
@@ -30853,6 +30885,62 @@ This ACL is evaluated after &%acl_smtp_dkim%& but before &%acl_smtp_data%&.
 If the ACL is not defined, processing completes as if
 the feature was not requested by the client.
 
 If the ACL is not defined, processing completes as if
 the feature was not requested by the client.
 
+.new
+.subsection "The SMTP WELLKNOWN ACL" SECTWELLKNOWNACL
+.cindex "WELLKNOWN" "ACL for"
+.oindex "&%acl_smtp_wellknown%&"
+The &%acl_smtp_wellknown%& ACL is available only when Exim is compiled
+with WELLKNOWN support enabled.
+
+The ACL determines the response to an SMTP WELLKNOWN command, using the normal
+accept/defer/deny verbs for the response code,
+and a new &"control=wellknown"& modifier.
+This modifier takes a single option, separated by a '/'
+character, which must be the name of a file containing the response
+cleartext.  The modifier is expanded before use in the usual way before
+it is used.  The configuration is responsible for picking a suitable file
+to return and, most importantly, not returning any unexpected file.
+The argument for the SMTP verb will be available in the &$smtp_command_argument$&
+variable and can be used for building the file path.
+If the file path given in the modifier is empty or inacessible, the control will
+fail.
+
+For example:
+.code
+ check_wellknown:
+  accept control = wellknown/\
+                   ${lookup {${xtextd:$smtp_command_argument}} \
+                       dsearch,key=path,filter=file,ret=full \
+                       {$spooldir/wellknown.d}}
+.endd
+File content will be encoded in &"xtext"& form, and line-wrapping
+for line-length limitation will be done before transmission.
+A response summary line will be prepended, with the (pre-encoding) file size.
+
+The above example uses the expansion operator ${xtextd:<coded-string>}
+which is needed to decode the xtext-encoded key from the SMTP verb.
+
+Under the util directory there is a "mailtest" utility which can be used
+to test/retrieve WELLKNOWN items. Syntax is
+.code
+  mailtest -h host.example.com -w security.txt
+.endd
+
+WELLKNOWN is a ESMTP extension providing access to extended
+information about the server.  It is modelled on the webserver
+facilities documented in RFC 8615 and can be used for a security.txt
+file and could be used for ACME handshaking (RFC 8555).
+
+Exim will advertise WELLKNOWN support in the EHLO response
+.oindex &%wellknown_advertise_hosts%&
+(conditional on a new option &%wellknown_advertise_hosts%&)
+and service WELLKNOWN smtp verbs having a single parameter
+giving a key for an item of "site-wide metadata".
+The verb and key are separated by whitespace,
+and the key is xtext-encoded (per RFC 3461 section 4).
+.wen
+
+
 .subsection "The QUIT ACL" SECTQUITACL
 .cindex "QUIT, ACL for"
 The ACL for the SMTP QUIT command is anomalous, in that the outcome of the ACL
 .subsection "The QUIT ACL" SECTQUITACL
 .cindex "QUIT, ACL for"
 The ACL for the SMTP QUIT command is anomalous, in that the outcome of the ACL
@@ -31023,12 +31111,15 @@ For &%acl_not_smtp%&, &%acl_smtp_auth%&, &%acl_smtp_connect%&,
 &%acl_smtp_mime%&, &%acl_smtp_predata%&, and &%acl_smtp_starttls%&, the action
 when the ACL is not defined is &"accept"&.
 
 &%acl_smtp_mime%&, &%acl_smtp_predata%&, and &%acl_smtp_starttls%&, the action
 when the ACL is not defined is &"accept"&.
 
-For the others (&%acl_smtp_etrn%&, &%acl_smtp_expn%&, &%acl_smtp_rcpt%&, and
-&%acl_smtp_vrfy%&), the action when the ACL is not defined is &"deny"&.
-This means that &%acl_smtp_rcpt%& must be defined in order to receive any
-messages over an SMTP connection. For an example, see the ACL in the default
-configuration file.
-
+For the others (&%acl_smtp_etrn%&, &%acl_smtp_expn%&, &%acl_smtp_rcpt%&,
+&%acl_smtp_vrfy%&
+.new
+and &%acl_smtp_wellknown%&),
+.wen
+the action when the ACL
+is not defined is &"deny"&.  This means that &%acl_smtp_rcpt%& must be
+defined in order to receive any messages over an SMTP connection.
+For an example, see the ACL in the default configuration file.
 
 
 
 
 
 
@@ -32133,6 +32224,13 @@ that are being submitted at the same time using &%-bs%& or &%-bS%&.
 This control enables conversion of UTF-8 in message envelope addresses
 to a-label form.
 For details see section &<<SECTi18nMTA>>&.
 This control enables conversion of UTF-8 in message envelope addresses
 to a-label form.
 For details see section &<<SECTi18nMTA>>&.
+
+.new
+.vitem &*control&~=&~wellknown*&
+This control sets up a response data file for a WELLKNOWN SMTP command.
+It may only be used in an ACL servicing that command.
+For details see section &<<SECTWELLKNOWNACL>>&.
+.wen
 .endlist vlist
 
 
 .endlist vlist
 
 
index b0702eea2ec9e652c59176fc8f23c113ebd5b5d2..3253b90aa7ea99a0928ab5cf4310ebcde42e2757 100644 (file)
@@ -24,6 +24,10 @@ Version 4.98
 
  7. The dsearch lookup supports search for a sub-path.
 
 
  7. The dsearch lookup supports search for a sub-path.
 
+ 8. Include mailtest utility for simple connection checking.
+
+ 9. Add SMTP WELLKNOWN extension.
+
 Version 4.97
 ------------
 
 Version 4.97
 ------------
 
index aa3a278fd5ec9cbfa6c98e50b2a2d777bfebd21a..56ee10f828df594bc868fa2499572d8f03017bc0 100644 (file)
@@ -658,6 +658,9 @@ After a success:
   $proxy_external_address, $proxy_external_port have the proxy "outside" values
   $sender_host_address, $sender_host_port have the remot client values
 
   $proxy_external_address, $proxy_external_port have the proxy "outside" values
   $sender_host_address, $sender_host_port have the remot client values
 
+
+
+
 --------------------------------------------------------------
 End of file
 --------------------------------------------------------------
 --------------------------------------------------------------
 End of file
 --------------------------------------------------------------
diff --git a/doc/doc-txt/id-wellknown.txt b/doc/doc-txt/id-wellknown.txt
new file mode 100644 (file)
index 0000000..14d89cb
--- /dev/null
@@ -0,0 +1,145 @@
+Internet Draft
+
+Stream: Independent Submission
+Category:
+Date:           2024/05/26
+Author:         J.Harris
+Author:         B.Quatermass
+
+--
+
+    Mailmaint Working Group                                      J. Harris
+    Internet Draft                                               Independent
+    Category: Experimental                                       B. Quatermass
+                                                                 Independent
+                                                                  May 2024
+
+The WELLKNOWN SMTP Service Extension
+
+Abstract
+--------
+
+This document defines a WELLKNOWN extension for the Simple Mail Transfer Protocol
+(SMTP).  The extension provides the means for an SMTP server to inform a client
+of information relating to the server which is intended to be public.
+
+Status of this Memo
+-------------------
+
+This document is published for examination, experimental implementation, and
+evaluation.
+
+This document defines an Experimental Protocol for the Internet community.
+
+This is a contribution to the RFC Series, independently of any other RFC
+stream. The RFC Editor has chosen to publish this document at its discretion
+and makes no statement about its value for implementation or deployment.
+
+1. Introduction
+---------------
+
+The Simple Mail Transfer Protocol [SMTP] provides the ability to transfer email
+messages from a sending system to a recieving one.
+
+Senders may on occasion wish to discover additional information, not directly
+related to a specific email message, about the receiving system.  An example
+is a contact point for discussing problems in communications.
+
+The WELLKNOWN extension provides a means for delivering such information, by an
+SMTP server on request from an SMTP client.
+
+2. The WELLKNOW SMTP Extension
+------------------------------
+
+The extension mechanism for SMTP is defined in Section 2.2 of the current SMTP
+specification [RFC5321a].
+
+The name of the extension is WELKNOWN.  Servers implementing this extension
+advertise a WELLKNOWN as a keyword in the response to EHLO.  The keyword has no
+parameters.
+
+A new SMTP verb, "WELLKNOWN" is defined.
+
+3. The WELLNOWN verb
+--------------------
+
+The format for the WELLKNOWN verb is:
+
+        WELLKNOWN <request-key>
+
+The <request-key> parameter identifies the specific type of information being
+requested.  It is separated from the verb by whitespace, and is xtext-encoded
+per RFC 3461 Section 4 [RFC3461].
+
+After the client gives the WELLKNOWN command, the server responds with one of
+the 2xx, 4xx or 5xx response codes.
+
+A success response MUST be a 250 response code, and MUST be multi-line.
+
+The first line of a success response will be a response summary; the following
+lines are the information data requested, xtext-encoded [RFC3461].  The encoded
+information data MAY be split over multiple response lines.
+
+A response summary MAY be empty.  In this case the first line of the response
+will be only "250-".
+
+A response summary MAY contain a size parameter, giving the number of bytes
+of data.  This parameter is expressed as "SIZE=" followed by a decimal number.
+The size value does not include the xtext-encoding overheader, the "250-" or
+"250 " response code prefixing each line, nor the CR,LF bytes between lines.
+
+4. Example
+----------
+
+S: 220 ESMTP spoken here
+
+C: EHLO test
+
+S: 250-Hi there, mate
+S: 250-SIZE
+S: 250-LIMITS
+S: 250-8BITMIME
+S: 250-PIPELINING
+S: 250-WELLKNOWN
+S: 250 HELP
+
+C: WELLKNOWN security.txt
+
+S: 250-SIZE=285
+S: 250-Contact:+20mailto:security@example.com+0A
+S: 250-+0A
+S: 250-Canonical:+20https://www.example.com/.well-known/security.txt+0A
+S: 250-Canonical:+20mailserver://mx1.example.com/WELLKNOWN/security.txt+0A
+S: 250-Canonical:+20mailserver://mx2.example.com/WELLKNOWN/security.txt+0A
+S: 250-+0A
+S: 250-Preferred-Languages:+20en+0A
+S: 250-+0A
+S: 250-Expires:+202025-02-01T00:00:00.000Z+0A
+S: 250 +0A
+
+C: QUIT
+
+S: 221
+
+
+5. Use Cases
+------------
+
+5.1 security.txt
+---
+It is common for a website to provide public-access information via the HTTP
+protocol.  One such item, a "security.txt" file, is descibed in RFC 9116.
+
+The WELLKNOWN extension provides a method for publishing similar information
+for an SMTP host, without the need to operate an HTTP server.
+
+It is RECOMMENDED that the request-key for this usage be "security.txt".
+
+5.2 ACME handshake
+---
+ACME [RFC8555] provides for obtaining a certificate, needed for encrpted
+communications using TLS.  It defines handshake methods using the DNS and using
+HTTP, for verifying ownership of the domain being certified.
+
+The WELLKNOWN extension provides a method for operating a similar handshake,
+without the need to operate an HTTP server or manipulate the DNS.
index 4a33677d5ca55fcbd8871cf277b179ad407702f1..1440b4b4449acf2c4c440f4dcf149a4123da5b05 100644 (file)
@@ -591,6 +591,9 @@ DISABLE_MAL_MKS=yes
 # using only native facilities.
 # SUPPORT_SRS=yes
 
 # using only native facilities.
 # SUPPORT_SRS=yes
 
+# Uncomment the following to remove support for the ESMTP extension "WELLKNOWN"
+# DISABLE_WELLKNOWN=yes
+
 
 #------------------------------------------------------------------------------
 # Compiling Exim with experimental features. These are documented in
 
 #------------------------------------------------------------------------------
 # Compiling Exim with experimental features. These are documented in
index 4e88fc1acd126dbf5b64168fa1d2e9da92263d78..bfb32df6f49ecc16239e551ca862956405063843 100644 (file)
@@ -57,9 +57,7 @@ static int msgcond[] = {
 
 #endif
 
 
 #endif
 
-/* ACL condition and modifier codes - keep in step with the table that
-follows.
-down. */
+/* ACL condition and modifier codes */
 
 enum { ACLC_ACL,
        ACLC_ADD_HEADER,
 
 enum { ACLC_ACL,
        ACLC_ADD_HEADER,
@@ -119,7 +117,8 @@ enum { ACLC_ACL,
        ACLC_SPF_GUESS,
 #endif
        ACLC_UDPSEND,
        ACLC_SPF_GUESS,
 #endif
        ACLC_UDPSEND,
-       ACLC_VERIFY };
+       ACLC_VERIFY,
+};
 
 /* ACL conditions/modifiers: "delay", "control", "continue", "endpass",
 "message", "log_message", "log_reject_target", "logwrite", "queue" and "set" are
 
 /* ACL conditions/modifiers: "delay", "control", "continue", "endpass",
 "message", "log_message", "log_reject_target", "logwrite", "queue" and "set" are
@@ -149,7 +148,7 @@ static condition_def conditions[] = {
   [ACLC_ACL] =                 { US"acl",              FALSE, FALSE,   0 },
 
   [ACLC_ADD_HEADER] =          { US"add_header",       TRUE, TRUE,
   [ACLC_ACL] =                 { US"acl",              FALSE, FALSE,   0 },
 
   [ACLC_ADD_HEADER] =          { US"add_header",       TRUE, TRUE,
-                                 (unsigned int)
+                                 (unsigned)
                                  ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
                                    ACL_BIT_PREDATA | ACL_BIT_DATA |
 #ifndef DISABLE_PRDR
                                  ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
                                    ACL_BIT_PREDATA | ACL_BIT_DATA |
 #ifndef DISABLE_PRDR
@@ -188,7 +187,7 @@ static condition_def conditions[] = {
 
 #ifdef EXPERIMENTAL_DCC
   [ACLC_DCC] =                 { US"dcc",              TRUE, FALSE,
 
 #ifdef EXPERIMENTAL_DCC
   [ACLC_DCC] =                 { US"dcc",              TRUE, FALSE,
-                                 (unsigned int)
+                                 (unsigned)
                                  ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                  ACL_BIT_PRDR |
                                  ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                  ACL_BIT_PRDR |
@@ -204,7 +203,7 @@ static condition_def conditions[] = {
 #ifndef DISABLE_DKIM
   [ACLC_DKIM_SIGNER] =         { US"dkim_signers",     TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
   [ACLC_DKIM_STATUS] =         { US"dkim_status",      TRUE, FALSE,
 #ifndef DISABLE_DKIM
   [ACLC_DKIM_SIGNER] =         { US"dkim_signers",     TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
   [ACLC_DKIM_STATUS] =         { US"dkim_status",      TRUE, FALSE,
-                                 (unsigned int)
+                                 (unsigned)
                                  ~(ACL_BIT_DKIM | ACL_BIT_DATA | ACL_BIT_MIME
 # ifndef DISABLE_PRDR
                                  | ACL_BIT_PRDR
                                  ~(ACL_BIT_DKIM | ACL_BIT_DATA | ACL_BIT_MIME
 # ifndef DISABLE_PRDR
                                  | ACL_BIT_PRDR
@@ -221,7 +220,7 @@ static condition_def conditions[] = {
   [ACLC_DNSLISTS] =            { US"dnslists", TRUE, FALSE,    0 },
 
   [ACLC_DOMAINS] =             { US"domains",  FALSE, FALSE,
   [ACLC_DNSLISTS] =            { US"dnslists", TRUE, FALSE,    0 },
 
   [ACLC_DOMAINS] =             { US"domains",  FALSE, FALSE,
-                                 (unsigned int)
+                                 (unsigned)
                                  ~(ACL_BIT_RCPT | ACL_BIT_VRFY
 #ifndef DISABLE_PRDR
                                  |ACL_BIT_PRDR
                                  ~(ACL_BIT_RCPT | ACL_BIT_VRFY
 #ifndef DISABLE_PRDR
                                  |ACL_BIT_PRDR
@@ -239,7 +238,7 @@ static condition_def conditions[] = {
                                  ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
   },
   [ACLC_LOCAL_PARTS] =         { US"local_parts",      FALSE, FALSE,
                                  ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
   },
   [ACLC_LOCAL_PARTS] =         { US"local_parts",      FALSE, FALSE,
-                                 (unsigned int)
+                                 (unsigned)
                                  ~(ACL_BIT_RCPT | ACL_BIT_VRFY
 #ifndef DISABLE_PRDR
                                  | ACL_BIT_PRDR
                                  ~(ACL_BIT_RCPT | ACL_BIT_VRFY
 #ifndef DISABLE_PRDR
                                  | ACL_BIT_PRDR
@@ -253,7 +252,7 @@ static condition_def conditions[] = {
 
 #ifdef WITH_CONTENT_SCAN
   [ACLC_MALWARE] =             { US"malware",  TRUE, FALSE,
 
 #ifdef WITH_CONTENT_SCAN
   [ACLC_MALWARE] =             { US"malware",  TRUE, FALSE,
-                                 (unsigned int)
+                                 (unsigned)
                                    ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                    ACL_BIT_PRDR |
                                    ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                    ACL_BIT_PRDR |
@@ -280,7 +279,7 @@ static condition_def conditions[] = {
 
 #ifdef WITH_CONTENT_SCAN
   [ACLC_REGEX] =               { US"regex",            TRUE, FALSE,
 
 #ifdef WITH_CONTENT_SCAN
   [ACLC_REGEX] =               { US"regex",            TRUE, FALSE,
-                                 (unsigned int)
+                                 (unsigned)
                                  ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                    ACL_BIT_PRDR |
                                  ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                    ACL_BIT_PRDR |
@@ -291,7 +290,7 @@ static condition_def conditions[] = {
 
 #endif
   [ACLC_REMOVE_HEADER] =       { US"remove_header",    TRUE, TRUE,
 
 #endif
   [ACLC_REMOVE_HEADER] =       { US"remove_header",    TRUE, TRUE,
-                                 (unsigned int)
+                                 (unsigned)
                                  ~(ACL_BIT_MAIL|ACL_BIT_RCPT |
                                    ACL_BIT_PREDATA | ACL_BIT_DATA |
 #ifndef DISABLE_PRDR
                                  ~(ACL_BIT_MAIL|ACL_BIT_RCPT |
                                    ACL_BIT_PREDATA | ACL_BIT_DATA |
 #ifndef DISABLE_PRDR
@@ -320,7 +319,7 @@ static condition_def conditions[] = {
 
 #ifdef WITH_CONTENT_SCAN
   [ACLC_SPAM] =                        { US"spam",             TRUE, FALSE,
 
 #ifdef WITH_CONTENT_SCAN
   [ACLC_SPAM] =                        { US"spam",             TRUE, FALSE,
-                                 (unsigned int) ~(ACL_BIT_DATA |
+                                 (unsigned) ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                  ACL_BIT_PRDR |
 # endif
 # ifndef DISABLE_PRDR
                                  ACL_BIT_PRDR |
 # endif
@@ -370,8 +369,7 @@ for (condition_def * c = conditions; c < conditions + nelem(conditions); c++)
 
 #ifndef MACRO_PREDEF
 
 
 #ifndef MACRO_PREDEF
 
-/* Return values from decode_control(); used as index so keep in step
-with the controls_list table that follows! */
+/* Return values from decode_control() */
 
 enum {
   CONTROL_AUTH_UNADVERTISED,
 
 enum {
   CONTROL_AUTH_UNADVERTISED,
@@ -411,6 +409,9 @@ enum {
 #ifdef SUPPORT_I18N
   CONTROL_UTF8_DOWNCONVERT,
 #endif
 #ifdef SUPPORT_I18N
   CONTROL_UTF8_DOWNCONVERT,
 #endif
+#ifndef DISABLE_WELLKNOWN
+  CONTROL_WELLKNOWN,
+#endif
 };
 
 
 };
 
 
@@ -564,7 +565,12 @@ static control_def controls_list[] = {
 #ifdef SUPPORT_I18N
 [CONTROL_UTF8_DOWNCONVERT] =
   { US"utf8_downconvert",        TRUE, (unsigned) ~(ACL_BIT_RCPT | ACL_BIT_VRFY)
 #ifdef SUPPORT_I18N
 [CONTROL_UTF8_DOWNCONVERT] =
   { US"utf8_downconvert",        TRUE, (unsigned) ~(ACL_BIT_RCPT | ACL_BIT_VRFY)
-  }
+  },
+#endif
+#ifndef DISABLE_WELLKNOWN
+[CONTROL_WELLKNOWN] =
+  { US"wellknown",               TRUE, (unsigned) ~ACL_BIT_WELLKNOWN
+  },
 #endif
 };
 
 #endif
 };
 
@@ -806,7 +812,7 @@ if (*s++ != '=')
   {
   *error = string_sprintf("\"=\" missing after ACL \"%s\" %s", name,
     conditions[cond->type].is_modifier ? US"modifier" : US"condition");
   {
   *error = string_sprintf("\"=\" missing after ACL \"%s\" %s", name,
     conditions[cond->type].is_modifier ? US"modifier" : US"condition");
-  return FALSE;;
+  return FALSE;
   }
 Uskip_whitespace(&s);
 cond->arg = taint ? string_copy_taint(s, GET_TAINTED) : string_copy(s);
   }
 Uskip_whitespace(&s);
 cond->arg = taint ? string_copy_taint(s, GET_TAINTED) : string_copy(s);
@@ -3122,6 +3128,80 @@ return DEFER;
 
 
 
 
 
 
+#ifndef DISABLE_WELLKNOWN
+/*************************************************
+*   The "wellknown" ACL modifier                 *
+*************************************************/
+
+/* Called by acl_check_condition() below.
+
+Retrieve the given file and encode content as xtext.
+Prefix with a summary line giving the length of plaintext.
+Leave a global pointer to the whole, for output by
+the smtp verb handler code (smtp_in.c).
+
+Arguments:
+  arg          the option string for wellknown=
+  log_msgptr   for error messages
+
+Returns:       OK/FAIL
+*/
+
+static int
+wellknown_process(const uschar * arg, uschar ** log_msgptr)
+{
+struct stat statbuf;
+FILE * rf;
+gstring * g;
+
+wellknown_response = NULL;
+if (f.no_multiline_responses) return FAIL;
+
+/* Check for file existence */
+
+if (!*arg) return FAIL;
+if (Ustat(arg, &statbuf) != 0)
+  { *log_msgptr = US"stat"; goto fail; }
+
+/*XXX perhaps refuse to serve a group- or world-writeable file? */
+
+if (!(rf = Ufopen(arg, "r")))
+  { *log_msgptr = US"open"; goto fail; }
+
+/* Set up summary line for output */
+
+g = string_fmt_append(NULL, "SIZE=%lu\n", (long) statbuf.st_size);
+
+#define LINE_LIM 75
+for (int n = 0, ch; (ch = fgetc(rf)) != EOF; )
+  {
+  /* Xtext-encode, adding output linebreaks for input linebreaks
+  or when the line gets long enough */
+
+  if (ch == '\n')
+    { g = string_fmt_append(g, "+%02X", ch); n = LINE_LIM; }
+  else if (ch < 33 || ch > 126 || ch == '+' || ch == '=')
+    { g = string_fmt_append(g, "+%02X", ch); n += 3; }
+  else
+    { g = string_fmt_append(g, "%c", ch); n++; }
+
+  if (n >= LINE_LIM)
+    { g = string_catn(g, US"\n", 1); n = 0; }
+  }
+#undef LINE_LIM
+
+gstring_release_unused(g);
+wellknown_response = string_from_gstring(g);
+return OK;
+
+fail:
+  *log_msgptr = string_sprintf("wellknown: failed to %s file \"%s\": %s",
+                 *log_msgptr, arg, strerror(errno));
+  return FAIL;
+}
+#endif
+
+
 /*************************************************
 *   Handle conditions/modifiers on an ACL item   *
 *************************************************/
 /*************************************************
 *   Handle conditions/modifiers on an ACL item   *
 *************************************************/
@@ -3311,7 +3391,7 @@ for (; cb; cb = cb->next)
 
     case ACLC_CONTROL:
       {
 
     case ACLC_CONTROL:
       {
-      const uschar *p = NULL;
+      const uschar * p = NULL;
       control_type = decode_control(arg, &p, where, log_msgptr);
 
       /* Check if this control makes sense at this time */
       control_type = decode_control(arg, &p, where, log_msgptr);
 
       /* Check if this control makes sense at this time */
@@ -3323,6 +3403,7 @@ for (; cb; cb = cb->next)
        return ERROR;
        }
 
        return ERROR;
        }
 
+      /*XXX ought to sort these, just for sanity */
       switch(control_type)
        {
        case CONTROL_AUTH_UNADVERTISED:
       switch(control_type)
        {
        case CONTROL_AUTH_UNADVERTISED:
@@ -3668,8 +3749,13 @@ for (; cb; cb = cb->next)
            break;
            }
          return ERROR;
            break;
            }
          return ERROR;
-#endif
+#endif /*I18N*/
 
 
+#ifndef DISABLE_WELLKNOWN
+       case CONTROL_WELLKNOWN:
+         rc = *p == '/' ? wellknown_process(p+1, log_msgptr) : FAIL;
+         break;
+#endif
        }
       break;
       }
        }
       break;
       }
index 75be18161f6d1685803f51686f29116f8b98c000..00f36e5442abcd1b7c6b921f081f6b9e3b1b2bef 100644 (file)
@@ -40,3 +40,5 @@ return string_from_gstring(g);
 
 
 /* End of xtextencode.c */
 
 
 /* End of xtextencode.c */
+/* vi: aw ai sw=2
+*/
index c08f1874d71f2b062491f75cefe745119e54690c..22749b174e60626cc678ee381c1aab4b4e6b56d1 100644 (file)
@@ -59,6 +59,7 @@ Do not put spaces between # and the 'define'.
 #define DISABLE_QUEUE_RAMP
 #define DISABLE_TLS
 #define DISABLE_TLS_RESUME
 #define DISABLE_QUEUE_RAMP
 #define DISABLE_TLS
 #define DISABLE_TLS_RESUME
+#define DISABLE_WELLKNOWN
 
 #define ENABLE_DISABLE_FSYNC
 
 
 #define ENABLE_DISABLE_FSYNC
 
index 040df2cd020aa2cd8b39a226b2ab6a2622e5aa4a..b78839b57ee90bcaa5999dec9049c6ca0470bcc2 100644 (file)
@@ -1108,6 +1108,9 @@ g = string_cat(g, US"Support for:");
 #ifndef DISABLE_ESMTP_LIMITS
   g = string_cat(g, US" ESMTP_Limits");
 #endif
 #ifndef DISABLE_ESMTP_LIMITS
   g = string_cat(g, US" ESMTP_Limits");
 #endif
+#ifndef DISABLE_WELLKNOWN
+  g = string_cat(g, US" ESMTP_Wellknown");
+#endif
 #ifndef DISABLE_EVENT
   g = string_cat(g, US" Event");
 #endif
 #ifndef DISABLE_EVENT
   g = string_cat(g, US" Event");
 #endif
index 1d121756da4db75df3a8e4f9b1873d11576d46ed..4bc680544dbf4cb573b40216d9436d0a5138c5d1 100644 (file)
@@ -259,7 +259,9 @@ static uschar *op_table_main[] = {
   US"strlen",
   US"substr",
   US"uc",
   US"strlen",
   US"substr",
   US"uc",
-  US"utf8clean" };
+  US"utf8clean",
+  US"xtextd",
+  };
 
 enum {
   EOP_ADDRESS =  nelem(op_table_underscore),
 
 enum {
   EOP_ADDRESS =  nelem(op_table_underscore),
@@ -307,7 +309,9 @@ enum {
   EOP_STRLEN,
   EOP_SUBSTR,
   EOP_UC,
   EOP_STRLEN,
   EOP_SUBSTR,
   EOP_UC,
-  EOP_UTF8CLEAN };
+  EOP_UTF8CLEAN,
+  EOP_XTEXTD,
+  };
 
 
 /* Table of condition names, and corresponding switch numbers. The names must
 
 
 /* Table of condition names, and corresponding switch numbers. The names must
@@ -7326,19 +7330,20 @@ NOT_ITEM: ;
 
       case EOP_LC:
        {
 
       case EOP_LC:
        {
-       int count = 0;
-       uschar *t = sub - 1;
-       while (*(++t) != 0) { *t = tolower(*t); count++; }
-       yield = string_catn(yield, sub, count);
+       uschar * t = sub - 1;
+       while (*++t) *t = tolower(*t);
+       yield = string_catn(yield, sub, t-sub);
        break;
        }
        break;
        }
+       {
+       uschar * s = sub;
+       }
 
       case EOP_UC:
        {
 
       case EOP_UC:
        {
-       int count = 0;
-       uschar *t = sub - 1;
-       while (*(++t) != 0) { *t = toupper(*t); count++; }
-       yield = string_catn(yield, sub, count);
+       uschar * t = sub - 1;
+       while (*++t) *t = toupper(*t);
+       yield = string_catn(yield, sub, t-sub);
        break;
        }
 
        break;
        }
 
@@ -7774,7 +7779,6 @@ NOT_ITEM: ;
            }
          else
            yield = string_cat(yield, sub);
            }
          else
            yield = string_cat(yield, sub);
-         break;
          }
 
        /* quote_lookuptype does lookup-specific quoting */
          }
 
        /* quote_lookuptype does lookup-specific quoting */
@@ -7806,526 +7810,533 @@ NOT_ITEM: ;
            }
 
          yield = string_cat(yield, sub);
            }
 
          yield = string_cat(yield, sub);
-         break;
          }
          }
+       break;
 
 
-       /* rx quote sticks in \ before any non-alphameric character so that
-       the insertion works in a regular expression. */
+      /* rx quote sticks in \ before any non-alphameric character so that
+      the insertion works in a regular expression. */
 
 
-       case EOP_RXQUOTE:
+      case EOP_RXQUOTE:
+       {
+       uschar *t = sub - 1;
+       while (*(++t) != 0)
          {
          {
-         uschar *t = sub - 1;
-         while (*(++t) != 0)
-           {
-           if (!isalnum(*t))
-             yield = string_catn(yield, US"\\", 1);
-           yield = string_catn(yield, t, 1);
-           }
-         break;
+         if (!isalnum(*t))
+           yield = string_catn(yield, US"\\", 1);
+         yield = string_catn(yield, t, 1);
          }
          }
+       break;
+       }
 
 
-       /* RFC 2047 encodes, assuming headers_charset (default ISO 8859-1) as
-       prescribed by the RFC, if there are characters that need to be encoded */
+      /* RFC 2047 encodes, assuming headers_charset (default ISO 8859-1) as
+      prescribed by the RFC, if there are characters that need to be encoded */
 
 
-       case EOP_RFC2047:
-         yield = string_cat(yield,
-                             parse_quote_2047(sub, Ustrlen(sub), headers_charset,
-                               FALSE));
-         break;
+      case EOP_RFC2047:
+       yield = string_cat(yield,
+                           parse_quote_2047(sub, Ustrlen(sub), headers_charset,
+                             FALSE));
+       break;
 
 
-       /* RFC 2047 decode */
+      /* RFC 2047 decode */
 
 
-       case EOP_RFC2047D:
+      case EOP_RFC2047D:
+       {
+       int len;
+       uschar *error;
+       uschar *decoded = rfc2047_decode(sub, check_rfc2047_length,
+         headers_charset, '?', &len, &error);
+       if (error)
          {
          {
-         int len;
-         uschar *error;
-         uschar *decoded = rfc2047_decode(sub, check_rfc2047_length,
-           headers_charset, '?', &len, &error);
-         if (error)
-           {
-           expand_string_message = error;
-           goto EXPAND_FAILED;
-           }
-         yield = string_catn(yield, decoded, len);
-         break;
+         expand_string_message = error;
+         goto EXPAND_FAILED;
          }
          }
+       yield = string_catn(yield, decoded, len);
+       break;
+       }
 
 
-       /* from_utf8 converts UTF-8 to 8859-1, turning non-existent chars into
-       underscores */
+      /* from_utf8 converts UTF-8 to 8859-1, turning non-existent chars into
+      underscores */
 
 
-       case EOP_FROM_UTF8:
+      case EOP_FROM_UTF8:
+       {
+       uschar * buff = store_get(4, sub);
+       while (*sub)
          {
          {
-         uschar * buff = store_get(4, sub);
-         while (*sub)
-           {
-           int c;
-           GETUTF8INC(c, sub);
-           if (c > 255) c = '_';
-           buff[0] = c;
-           yield = string_catn(yield, buff, 1);
-           }
-         break;
+         int c;
+         GETUTF8INC(c, sub);
+         if (c > 255) c = '_';
+         buff[0] = c;
+         yield = string_catn(yield, buff, 1);
          }
          }
+       break;
+       }
 
 
-       /* replace illegal UTF-8 sequences by replacement character  */
+      /* replace illegal UTF-8 sequences by replacement character  */
 
 
-       #define UTF8_REPLACEMENT_CHAR US"?"
+      #define UTF8_REPLACEMENT_CHAR US"?"
+
+      case EOP_UTF8CLEAN:
+       {
+       int seq_len = 0, index = 0, bytes_left = 0, complete;
+       u_long codepoint = (u_long)-1;
+       uschar seq_buff[4];                     /* accumulate utf-8 here */
 
 
-       case EOP_UTF8CLEAN:
+       /* Manually track tainting, as we deal in individual chars below */
+
+       if (!yield)
+         yield = string_get_tainted(Ustrlen(sub), sub);
+       else if (!yield->s || !yield->ptr)
          {
          {
-         int seq_len = 0, index = 0, bytes_left = 0, complete;
-         u_long codepoint = (u_long)-1;
-         uschar seq_buff[4];                   /* accumulate utf-8 here */
+         yield->s = store_get(yield->size = Ustrlen(sub), sub);
+         gstring_reset(yield);
+         }
+       else if (is_incompatible(yield->s, sub))
+         gstring_rebuffer(yield, sub);
 
 
-         /* Manually track tainting, as we deal in individual chars below */
+       /* Check the UTF-8, byte-by-byte */
+
+       while (*sub)
+         {
+         complete = 0;
+         uschar c = *sub++;
 
 
-         if (!yield)
-           yield = string_get_tainted(Ustrlen(sub), sub);
-         else if (!yield->s || !yield->ptr)
+         if (bytes_left)
            {
            {
-           yield->s = store_get(yield->size = Ustrlen(sub), sub);
-           gstring_reset(yield);
+           if ((c & 0xc0) != 0x80)
+                   /* wrong continuation byte; invalidate all bytes */
+             complete = 1; /* error */
+           else
+             {
+             codepoint = (codepoint << 6) | (c & 0x3f);
+             seq_buff[index++] = c;
+             if (--bytes_left == 0)            /* codepoint complete */
+               if(codepoint > 0x10FFFF)        /* is it too large? */
+                 complete = -1;        /* error (RFC3629 limit) */
+               else if ( (codepoint & 0x1FF800 ) == 0xD800 ) /* surrogate */
+                 /* A UTF-16 surrogate (which should be one of a pair that
+                 encode a Unicode codepoint that is outside the Basic
+                 Multilingual Plane).  Error, not UTF8.
+                 RFC2279.2 is slightly unclear on this, but 
+                 https://unicodebook.readthedocs.io/issues.html#strict-utf8-decoder
+                 says "Surrogates characters are also invalid in UTF-8:
+                 characters in U+D800—U+DFFF have to be rejected." */
+                 complete = -1;
+               else
+                 {             /* finished; output utf-8 sequence */
+                 yield = string_catn(yield, seq_buff, seq_len);
+                 index = 0;
+                 }
+             }
            }
            }
-         else if (is_incompatible(yield->s, sub))
-           gstring_rebuffer(yield, sub);
-
-         /* Check the UTF-8, byte-by-byte */
-
-         while (*sub)
+         else  /* no bytes left: new sequence */
            {
            {
-           complete = 0;
-           uschar c = *sub++;
-
-           if (bytes_left)
+           if (!(c & 0x80))    /* 1-byte sequence, US-ASCII, keep it */
              {
              {
-             if ((c & 0xc0) != 0x80)
-                     /* wrong continuation byte; invalidate all bytes */
-               complete = 1; /* error */
+             yield = string_catn(yield, &c, 1);
+             continue;
+             }
+           if ((c & 0xe0) == 0xc0)             /* 2-byte sequence */
+             if (c == 0xc0 || c == 0xc1)       /* 0xc0 and 0xc1 are illegal */
+               complete = -1;
              else
                {
              else
                {
-               codepoint = (codepoint << 6) | (c & 0x3f);
-               seq_buff[index++] = c;
-               if (--bytes_left == 0)          /* codepoint complete */
-                 if(codepoint > 0x10FFFF)      /* is it too large? */
-                   complete = -1;      /* error (RFC3629 limit) */
-                 else if ( (codepoint & 0x1FF800 ) == 0xD800 ) /* surrogate */
-                   /* A UTF-16 surrogate (which should be one of a pair that
-                   encode a Unicode codepoint that is outside the Basic
-                   Multilingual Plane).  Error, not UTF8.
-                   RFC2279.2 is slightly unclear on this, but 
-                   https://unicodebook.readthedocs.io/issues.html#strict-utf8-decoder
-                   says "Surrogates characters are also invalid in UTF-8:
-                   characters in U+D800—U+DFFF have to be rejected." */
-                   complete = -1;
-                 else
-                   {           /* finished; output utf-8 sequence */
-                   yield = string_catn(yield, seq_buff, seq_len);
-                   index = 0;
-                   }
+               bytes_left = 1;
+               codepoint = c & 0x1f;
                }
                }
+           else if ((c & 0xf0) == 0xe0)                /* 3-byte sequence */
+             {
+             bytes_left = 2;
+             codepoint = c & 0x0f;
              }
              }
-           else        /* no bytes left: new sequence */
+           else if ((c & 0xf8) == 0xf0)                /* 4-byte sequence */
              {
              {
-             if (!(c & 0x80))  /* 1-byte sequence, US-ASCII, keep it */
-               {
-               yield = string_catn(yield, &c, 1);
-               continue;
-               }
-             if ((c & 0xe0) == 0xc0)           /* 2-byte sequence */
-               if (c == 0xc0 || c == 0xc1)     /* 0xc0 and 0xc1 are illegal */
-                 complete = -1;
-               else
-                 {
-                 bytes_left = 1;
-                 codepoint = c & 0x1f;
-                 }
-             else if ((c & 0xf0) == 0xe0)              /* 3-byte sequence */
-               {
-               bytes_left = 2;
-               codepoint = c & 0x0f;
-               }
-             else if ((c & 0xf8) == 0xf0)              /* 4-byte sequence */
-               {
-               bytes_left = 3;
-               codepoint = c & 0x07;
-               }
-             else      /* invalid or too long (RFC3629 allows only 4 bytes) */
-               complete = -1;
+             bytes_left = 3;
+             codepoint = c & 0x07;
+             }
+           else        /* invalid or too long (RFC3629 allows only 4 bytes) */
+             complete = -1;
 
 
-             seq_buff[index++] = c;
-             seq_len = bytes_left + 1;
-             }         /* if(bytes_left) */
+           seq_buff[index++] = c;
+           seq_len = bytes_left + 1;
+           }           /* if(bytes_left) */
 
 
-           if (complete != 0)
-             {
-             bytes_left = index = 0;
-             yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1);
-             }
-           if ((complete == 1) && ((c & 0x80) == 0))
-                         /* ASCII character follows incomplete sequence */
-               yield = string_catn(yield, &c, 1);
+         if (complete != 0)
+           {
+           bytes_left = index = 0;
+           yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1);
            }
            }
-         /* If given a sequence truncated mid-character, we also want to report ?
-         Eg, ${length_1:フィル} is one byte, not one character, so we expect
-         ${utf8clean:${length_1:フィル}} to yield '?' */
+         if ((complete == 1) && ((c & 0x80) == 0))
+                       /* ASCII character follows incomplete sequence */
+             yield = string_catn(yield, &c, 1);
+         }
+       /* If given a sequence truncated mid-character, we also want to report ?
+       Eg, ${length_1:フィル} is one byte, not one character, so we expect
+       ${utf8clean:${length_1:フィル}} to yield '?' */
 
 
-         if (bytes_left != 0)
-           yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1);
+       if (bytes_left != 0)
+         yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1);
 
 
-         break;
-         }
+       break;
+       }
 
 #ifdef SUPPORT_I18N
 
 #ifdef SUPPORT_I18N
-       case EOP_UTF8_DOMAIN_TO_ALABEL:
+      case EOP_UTF8_DOMAIN_TO_ALABEL:
+       {
+       uschar * error = NULL;
+       uschar * s = string_domain_utf8_to_alabel(sub, &error);
+       if (error)
          {
          {
-         uschar * error = NULL;
-         uschar * s = string_domain_utf8_to_alabel(sub, &error);
-         if (error)
-           {
-           expand_string_message = string_sprintf(
-             "error converting utf8 (%s) to alabel: %s",
-             string_printing(sub), error);
-           goto EXPAND_FAILED;
-           }
-         yield = string_cat(yield, s);
-         break;
+         expand_string_message = string_sprintf(
+           "error converting utf8 (%s) to alabel: %s",
+           string_printing(sub), error);
+         goto EXPAND_FAILED;
          }
          }
+       yield = string_cat(yield, s);
+       break;
+       }
 
 
-       case EOP_UTF8_DOMAIN_FROM_ALABEL:
+      case EOP_UTF8_DOMAIN_FROM_ALABEL:
+       {
+       uschar * error = NULL;
+       uschar * s = string_domain_alabel_to_utf8(sub, &error);
+       if (error)
          {
          {
-         uschar * error = NULL;
-         uschar * s = string_domain_alabel_to_utf8(sub, &error);
-         if (error)
-           {
-           expand_string_message = string_sprintf(
-             "error converting alabel (%s) to utf8: %s",
-             string_printing(sub), error);
-           goto EXPAND_FAILED;
-           }
-         yield = string_cat(yield, s);
-         break;
+         expand_string_message = string_sprintf(
+           "error converting alabel (%s) to utf8: %s",
+           string_printing(sub), error);
+         goto EXPAND_FAILED;
          }
          }
+       yield = string_cat(yield, s);
+       break;
+       }
 
 
-       case EOP_UTF8_LOCALPART_TO_ALABEL:
+      case EOP_UTF8_LOCALPART_TO_ALABEL:
+       {
+       uschar * error = NULL;
+       uschar * s = string_localpart_utf8_to_alabel(sub, &error);
+       if (error)
          {
          {
-         uschar * error = NULL;
-         uschar * s = string_localpart_utf8_to_alabel(sub, &error);
-         if (error)
-           {
-           expand_string_message = string_sprintf(
-             "error converting utf8 (%s) to alabel: %s",
-             string_printing(sub), error);
-           goto EXPAND_FAILED;
-           }
-         yield = string_cat(yield, s);
-         DEBUG(D_expand) debug_printf_indent("yield: '%Y'\n", yield);
-         break;
+         expand_string_message = string_sprintf(
+           "error converting utf8 (%s) to alabel: %s",
+           string_printing(sub), error);
+         goto EXPAND_FAILED;
          }
          }
+       yield = string_cat(yield, s);
+       DEBUG(D_expand) debug_printf_indent("yield: '%Y'\n", yield);
+       break;
+       }
 
 
-       case EOP_UTF8_LOCALPART_FROM_ALABEL:
+      case EOP_UTF8_LOCALPART_FROM_ALABEL:
+       {
+       uschar * error = NULL;
+       uschar * s = string_localpart_alabel_to_utf8(sub, &error);
+       if (error)
          {
          {
-         uschar * error = NULL;
-         uschar * s = string_localpart_alabel_to_utf8(sub, &error);
-         if (error)
-           {
-           expand_string_message = string_sprintf(
-             "error converting alabel (%s) to utf8: %s",
-             string_printing(sub), error);
-           goto EXPAND_FAILED;
-           }
-         yield = string_cat(yield, s);
-         break;
+         expand_string_message = string_sprintf(
+           "error converting alabel (%s) to utf8: %s",
+           string_printing(sub), error);
+         goto EXPAND_FAILED;
          }
          }
+       yield = string_cat(yield, s);
+       break;
+       }
 #endif /* EXPERIMENTAL_INTERNATIONAL */
 
 #endif /* EXPERIMENTAL_INTERNATIONAL */
 
-       /* escape turns all non-printing characters into escape sequences. */
+      /* escape turns all non-printing characters into escape sequences. */
 
 
-       case EOP_ESCAPE:
-         {
-         const uschar * t = string_printing(sub);
-         yield = string_cat(yield, t);
-         break;
-         }
+      case EOP_ESCAPE:
+       {
+       const uschar * t = string_printing(sub);
+       yield = string_cat(yield, t);
+       break;
+       }
 
 
-       case EOP_ESCAPE8BIT:
-         {
-         uschar c;
+      case EOP_ESCAPE8BIT:
+       {
+       uschar c;
 
 
-         for (const uschar * s = sub; (c = *s); s++)
-           yield = c < 127 && c != '\\'
-             ? string_catn(yield, s, 1)
-             : string_fmt_append(yield, "\\%03o", c);
-         break;
-         }
+       for (const uschar * s = sub; (c = *s); s++)
+         yield = c < 127 && c != '\\'
+           ? string_catn(yield, s, 1)
+           : string_fmt_append(yield, "\\%03o", c);
+       break;
+       }
 
 
-       /* Handle numeric expression evaluation */
+      /* Handle numeric expression evaluation */
 
 
-       case EOP_EVAL:
-       case EOP_EVAL10:
+      case EOP_EVAL:
+      case EOP_EVAL10:
+       {
+       uschar *save_sub = sub;
+       uschar *error = NULL;
+       int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
+       if (error)
          {
          {
-         uschar *save_sub = sub;
-         uschar *error = NULL;
-         int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
-         if (error)
-           {
-           expand_string_message = string_sprintf("error in expression "
-             "evaluation: %s (after processing \"%.*s\")", error,
-             (int)(sub-save_sub), save_sub);
-           goto EXPAND_FAILED;
-           }
-         yield = string_fmt_append(yield, PR_EXIM_ARITH, n);
-         break;
+         expand_string_message = string_sprintf("error in expression "
+           "evaluation: %s (after processing \"%.*s\")", error,
+           (int)(sub-save_sub), save_sub);
+         goto EXPAND_FAILED;
          }
          }
+       yield = string_fmt_append(yield, PR_EXIM_ARITH, n);
+       break;
+       }
 
 
-       /* Handle time period formatting */
+      /* Handle time period formatting */
 
 
-       case EOP_TIME_EVAL:
+      case EOP_TIME_EVAL:
+       {
+       int n = readconf_readtime(sub, 0, FALSE);
+       if (n < 0)
          {
          {
-         int n = readconf_readtime(sub, 0, FALSE);
-         if (n < 0)
-           {
-           expand_string_message = string_sprintf("string \"%s\" is not an "
-             "Exim time interval in \"%s\" operator", sub, name);
-           goto EXPAND_FAILED;
-           }
-         yield = string_fmt_append(yield, "%d", n);
-         break;
+         expand_string_message = string_sprintf("string \"%s\" is not an "
+           "Exim time interval in \"%s\" operator", sub, name);
+         goto EXPAND_FAILED;
          }
          }
+       yield = string_fmt_append(yield, "%d", n);
+       break;
+       }
 
 
-       case EOP_TIME_INTERVAL:
+      case EOP_TIME_INTERVAL:
+       {
+       int n;
+       uschar *t = read_number(&n, sub);
+       if (*t != 0) /* Not A Number*/
          {
          {
-         int n;
-         uschar *t = read_number(&n, sub);
-         if (*t != 0) /* Not A Number*/
-           {
-           expand_string_message = string_sprintf("string \"%s\" is not a "
-             "positive number in \"%s\" operator", sub, name);
-           goto EXPAND_FAILED;
-           }
-         t = readconf_printtime(n);
-         yield = string_cat(yield, t);
-         break;
+         expand_string_message = string_sprintf("string \"%s\" is not a "
+           "positive number in \"%s\" operator", sub, name);
+         goto EXPAND_FAILED;
          }
          }
+       t = readconf_printtime(n);
+       yield = string_cat(yield, t);
+       break;
+       }
 
 
-       /* Convert string to base64 encoding */
+      /* Convert string to base64 encoding */
 
 
-       case EOP_STR2B64:
-       case EOP_BASE64:
-         {
+      case EOP_STR2B64:
+      case EOP_BASE64:
+       {
 #ifndef DISABLE_TLS
 #ifndef DISABLE_TLS
-         uschar * s = vp && *(void **)vp->value
-           ? tls_cert_der_b64(*(void **)vp->value)
-           : b64encode(CUS sub, Ustrlen(sub));
+       uschar * s = vp && *(void **)vp->value
+         ? tls_cert_der_b64(*(void **)vp->value)
+         : b64encode(CUS sub, Ustrlen(sub));
 #else
 #else
-         uschar * s = b64encode(CUS sub, Ustrlen(sub));
+       uschar * s = b64encode(CUS sub, Ustrlen(sub));
 #endif
 #endif
-         yield = string_cat(yield, s);
-         break;
-         }
+       yield = string_cat(yield, s);
+       break;
+       }
 
 
-       case EOP_BASE64D:
+      case EOP_BASE64D:
+       {
+       uschar * s;
+       int len = b64decode(sub, &s, sub);
+       if (len < 0)
          {
          {
-         uschar * s;
-         int len = b64decode(sub, &s, sub);
-         if (len < 0)
-           {
-           expand_string_message = string_sprintf("string \"%s\" is not "
-             "well-formed for \"%s\" operator", sub, name);
-           goto EXPAND_FAILED;
-           }
-         yield = string_cat(yield, s);
-         break;
+         expand_string_message = string_sprintf("string \"%s\" is not "
+           "well-formed for \"%s\" operator", sub, name);
+         goto EXPAND_FAILED;
          }
          }
+       yield = string_cat(yield, s);
+       break;
+       }
 
 
-       /* strlen returns the length of the string */
+      /* strlen returns the length of the string */
 
 
-       case EOP_STRLEN:
-         yield = string_fmt_append(yield, "%d", Ustrlen(sub));
-         break;
+      case EOP_STRLEN:
+       yield = string_fmt_append(yield, "%d", Ustrlen(sub));
+       break;
+
+      /* length_n or l_n takes just the first n characters or the whole string,
+      whichever is the shorter;
+
+      substr_m_n, and s_m_n take n characters from offset m; negative m take
+      from the end; l_n is synonymous with s_0_n. If n is omitted in substr it
+      takes the rest, either to the right or to the left.
+
+      hash_n or h_n makes a hash of length n from the string, yielding n
+      characters from the set a-z; hash_n_m makes a hash of length n, but
+      uses m characters from the set a-zA-Z0-9.
+
+      nhash_n returns a single number between 0 and n-1 (in text form), while
+      nhash_n_m returns a div/mod hash as two numbers "a/b". The first lies
+      between 0 and n-1 and the second between 0 and m-1. */
+
+      case EOP_LENGTH:
+      case EOP_L:
+      case EOP_SUBSTR:
+      case EOP_S:
+      case EOP_HASH:
+      case EOP_H:
+      case EOP_NHASH:
+      case EOP_NH:
+       {
+       int sign = 1;
+       int value1 = 0;
+       int value2 = -1;
+       int *pn;
+       int len;
+       uschar *ret;
 
 
-       /* length_n or l_n takes just the first n characters or the whole string,
-       whichever is the shorter;
-
-       substr_m_n, and s_m_n take n characters from offset m; negative m take
-       from the end; l_n is synonymous with s_0_n. If n is omitted in substr it
-       takes the rest, either to the right or to the left.
-
-       hash_n or h_n makes a hash of length n from the string, yielding n
-       characters from the set a-z; hash_n_m makes a hash of length n, but
-       uses m characters from the set a-zA-Z0-9.
-
-       nhash_n returns a single number between 0 and n-1 (in text form), while
-       nhash_n_m returns a div/mod hash as two numbers "a/b". The first lies
-       between 0 and n-1 and the second between 0 and m-1. */
-
-       case EOP_LENGTH:
-       case EOP_L:
-       case EOP_SUBSTR:
-       case EOP_S:
-       case EOP_HASH:
-       case EOP_H:
-       case EOP_NHASH:
-       case EOP_NH:
+       if (!arg)
          {
          {
-         int sign = 1;
-         int value1 = 0;
-         int value2 = -1;
-         int *pn;
-         int len;
-         uschar *ret;
+         expand_string_message = string_sprintf("missing values after %s",
+           name);
+         goto EXPAND_FAILED;
+         }
 
 
-         if (!arg)
-           {
-           expand_string_message = string_sprintf("missing values after %s",
-             name);
-           goto EXPAND_FAILED;
-           }
+       /* "length" has only one argument, effectively being synonymous with
+       substr_0_n. */
 
 
-         /* "length" has only one argument, effectively being synonymous with
-         substr_0_n. */
+       if (c == EOP_LENGTH || c == EOP_L)
+         {
+         pn = &value2;
+         value2 = 0;
+         }
 
 
-         if (c == EOP_LENGTH || c == EOP_L)
+       /* The others have one or two arguments; for "substr" the first may be
+       negative. The second being negative means "not supplied". */
+
+       else
+         {
+         pn = &value1;
+         if (name[0] == 's' && *arg == '-') { sign = -1; arg++; }
+         }
+
+       /* Read up to two numbers, separated by underscores */
+
+       ret = arg;
+       while (*arg != 0)
+         {
+         if (arg != ret && *arg == '_' && pn == &value1)
            {
            pn = &value2;
            value2 = 0;
            {
            pn = &value2;
            value2 = 0;
+           if (arg[1] != 0) arg++;
            }
            }
-
-         /* The others have one or two arguments; for "substr" the first may be
-         negative. The second being negative means "not supplied". */
-
-         else
+         else if (!isdigit(*arg))
            {
            {
-           pn = &value1;
-           if (name[0] == 's' && *arg == '-') { sign = -1; arg++; }
+           expand_string_message =
+             string_sprintf("non-digit after underscore in \"%s\"", name);
+           goto EXPAND_FAILED;
            }
            }
+         else *pn = (*pn)*10 + *arg++ - '0';
+         }
+       value1 *= sign;
 
 
-         /* Read up to two numbers, separated by underscores */
-
-         ret = arg;
-         while (*arg != 0)
-           {
-           if (arg != ret && *arg == '_' && pn == &value1)
-             {
-             pn = &value2;
-             value2 = 0;
-             if (arg[1] != 0) arg++;
-             }
-           else if (!isdigit(*arg))
-             {
-             expand_string_message =
-               string_sprintf("non-digit after underscore in \"%s\"", name);
-             goto EXPAND_FAILED;
-             }
-           else *pn = (*pn)*10 + *arg++ - '0';
-           }
-         value1 *= sign;
+       /* Perform the required operation */
 
 
-         /* Perform the required operation */
+       ret = c == EOP_HASH || c == EOP_H
+         ? compute_hash(sub, value1, value2, &len)
+         : c == EOP_NHASH || c == EOP_NH
+         ? compute_nhash(sub, value1, value2, &len)
+         : extract_substr(sub, value1, value2, &len);
+       if (!ret) goto EXPAND_FAILED;
 
 
-         ret = c == EOP_HASH || c == EOP_H
-           ? compute_hash(sub, value1, value2, &len)
-           : c == EOP_NHASH || c == EOP_NH
-           ? compute_nhash(sub, value1, value2, &len)
-           : extract_substr(sub, value1, value2, &len);
-         if (!ret) goto EXPAND_FAILED;
+       yield = string_catn(yield, ret, len);
+       break;
+       }
 
 
-         yield = string_catn(yield, ret, len);
-         break;
-         }
+      /* Stat a path */
 
 
-       /* Stat a path */
+      case EOP_STAT:
+       {
+       uschar smode[12];
+       uschar **modetable[3];
+       mode_t mode;
+       struct stat st;
 
 
-       case EOP_STAT:
+       if (expand_forbid & RDO_EXISTS)
          {
          {
-         uschar smode[12];
-         uschar **modetable[3];
-         mode_t mode;
-         struct stat st;
+         expand_string_message = US"Use of the stat() expansion is not permitted";
+         goto EXPAND_FAILED;
+         }
 
 
-         if (expand_forbid & RDO_EXISTS)
-           {
-           expand_string_message = US"Use of the stat() expansion is not permitted";
-           goto EXPAND_FAILED;
-           }
+       if (stat(CS sub, &st) < 0)
+         {
+         expand_string_message = string_sprintf("stat(%s) failed: %s",
+           sub, strerror(errno));
+         goto EXPAND_FAILED;
+         }
+       mode = st.st_mode;
+       switch (mode & S_IFMT)
+         {
+         case S_IFIFO: smode[0] = 'p'; break;
+         case S_IFCHR: smode[0] = 'c'; break;
+         case S_IFDIR: smode[0] = 'd'; break;
+         case S_IFBLK: smode[0] = 'b'; break;
+         case S_IFREG: smode[0] = '-'; break;
+         default: smode[0] = '?'; break;
+         }
 
 
-         if (stat(CS sub, &st) < 0)
-           {
-           expand_string_message = string_sprintf("stat(%s) failed: %s",
-             sub, strerror(errno));
-           goto EXPAND_FAILED;
-           }
-         mode = st.st_mode;
-         switch (mode & S_IFMT)
-           {
-           case S_IFIFO: smode[0] = 'p'; break;
-           case S_IFCHR: smode[0] = 'c'; break;
-           case S_IFDIR: smode[0] = 'd'; break;
-           case S_IFBLK: smode[0] = 'b'; break;
-           case S_IFREG: smode[0] = '-'; break;
-           default: smode[0] = '?'; break;
-           }
+       modetable[0] = ((mode & 01000) == 0)? mtable_normal : mtable_sticky;
+       modetable[1] = ((mode & 02000) == 0)? mtable_normal : mtable_setid;
+       modetable[2] = ((mode & 04000) == 0)? mtable_normal : mtable_setid;
 
 
-         modetable[0] = ((mode & 01000) == 0)? mtable_normal : mtable_sticky;
-         modetable[1] = ((mode & 02000) == 0)? mtable_normal : mtable_setid;
-         modetable[2] = ((mode & 04000) == 0)? mtable_normal : mtable_setid;
+       for (int i = 0; i < 3; i++)
+         {
+         memcpy(CS(smode + 7 - i*3), CS(modetable[i][mode & 7]), 3);
+         mode >>= 3;
+         }
 
 
-         for (int i = 0; i < 3; i++)
-           {
-           memcpy(CS(smode + 7 - i*3), CS(modetable[i][mode & 7]), 3);
-           mode >>= 3;
-           }
+       smode[10] = 0;
+       yield = string_fmt_append(yield,
+         "mode=%04lo smode=%s inode=%ld device=%ld links=%ld "
+         "uid=%ld gid=%ld size=" OFF_T_FMT " atime=%ld mtime=%ld ctime=%ld",
+         (long)(st.st_mode & 077777), smode, (long)st.st_ino,
+         (long)st.st_dev, (long)st.st_nlink, (long)st.st_uid,
+         (long)st.st_gid, st.st_size, (long)st.st_atime,
+         (long)st.st_mtime, (long)st.st_ctime);
+       break;
+       }
 
 
-         smode[10] = 0;
-         yield = string_fmt_append(yield,
-           "mode=%04lo smode=%s inode=%ld device=%ld links=%ld "
-           "uid=%ld gid=%ld size=" OFF_T_FMT " atime=%ld mtime=%ld ctime=%ld",
-           (long)(st.st_mode & 077777), smode, (long)st.st_ino,
-           (long)st.st_dev, (long)st.st_nlink, (long)st.st_uid,
-           (long)st.st_gid, st.st_size, (long)st.st_atime,
-           (long)st.st_mtime, (long)st.st_ctime);
-         break;
-         }
+      /* vaguely random number less than N */
 
 
-       /* vaguely random number less than N */
+      case EOP_RANDINT:
+       {
+       int_eximarith_t max = expanded_string_integer(sub, TRUE);
 
 
-       case EOP_RANDINT:
-         {
-         int_eximarith_t max = expanded_string_integer(sub, TRUE);
+       if (expand_string_message)
+         goto EXPAND_FAILED;
+       yield = string_fmt_append(yield, "%d", vaguely_random_number((int)max));
+       break;
+       }
 
 
-         if (expand_string_message)
-           goto EXPAND_FAILED;
-         yield = string_fmt_append(yield, "%d", vaguely_random_number((int)max));
-         break;
-         }
+      /* Reverse IP, including IPv6 to dotted-nibble */
 
 
-       /* Reverse IP, including IPv6 to dotted-nibble */
+      case EOP_REVERSE_IP:
+       {
+       int family, maskptr;
+       uschar reversed[128];
 
 
-       case EOP_REVERSE_IP:
+       family = string_is_ip_address(sub, &maskptr);
+       if (family == 0)
          {
          {
-         int family, maskptr;
-         uschar reversed[128];
-
-         family = string_is_ip_address(sub, &maskptr);
-         if (family == 0)
-           {
-           expand_string_message = string_sprintf(
-               "reverse_ip() not given an IP address [%s]", sub);
-           goto EXPAND_FAILED;
-           }
-         invert_address(reversed, sub);
-         yield = string_cat(yield, reversed);
-         break;
+         expand_string_message = string_sprintf(
+             "reverse_ip() not given an IP address [%s]", sub);
+         goto EXPAND_FAILED;
          }
          }
+       invert_address(reversed, sub);
+       yield = string_cat(yield, reversed);
+       break;
+       }
 
 
-       /* Unknown operator */
+      case EOP_XTEXTD:
+       {
+       uschar * s;
+       int len = auth_xtextdecode(sub, &s);
+       yield = string_catn(yield, s, len);
+       break;
+       }
 
 
-       default:
-         expand_string_message =
-           string_sprintf("unknown expansion operator \"%s\"", name);
-         goto EXPAND_FAILED;
-       }       /* EOP_* switch */
+      /* Unknown operator */
+      default:
+       expand_string_message =
+         string_sprintf("unknown expansion operator \"%s\"", name);
+       goto EXPAND_FAILED;
+      }        /* EOP_* switch */
 
 
-       DEBUG(D_expand)
+      DEBUG(D_expand)
        {
        const uschar * res = string_from_gstring(yield);
        const uschar * s = res + expansion_start;
        {
        const uschar * res = string_from_gstring(yield);
        const uschar * s = res + expansion_start;
index 4e5fd2991db21e44d63e69d151085192db00a150..8230fe9cb9c7a7ce9697614c9eab4689f033d87f 100644 (file)
@@ -467,7 +467,7 @@ uschar *acl_smtp_quit          = NULL;
 uschar *acl_smtp_rcpt          = NULL;
 uschar *acl_smtp_starttls      = NULL;
 uschar *acl_smtp_vrfy          = NULL;
 uschar *acl_smtp_rcpt          = NULL;
 uschar *acl_smtp_starttls      = NULL;
 uschar *acl_smtp_vrfy          = NULL;
-#ifdef EXPERIMENTAL_WELLKNOWN
+#ifndef DISABLE_WELLKNOWN
 uschar *acl_smtp_wellknown     = NULL;
 #endif
 
 uschar *acl_smtp_wellknown     = NULL;
 #endif
 
@@ -500,6 +500,9 @@ uschar *acl_wherenames[]       = { [ACL_WHERE_RCPT] =               US"RCPT",
                                    [ACL_WHERE_QUIT] =          US"QUIT",
                                    [ACL_WHERE_STARTTLS] =      US"STARTTLS",
                                    [ACL_WHERE_VRFY] =          US"VRFY",
                                    [ACL_WHERE_QUIT] =          US"QUIT",
                                    [ACL_WHERE_STARTTLS] =      US"STARTTLS",
                                    [ACL_WHERE_VRFY] =          US"VRFY",
+#ifndef DISABLE_WELLKNOWN
+                                  [ACL_WHERE_WELLKNOWN] =      US"WELLKNOWN",
+#endif
                                   [ACL_WHERE_DELIVERY] =       US"delivery",
                                   [ACL_WHERE_UNKNOWN] =        US"unknown"
                                  };
                                   [ACL_WHERE_DELIVERY] =       US"delivery",
                                   [ACL_WHERE_UNKNOWN] =        US"unknown"
                                  };
@@ -519,6 +522,9 @@ uschar *acl_wherecodes[]       = { [ACL_WHERE_RCPT] =       US"550",
                                    [ACL_WHERE_EXPN] =  US"550",
                                    [ACL_WHERE_HELO] =  US"550",
                                    [ACL_WHERE_STARTTLS] = US"550",
                                    [ACL_WHERE_EXPN] =  US"550",
                                    [ACL_WHERE_HELO] =  US"550",
                                    [ACL_WHERE_STARTTLS] = US"550",
+#ifndef DISABLE_WELLKNOWN
+                                   [ACL_WHERE_WELLKNOWN] =US"550",
+#endif
                                    [ACL_WHERE_VRFY] =  US"252",
                                  };
 
                                    [ACL_WHERE_VRFY] =  US"252",
                                  };
 
@@ -999,9 +1005,6 @@ uschar *hosts_proxy            = NULL;
 #endif
 uschar *hosts_treat_as_local   = NULL;
 uschar *hosts_require_helo     = US"*";
 #endif
 uschar *hosts_treat_as_local   = NULL;
 uschar *hosts_require_helo     = US"*";
-#ifdef EXPERIMENTAL_WELLKNOWN
-uschar *hosts_wellknown               = NULL;
-#endif
 #ifdef EXPERIMENTAL_XCLIENT
 uschar *hosts_xclient         = NULL;
 #endif
 #ifdef EXPERIMENTAL_XCLIENT
 uschar *hosts_xclient         = NULL;
 #endif
@@ -1669,6 +1672,11 @@ int     warning_count          = 0;
 const uschar *warnmsg_delay    = NULL;
 const uschar *warnmsg_recipients = NULL;
 
 const uschar *warnmsg_delay    = NULL;
 const uschar *warnmsg_recipients = NULL;
 
+#ifndef DISABLE_WELLKNOWN
+uschar *wellknown_advertise_hosts = NULL;
+uschar *wellknown_response     = NULL;
+#endif
+
 /*  End of globals.c */
 /* vi: aw ai sw=2
 */
 /*  End of globals.c */
 /* vi: aw ai sw=2
 */
index 30c8bbad42076e0862e9ef7bac8cbc023a219230..febc30c8429664d30c23d6006916b94074217156 100644 (file)
@@ -342,6 +342,9 @@ extern uschar *acl_smtp_quit;          /* ACL run for QUIT */
 extern uschar *acl_smtp_rcpt;          /* ACL run for RCPT */
 extern uschar *acl_smtp_starttls;      /* ACL run for STARTTLS */
 extern uschar *acl_smtp_vrfy;          /* ACL run for VRFY */
 extern uschar *acl_smtp_rcpt;          /* ACL run for RCPT */
 extern uschar *acl_smtp_starttls;      /* ACL run for STARTTLS */
 extern uschar *acl_smtp_vrfy;          /* ACL run for VRFY */
+#ifndef DISABLE_WELLKNOWN
+extern uschar *acl_smtp_wellknown;     /* ACL run for WELLKNOWN */
+#endif
 extern tree_node *acl_var_c;           /* ACL connection variables */
 extern tree_node *acl_var_m;           /* ACL message variables */
 extern uschar *acl_verify_message;     /* User message for verify failure */
 extern tree_node *acl_var_c;           /* ACL connection variables */
 extern tree_node *acl_var_m;           /* ACL message variables */
 extern uschar *acl_verify_message;     /* User message for verify failure */
@@ -1134,4 +1137,9 @@ extern uschar *version_string;         /* Version string */
 
 extern int     warning_count;          /* Delay warnings sent for this msg */
 
 
 extern int     warning_count;          /* Delay warnings sent for this msg */
 
+#ifndef DISABLE_WELLKNOWN
+extern uschar *wellknown_advertise_hosts;/* Allow WELLKNOWN command for specified hosts */
+extern uschar *wellknown_response;     /* SMTP response for WELLKNOWN verb */
+#endif
+
 /* End of globals.h */
 /* End of globals.h */
index 9b354d345f57e34d81654548775f5775407d3b01..55401e316e18fabd121cc37475b931ed21d57a26 100644 (file)
@@ -214,6 +214,9 @@ due to conflicts with other common macros. */
 #ifndef DISABLE_TLS_RESUME
   builtin_macro_create(US"_HAVE_TLS_RESUME");
 #endif
 #ifndef DISABLE_TLS_RESUME
   builtin_macro_create(US"_HAVE_TLS_RESUME");
 #endif
+#ifndef DISABLE_WELLKNOWN
+  builtin_macro_create(US"_HAVE_WELLKNOWN");
+#endif
 #ifdef EXPERIMENTAL_XCLIENT
   builtin_macro_create(US"_HAVE_XCLIENT");
 #endif
 #ifdef EXPERIMENTAL_XCLIENT
   builtin_macro_create(US"_HAVE_XCLIENT");
 #endif
index 2938b2523536dea369da92d749eaa029de9b4189..16d1503f28a728ce6b68075d3dda57e979d3a1a8 100644 (file)
@@ -828,6 +828,9 @@ enum { SCH_NONE, SCH_AUTH, SCH_DATA, SCH_BDAT,
        SCH_EHLO, SCH_ETRN, SCH_EXPN, SCH_HELO,
        SCH_HELP, SCH_MAIL, SCH_NOOP, SCH_QUIT, SCH_RCPT, SCH_RSET, SCH_STARTTLS,
        SCH_VRFY,
        SCH_EHLO, SCH_ETRN, SCH_EXPN, SCH_HELO,
        SCH_HELP, SCH_MAIL, SCH_NOOP, SCH_QUIT, SCH_RCPT, SCH_RSET, SCH_STARTTLS,
        SCH_VRFY,
+#ifndef DISABLE_WELLKNOWN
+       SCH_WELLKNOWN,
+#endif
 #ifdef EXPERIMENTAL_XCLIENT
        SCH_XCLIENT,
 #endif
 #ifdef EXPERIMENTAL_XCLIENT
        SCH_XCLIENT,
 #endif
@@ -972,6 +975,9 @@ enum { ACL_WHERE_RCPT,       /* Some controls are for RCPT only */
        ACL_WHERE_NOTQUIT,
        ACL_WHERE_QUIT,
        ACL_WHERE_STARTTLS,
        ACL_WHERE_NOTQUIT,
        ACL_WHERE_QUIT,
        ACL_WHERE_STARTTLS,
+#ifndef DISABLE_WELLKNOWN
+       ACL_WHERE_WELLKNOWN,
+#endif
        ACL_WHERE_VRFY,
 
        ACL_WHERE_DELIVERY,
        ACL_WHERE_VRFY,
 
        ACL_WHERE_DELIVERY,
@@ -1001,6 +1007,9 @@ enum { ACL_WHERE_RCPT,       /* Some controls are for RCPT only */
 #define ACL_BIT_QUIT           BIT(ACL_WHERE_QUIT)
 #define ACL_BIT_STARTTLS       BIT(ACL_WHERE_STARTTLS)
 #define ACL_BIT_VRFY           BIT(ACL_WHERE_VRFY)
 #define ACL_BIT_QUIT           BIT(ACL_WHERE_QUIT)
 #define ACL_BIT_STARTTLS       BIT(ACL_WHERE_STARTTLS)
 #define ACL_BIT_VRFY           BIT(ACL_WHERE_VRFY)
+#ifndef DISABLE_WELLKNOWN
+# define ACL_BIT_WELLKNOWN     BIT(ACL_WHERE_WELLKNOWN)
+#endif
 #define ACL_BIT_DELIVERY       BIT(ACL_WHERE_DELIVERY)
 #define ACL_BIT_UNKNOWN                BIT(ACL_WHERE_UNKNOWN)
 
 #define ACL_BIT_DELIVERY       BIT(ACL_WHERE_DELIVERY)
 #define ACL_BIT_UNKNOWN                BIT(ACL_WHERE_UNKNOWN)
 
@@ -1201,3 +1210,5 @@ When doing en extended loop of matching, release store periodically. */
   DEBUG(D_expand) debug_printf("try option " name "\n");
 
 /* End of macros.h */
   DEBUG(D_expand) debug_printf("try option " name "\n");
 
 /* End of macros.h */
+/* vi: aw ai sw=2
+*/
index d87a56f3d6e5562cc44f313ace3366d0318ba38a..aef07184ade7cdda72f91f96b9050876fd932dbc 100644 (file)
@@ -66,6 +66,9 @@ static optionlist optionlist_config[] = {
   { "acl_smtp_starttls",        opt_stringptr,   {&acl_smtp_starttls} },
 #endif
   { "acl_smtp_vrfy",            opt_stringptr,   {&acl_smtp_vrfy} },
   { "acl_smtp_starttls",        opt_stringptr,   {&acl_smtp_starttls} },
 #endif
   { "acl_smtp_vrfy",            opt_stringptr,   {&acl_smtp_vrfy} },
+#ifndef DISABLE_WELLKNOWN
+  { "acl_smtp_wellknown",       opt_stringptr,   {&acl_smtp_wellknown} },
+#endif
   { "add_environment",          opt_stringptr,   {&add_environment} },
   { "admin_groups",             opt_gidlist,     {&admin_groups} },
   { "allow_domain_literals",    opt_bool,        {&allow_domain_literals} },
   { "add_environment",          opt_stringptr,   {&add_environment} },
   { "admin_groups",             opt_gidlist,     {&admin_groups} },
   { "allow_domain_literals",    opt_bool,        {&allow_domain_literals} },
@@ -402,6 +405,9 @@ static optionlist optionlist_config[] = {
   { "uucp_from_pattern",        opt_stringptr,   {&uucp_from_pattern} },
   { "uucp_from_sender",         opt_stringptr,   {&uucp_from_sender} },
   { "warn_message_file",        opt_stringptr,   {&warn_message_file} },
   { "uucp_from_pattern",        opt_stringptr,   {&uucp_from_pattern} },
   { "uucp_from_sender",         opt_stringptr,   {&uucp_from_sender} },
   { "warn_message_file",        opt_stringptr,   {&warn_message_file} },
+#ifndef DISABLE_WELLKNOWN
+  { "wellknown_advertise_hosts",opt_stringptr,  {&wellknown_advertise_hosts} },
+#endif
   { "write_rejectlog",          opt_bool,        {&write_rejectlog} },
 };
 
   { "write_rejectlog",          opt_bool,        {&write_rejectlog} },
 };
 
index ff50c80f939d812fab854fde49fece3c942378c2..c941d115ccb529ec1b932c09d560935d536bed5b 100644 (file)
@@ -86,6 +86,9 @@ enum {
   /* These commands need not be synchronized when pipelining */
 
   MAIL_CMD, RCPT_CMD, RSET_CMD,
   /* These commands need not be synchronized when pipelining */
 
   MAIL_CMD, RCPT_CMD, RSET_CMD,
+#ifndef DISABLE_WELLKNOWN
+  WELLKNOWN_CMD,
+#endif
 
   /* This is a dummy to identify the non-sync commands when not pipelining */
 
 
   /* This is a dummy to identify the non-sync commands when not pipelining */
 
@@ -121,7 +124,8 @@ enum {
   /* These are specials that don't correspond to actual commands */
 
   EOF_CMD, OTHER_CMD, BADARG_CMD, BADCHAR_CMD, BADSYN_CMD,
   /* These are specials that don't correspond to actual commands */
 
   EOF_CMD, OTHER_CMD, BADARG_CMD, BADCHAR_CMD, BADSYN_CMD,
-  TOO_MANY_NONMAIL_CMD };
+  TOO_MANY_NONMAIL_CMD
+};
 
 
 /* This is a convenience macro for adding the identity of an SMTP command
 
 
 /* This is a convenience macro for adding the identity of an SMTP command
@@ -230,7 +234,10 @@ static smtp_cmd_list cmd_list[] = {
   { "etrn",       sizeof("etrn")-1,       ETRN_CMD, TRUE,  FALSE },
   { "vrfy",       sizeof("vrfy")-1,       VRFY_CMD, TRUE,  FALSE },
   { "expn",       sizeof("expn")-1,       EXPN_CMD, TRUE,  FALSE },
   { "etrn",       sizeof("etrn")-1,       ETRN_CMD, TRUE,  FALSE },
   { "vrfy",       sizeof("vrfy")-1,       VRFY_CMD, TRUE,  FALSE },
   { "expn",       sizeof("expn")-1,       EXPN_CMD, TRUE,  FALSE },
-  { "help",       sizeof("help")-1,       HELP_CMD, TRUE,  FALSE }
+  { "help",       sizeof("help")-1,       HELP_CMD, TRUE,  FALSE },
+#ifndef DISABLE_WELLKNOWN
+  { "wellknown",  sizeof("wellknown")-1,  WELLKNOWN_CMD, TRUE,  FALSE },
+#endif
 };
 
 /* This list of names is used for performing the smtp_no_mail logging action. */
 };
 
 /* This list of names is used for performing the smtp_no_mail logging action. */
@@ -253,6 +260,9 @@ uschar * smtp_names[] =
   [SCH_RSET] = US"RSET",
   [SCH_STARTTLS] = US"STARTTLS",
   [SCH_VRFY] = US"VRFY",
   [SCH_RSET] = US"RSET",
   [SCH_STARTTLS] = US"STARTTLS",
   [SCH_VRFY] = US"VRFY",
+#ifndef DISABLE_WELLKNOWN
+  [SCH_WELLKNOWN] = US"WELLKNOWN",
+#endif
 #ifdef EXPERIMENTAL_XCLIENT
   [SCH_XCLIENT] = US"XCLIENT",
 #endif
 #ifdef EXPERIMENTAL_XCLIENT
   [SCH_XCLIENT] = US"XCLIENT",
 #endif
@@ -1946,7 +1956,7 @@ while (done <= 0)
     case HELP_CMD:
     case NOOP_CMD:
     case ETRN_CMD:
     case HELP_CMD:
     case NOOP_CMD:
     case ETRN_CMD:
-#ifdef EXPERIMENTAL_WELLKNOWN
+#ifndef DISABLE_WELLKNOWN
     case WELLKNOWN_CMD:
 #endif
       bsmtp_transaction_linecount = receive_linecount;
     case WELLKNOWN_CMD:
 #endif
       bsmtp_transaction_linecount = receive_linecount;
@@ -2832,10 +2842,8 @@ if (fl.rcpt_in_progress)
 We only handle pipelining these responses as far as nonfinal/final groups,
 not the whole MAIL/RCPT/DATA response set. */
 
 We only handle pipelining these responses as far as nonfinal/final groups,
 not the whole MAIL/RCPT/DATA response set. */
 
-for (;;)
-  {
-  uschar *nl = Ustrchr(msg, '\n');
-  if (!nl)
+for (uschar * nl;;)
+  if (!(nl = Ustrchr(msg, '\n')))
     {
     smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg);
     return;
     {
     smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg);
     return;
@@ -2852,7 +2860,6 @@ for (;;)
     msg = nl + 1;
     Uskip_whitespace(&msg);
     }
     msg = nl + 1;
     Uskip_whitespace(&msg);
     }
-  }
 }
 
 
 }
 
 
@@ -2971,7 +2978,8 @@ smtp_code = rc == FAIL ? acl_wherecodes[where] : US"451";
 smtp_message_code(&smtp_code, &codelen, &user_msg, &log_msg,
   where != ACL_WHERE_VRFY);
 
 smtp_message_code(&smtp_code, &codelen, &user_msg, &log_msg,
   where != ACL_WHERE_VRFY);
 
-/* We used to have sender_address here; however, there was a bug that was not
+/* Get info for logging.
+We used to have sender_address here; however, there was a bug that was not
 updating sender_address after a rewrite during a verify. When this bug was
 fixed, sender_address at this point became the rewritten address. I'm not sure
 this is what should be logged, so I've changed to logging the unrewritten
 updating sender_address after a rewrite during a verify. When this bug was
 fixed, sender_address at this point became the rewritten address. I'm not sure
 this is what should be logged, so I've changed to logging the unrewritten
@@ -2996,7 +3004,7 @@ switch (where)
       {
       uschar * s = smtp_cmd_data;
       Uskip_nonwhite(&s);
       {
       uschar * s = smtp_cmd_data;
       Uskip_nonwhite(&s);
-      lim = s - smtp_cmd_data; /* atop after method */
+      lim = s - smtp_cmd_data; /* stop after method */
       }
     what = string_sprintf("%s %.*s", acl_wherenames[where], lim, place);
     }
       }
     what = string_sprintf("%s %.*s", acl_wherenames[where], lim, place);
     }
@@ -3375,7 +3383,7 @@ Returns:       nothing
 */
 
 static void
 */
 
 static void
-smtp_user_msg(uschar *code, uschar *user_msg)
+smtp_user_msg(uschar * code, uschar * user_msg)
 {
 int len = 3;
 smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
 {
 int len = 3;
 smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
@@ -3581,6 +3589,36 @@ if (chunking_state > CHUNKING_OFFERED)
 }
 
 
 }
 
 
+#ifndef DISABLE_WELLKNOWN
+static int
+smtp_wellknown_handler(void)
+{
+if (verify_check_host(&wellknown_advertise_hosts) != FAIL)
+  {
+  GET_OPTION("acl_smtp_wellknown");
+  if (acl_smtp_wellknown)
+    {
+    uschar * user_msg = NULL, * log_msg;
+    int rc;
+
+    if ((rc = acl_check(ACL_WHERE_WELLKNOWN, NULL, acl_smtp_wellknown,
+               &user_msg, &log_msg)) != OK)
+      return smtp_handle_acl_fail(ACL_WHERE_WELLKNOWN, rc, user_msg, log_msg);
+    else if (!wellknown_response)
+      return smtp_handle_acl_fail(ACL_WHERE_WELLKNOWN, ERROR, user_msg, log_msg);
+    smtp_user_msg(US"250", wellknown_response);
+    return 0;
+    }
+  }
+
+smtp_printf("554 not permitted\r\n", SP_NO_MORE);
+log_write(0, LOG_MAIN|LOG_REJECT, "rejected \"%s\" from %s",
+             smtp_cmd_buffer, sender_helo_name, host_and_ident(FALSE));
+return 0;
+}
+#endif
+
+
 static int
 expand_mailmax(const uschar * s)
 {
 static int
 expand_mailmax(const uschar * s)
 {
@@ -3686,9 +3724,8 @@ while (done <= 0)
   void (*oldsignal)(int);
   pid_t pid;
   int start, end, sender_domain, recipient_domain;
   void (*oldsignal)(int);
   pid_t pid;
   int start, end, sender_domain, recipient_domain;
-  int rc;
-  int c;
-  uschar *orcpt = NULL;
+  int rc, c;
+  uschar * orcpt = NULL;
   int dsn_flags;
   gstring * g;
 
   int dsn_flags;
   gstring * g;
 
@@ -4213,12 +4250,12 @@ while (done <= 0)
          chunking_state = CHUNKING_OFFERED;
          }
 
          chunking_state = CHUNKING_OFFERED;
          }
 
+#ifndef DISABLE_TLS
        /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
        if it has been included in the binary, and the host matches
        tls_advertise_hosts. We must *not* advertise if we are already in a
        secure connection. */
 
        /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
        if it has been included in the binary, and the host matches
        tls_advertise_hosts. We must *not* advertise if we are already in a
        secure connection. */
 
-#ifndef DISABLE_TLS
        if (tls_in.active.sock < 0 &&
            verify_check_host(&tls_advertise_hosts) != FAIL)
          {
        if (tls_in.active.sock < 0 &&
            verify_check_host(&tls_advertise_hosts) != FAIL)
          {
@@ -4252,6 +4289,13 @@ while (done <= 0)
          fl.smtputf8_advertised = TRUE;
          }
 #endif
          fl.smtputf8_advertised = TRUE;
          }
 #endif
+#ifndef DISABLE_WELLKNOWN
+       if (verify_check_host(&wellknown_advertise_hosts) != FAIL)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-WELLKNOWN\r\n", 12);
+         }
+#endif
 
        /* Finish off the multiline reply with one that is always available. */
 
 
        /* Finish off the multiline reply with one that is always available. */
 
@@ -4299,6 +4343,14 @@ while (done <= 0)
       toomany = FALSE;
       break;   /* HELO/EHLO */
 
       toomany = FALSE;
       break;   /* HELO/EHLO */
 
+#ifndef DISABLE_WELLKNOWN
+    case WELLKNOWN_CMD:
+      HAD(SCH_WELLKNOWN);
+      smtp_mailcmd_count++;
+      smtp_wellknown_handler();
+      break;
+#endif
+
 #ifdef EXPERIMENTAL_XCLIENT
     case XCLIENT_CMD:
       {
 #ifdef EXPERIMENTAL_XCLIENT
     case XCLIENT_CMD:
       {
@@ -5455,6 +5507,10 @@ while (done <= 0)
       if (acl_smtp_etrn) smtp_printf(" ETRN", SP_MORE);
       if (acl_smtp_expn) smtp_printf(" EXPN", SP_MORE);
       if (acl_smtp_vrfy) smtp_printf(" VRFY", SP_MORE);
       if (acl_smtp_etrn) smtp_printf(" ETRN", SP_MORE);
       if (acl_smtp_expn) smtp_printf(" EXPN", SP_MORE);
       if (acl_smtp_vrfy) smtp_printf(" VRFY", SP_MORE);
+#ifndef DISABLE_WELLKNOWN
+      if (verify_check_host(&wellknown_advertise_hosts) != FAIL)
+       smtp_printf(" WELLKNOWN", SP_MORE);
+#endif
 #ifdef EXPERIMENTAL_XCLIENT
       if (proxy_session || verify_check_host(&hosts_xclient) != FAIL)
        smtp_printf(" XCLIENT", SP_MORE);
 #ifdef EXPERIMENTAL_XCLIENT
       if (proxy_session || verify_check_host(&hosts_xclient) != FAIL)
        smtp_printf(" XCLIENT", SP_MORE);
diff --git a/src/util/mailtest b/src/util/mailtest
new file mode 100755 (executable)
index 0000000..0c50d93
--- /dev/null
@@ -0,0 +1,486 @@
+#!/usr/bin/perl
+#
+###############################################################
+###############################################################
+
+use strict;
+
+use Net::SMTP;
+#use IO::Socket::SSL qw( SSL_ERROR );
+use Net::Domain qw(hostfqdn);
+use Getopt::Long qw(:config default bundling no_ignore_case auto_version);
+use Pod::Usage;
+use Net::Cmd;
+use Data::Dumper;
+
+our @ISA = qw(Net::Cmd);
+
+###############################################################
+###############################################################
+
+my ($smtp,$optsin,$opt,$mess,$rcpt,@headers,$finished_header,$ofh);
+$main::VERSION = '1.2.2';
+
+$optsin = {
+    'body|b'                    => \&optset,
+    'debug|d'                   => \&optset,
+    'ehlo|helo|m=s'             => \&optset,
+    'rcptto|recipient|r=s'      => \&optset,
+    'host|h=s'                  => \&optset,
+    'from822|u=s'               => \&optset,
+    'vrfy|v'                    => \&optset,
+    'expn|e'                    => \&optset,
+    'mailfrom|from821|from|f=s' => \&optset,
+    'port|p=i'                  => \&optset,
+    'wellknown|w=s'             => \&optset,
+    'output|W=s'                => \&optset,
+    'include_options|O'         => \&optset,
+    'include_headers|H'         => \&optset,
+    'bounce|B'                  => \&optset,
+    'tls|S'                     => \&optset,
+    'nostarttls|s'              => \&optset,
+    'stricttls|strict_tls'      => \&optset,
+    'sslargs|tlsargs=s'         => \&optset,
+    'verbose'                   => \&optset,
+    'help'                      => \&optset,
+    'man'                       => \&optset,
+};
+map { my $t = $_; $t =~ s/\|.*//; $opt->{$t} = undef; } keys %$optsin;
+GetOptions( %$optsin ) or pod2usage(2);
+pod2usage(1) if $opt->{'help'};
+pod2usage(-exitval => 0, -verbose => 2) if $opt->{'man'};
+
+print _Dumper($opt, 'Options')
+    if $opt->{'debug'};
+
+###############################################################
+###############################################################
+##
+## parameter checking
+##
+###############################################################
+###############################################################
+
+bail( 1, "Host(--host) must be provided" )
+    if !$opt->{'host'};
+
+$opt->{'port'} = $opt->{'tls'} ? 465 : 25
+    if ! $opt->{'port'};
+
+if (!$opt->{'ehlo'})
+{
+    $opt->{'ehlo'} = hostfqdn();
+    fret( "Machine set to $opt->{'ehlo'}" ) if $opt->{'debug'};
+}
+if (!$opt->{'mailfrom'} && !$opt->{'bounce'})
+{
+    $opt->{'mailfrom'} = $ENV{USER}. "@". $opt->{'ehlo'};
+    fret( "MAIL FROM set to $opt->{'mailfrom'}" ) if $opt->{'debug'};
+}
+if (!$opt->{'from822'})
+{
+    $opt->{'from822'} = $opt->{'mailfrom'};
+    fret( "From: set to $opt->{'from822'}" ) if $opt->{'debug'};
+}
+if ($opt->{'bounce'})
+{
+    $opt->{'mailfrom'} = "";
+    $opt->{'from822'} = 'mailer-daemon@'. hostfqdn();
+    fret( "MAIL FROM set to $opt->{'mailfrom'}", "From: set to $opt->{'from822'}" ) if $opt->{'debug'};
+}
+
+bail( 1, "EXPN or VRFY cannot be used without a recipient" )
+    if ($opt->{'expn'} || $opt->{'vrfy'}) && ! $opt->{'rcptto'};
+
+bail( 1, "Either a recipient or well-known resource must be specified" )
+    if ! $opt->{'wellknown'} && ! $opt->{'rcptto'};
+
+bail( 1, "Only one of recipient or well-known resource can be specified" )
+    if $opt->{'wellknown'} && $opt->{'rcptto'};
+
+if ( $opt->{'sslargs'} )
+{
+    my @p = split /[=,]/, $opt->{'sslargs'};
+    $opt->{'sslparams'} = \@p;
+}
+else
+{
+    $opt->{'sslparams'} = [ 'SSL_verify_mode', $opt->{'stricttls'} ? 1 : 0 ];
+}
+fret( _Dumper( $opt->{'sslparams'}, 'sslparams' ) )
+    if $opt->{'debug'} && ( $opt->{'tls'} || ! $opt->{'nostarttls'} );
+
+###############################################################
+###############################################################
+##
+## parameter checking complete. now onto operations
+##
+##
+###############################################################
+###############################################################
+
+
+
+$smtp= Net::SMTP->new(  $opt->{'host'},
+                        Hello   => $opt->{'ehlo'},
+                        Debug   => $opt->{'debug'},
+                        ( $opt->{'tls'} ? ( 'SSL' => $opt->{'sslargs'} || 1 ) : () ),
+                        Port    => $opt->{'port'},
+                        );
+bail( 1, "Connection Failed: $@" )
+    if !$smtp;
+
+if (!$opt->{'nostarttls'})
+{
+    bail( $smtp, 1, "Failed to STARTTLS - $@" )
+        if ! $smtp->starttls( @{$opt->{'sslparams'}} );
+
+    fret( $smtp->message() )
+        if $opt->{'verbose'};
+}
+
+if ($opt->{'wellknown'})
+{
+    bail( $smtp, 1, "Server does not support WELLKNOWN" )
+        if ! $smtp->supports('WELLKNOWN');
+
+    my $e_wk = encode_xtext( $opt->{'wellknown'} );
+
+    bail( $smtp, 1, "Failed to WELLKNOWN - $e_wk", $smtp->message() )
+        if ! ( $smtp->command( 'WELLKNOWN', $e_wk )->response() == CMD_OK );
+
+    fret( "Protocol violation. Code was OK, but not 250",   $smtp->code. " - ". $smtp->message )
+        if $smtp->code ne '250';
+
+    $mess = $smtp->message;
+    my ($info,$size);
+    ($info,$mess) = split( /\n/, $mess, 2 );
+    $info =~ /size=(\d+)/i;
+    $size = $1 + 0;
+    $mess = decode_xtext( $mess );
+    fret( "Size mismatch on wellknown fetch", "Expected: ". $size, "Received: ". length($mess) )
+        if length($mess) != $size;
+
+    if ( $opt->{'output'} )
+    {
+        # Output to named file
+        #
+        bail( $smtp, 1, "Unable to open file $opt->{'output'} for WELLKNOWN output - $!" )
+            if ! ( $ofh = IO::File->new("> $opt->{'output'}") );
+
+        print $ofh $mess;
+        $ofh->close;
+    }
+    else
+    {
+        # might be hazardous, output via pager
+        print STDERR "$mess\n";
+    }
+}
+
+if ($opt->{'vrfy'})
+{
+    $smtp->verify($opt->{'vrfy'});
+    fret( $smtp->message() );
+}
+
+if ($opt->{'expn'})
+{
+    $smtp->expand($opt->{'expn'});
+    fret( $smtp->message() );
+}
+
+if ($opt->{'rcptto'})
+{
+    bail( $smtp, 1, "MAIL FROM $opt->{'mailfrom'} failed", $@ )
+        if ! $smtp->mail($opt->{'mailfrom'});
+
+    bail( $smtp, 1, "RCPT TO $opt->{'rcptto'} failed", $@ )
+        if ! $smtp->to($opt->{'rcptto'});
+
+    # handle any recipients on command line
+    while( $rcpt = shift @ARGV )
+    {
+        last if $rcpt eq '--';
+        fret( "RCPT TO $rcpt failed", $@ )
+            if ! $smtp->to($rcpt);
+    }
+
+    bail( $smtp, 1, "Unable to set data mode", @_ )
+        if ! $smtp->data();
+
+    if ($opt->{'body'})
+    {
+        push @headers, "Subject: Test Message\n";
+        $smtp->datasend("From: $opt->{'from822'}\n");
+        $smtp->datasend("To: $opt->{'rcptto'}\n");
+        $smtp->datasend("Subject: Test Message\n");
+        $smtp->datasend("\n");
+        $smtp->datasend("This is a test message\n");
+        $smtp->datasend("generated with mailtest\n");
+    }else
+    {
+        while(<>)
+        {
+            if($finished_header==0)
+            {
+                if (length($_)<=1)
+                {
+                    $finished_header = 1;
+                }else
+                {
+                    push @headers,"    ".$_;
+                }
+            }
+            $smtp->datasend("$_");
+        }
+    }
+    if($opt->{'include_headers'} && @headers)
+    {
+        $smtp->datasend("\n Copy of headers follow....\n");
+        foreach(@headers)
+        {
+            $smtp->datasend("$_");
+        }
+        $smtp->datasend("\n");
+    }
+    if($opt->{'include_options'})
+    {
+        $smtp->datasend("\n Copy of options follow....\n");
+        $smtp->datasend("    SMTP HOST $opt->{'host'}\n");
+        $smtp->datasend("    HELO $opt->{'ehlo'}\n");
+        $smtp->datasend("    MAIL FROM: $opt->{'mailfrom'}\n");
+        $smtp->datasend("    RCPT TO: $opt->{'rcptto'}\n\n");
+    }
+    fret( "dataend failed", $@ )
+        if ! $smtp->dataend();
+}
+$smtp->quit();
+
+exit;
+
+##############################################################
+##############################################################
+
+sub
+optset
+{
+    my $n = shift;
+    my $v = shift;
+    #print STDERR "Setting $n to $v\n";
+    $opt->{$n->{'name'}} = $v;
+}
+
+sub
+decode_xtext
+{
+    my $mess = shift;
+    $mess =~ s/[\n\r]//g;
+    $mess =~ s/\+([0-9a-fA-F]{2})/chr(hex($1))/ge;
+    return $mess;
+}
+
+sub
+encode_xtext
+{
+    my $mess = shift;
+    $mess =~ s/([^!-*,-<>-~])/'+'.uc(unpack('H*', $1))/eg;
+    return $mess;
+}
+
+sub
+_Dumper
+{
+    return Data::Dumper->Dump( [$_[0]], [$_[1] || 'VAR1'] );
+}
+
+sub
+fret
+{
+    map { print STDERR $_,"\n"; } @_;
+}
+
+sub
+bail
+{
+    shift->quit
+        if ref($_[0]);
+    my $rc = shift;
+    fret( @_ );
+    exit $rc;
+}
+
+##############################################################
+##############################################################
+
+__END__
+
+=head1 NAME
+
+mailtest - Simple SMTP sending for diagnostics
+
+=head1 SYNOPSIS
+
+mailtest --host host.example.com --rcptto recipient@example.com [ send_options ... ] [ additional recipients ...]
+
+
+Options:
+  --help
+                brief help message
+  --debug
+                enable debugging
+
+  --host host
+                host to connect to
+  --rcptto recipient
+                recipient for message
+
+  --helo machine
+                machine name for EHLO
+
+  --vrfy        request VRFY of recipient
+  --expn        request EXPN of recipient
+
+  --mailfrom from
+                use as MAIL FROM value
+  --from822 from
+                content From:
+
+  --port port
+                port to connect to
+
+  --body            generate body
+  --include_options
+                include Options in body
+  --include_headers
+                include generated headers in body
+
+  --tls         perform TLS on connect
+  --nostarttls  do no attempt STARTTLS
+  --stricttls   Enable strict verification on TLS connection
+
+  --tlsargs arg=value[,arg=value]
+                Explicitly define TLS options.
+
+  --bounce      sending as bounce (<>)
+
+  --wellknown path
+                well-known path
+  --output file
+                Output file to receive well-known data
+
+=head1 OPTIONS
+
+=over 8
+
+
+=item B<--help>
+
+Print a brief help message and exits.
+
+=item B<-d, --debug>
+
+Enables debugging, outpus additional information whilst processing requests.
+
+=item B<-h, --host>=I<host>
+
+Specifies the host to connect to. Must be specified and must be IP-resolvable.
+
+=item B<-m, --ehlo>=I<machine>
+
+Specified the machine name to use as the B<EHLO> value. Defaults to the fully-qualified name of the host running the command.
+
+=item B<-r, --rcptto>=I<recipient>
+
+Specifies the recipient of message. This is used as the B<RCPT TO> value.
+
+=item B<-v, --vrfy>
+
+Uses the I<recipient> parameter for the value in a B<VRFY> request. This disables the sending of an email.
+
+=item B<-e, --expn>
+
+Uses the I<recipient> parameter for the value in an B<EXPN> request. This disables the sending of an email.
+
+=item B<-f, --mailfrom>=I<from_address>
+
+Specified the value to use in the B<MAIL FROM> command. Defaults to the current username at the FQDN of the machine B<-m> unless the B<-B> option is used.
+
+=item B<-u, --from822>=I<from_user>
+
+Specified the value to use in the message headers. Defaults to the B<-f> I<from_address> value unless the B<-B> option is used.
+
+=item B<-B, --bounce>
+
+Replace the B<--mailfrom> I<from_address> with B<\<\>> and the B<--from833> I<from_user> with B<mailer-daemon@host> where the host is the value of B<--ehlo> I<machine>
+
+=item B<-p, --port>=I<port>
+
+Specified the port to connect to on the specified host. Defaults to port 25 unless B<-S> is given in which case it defaults to 465.
+
+=item B<-S, --tls>
+
+Specifies that TLS be used directly on the connection prior to any SMTP commands. Changes the connection port to 465 unless it has been explicitly provided. Disables any attempts at B<STARTTLS>.
+
+=item B<-s, --nostarttls>
+
+Disables attempting STARTTLS if offered. Disabled by use of B<-S>.
+
+=item B<--stricttls>
+
+Enables strict verification of the TLS connection. Sets the underlying SSL option B<SSL_verify_mode> to 1/SSL_VERIFY_PEER rather than 0/SSL_VERIFY_NONE. Since the aim of this tool is to test the SMTP protocol behaviour and not the TLS behaviour the decision was made to default the B<SSL_verify_mode> to 0/SSL_VERIFY_NONE.
+
+=item B<--sslargs>=argname=argvalue[,argname=argvalue...]
+
+Allow full control over underlying SSL options. Overrides B<--stricttls>. See the documentation for B<IO::Socket::SSL> for further details.
+
+    --sslargs SSL_verifycn_name=certname.example.com
+
+=item B<-b, --body>
+
+Generate a body for the message being sent.
+
+=item B<-O, --include-options>
+
+Include details of options used in the message body.
+
+=item B<-H, --include-headers>
+
+Include a copy of the generated headers in the message body.
+
+=item B<-w, --wellknown>=I<well-known-path>
+
+Provides the path value for a B<WELLKNOWN> command.
+
+=item B<-W, --output>=I<output_file>
+
+Provides the output file where the B<WELLKNOWN> data should be stored.
+
+=back
+
+=head1 DESCRIPTION
+
+B<mailtest> is a simple utility for testing SMTP connections.
+It is designed to debug endpoints and not for full email generation.
+
+It support a number of basic operations, SEND, VRFY, EXPN, WELLKNOWN.
+
+=head1 COMPATIBILITY
+
+C<mailtest> only requires modules that should be in all normal distributions.
+
+=head1 AUTHOR
+
+Bernard Quatermass <bernardq@exim.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is Copyright (c) 2008,2020,2024 by Bernard Quatermass.
+
+
+=cut
+
+###############################################################
+# vi: sw=4 et
+# End of File
+###############################################################
diff --git a/test/aux-fixed/4040/acme-response b/test/aux-fixed/4040/acme-response
new file mode 100644 (file)
index 0000000..d3f618d
--- /dev/null
@@ -0,0 +1,3 @@
+line 1
+line 2
+last line
diff --git a/test/aux-fixed/4040/sub/acme-response b/test/aux-fixed/4040/sub/acme-response
new file mode 100644 (file)
index 0000000..d3f618d
--- /dev/null
@@ -0,0 +1,3 @@
+line 1
+line 2
+last line
diff --git a/test/confs/4040 b/test/confs/4040
new file mode 100644 (file)
index 0000000..c5c6c3c
--- /dev/null
@@ -0,0 +1,29 @@
+# Exim test configuration 4040
+
+SERVER=
+OPT=
+
+.include DIR/aux-var/std_conf_prefix
+
+primary_hostname = myhost.test.ex
+
+# ----- Main settings -----
+
+wellknown_advertise_hosts = 127.0.0.1
+acl_smtp_wellknown = check_wellknown
+
+# ----- ACL -----
+
+begin acl
+
+check_wellknown:
+  accept
+    logwrite =          [$sender_host_address] $smtp_command
+    condition =                ${if == {${received_port}}{PORT_D}}
+    set acl_c_wellknown = ${lookup {${xtextd:$smtp_command_argument}} \
+                               dsearch,ret=full,filter=fileOPT \
+                               {DIR/aux-fixed/TESTNUM}}
+    logwrite =          [$sender_host_address] -> '$acl_c_wellknown'
+    control =           wellknown/$acl_c_wellknown
+
+# End
diff --git a/test/log/4040 b/test/log/4040
new file mode 100644 (file)
index 0000000..4274a7a
--- /dev/null
@@ -0,0 +1,21 @@
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=p1234, no queue runs, listening for SMTP on port PORT_D port PORT_D2
+1999-03-02 09:44:33 [127.0.0.1] WELLKNOWN acme-response
+1999-03-02 09:44:33 [127.0.0.1] -> 'TESTSUITE/aux-fixed/4040/acme-response'
+1999-03-02 09:44:33 [127.0.0.1] WELLKNOWN acme-response
+1999-03-02 09:44:33 H=(test) [127.0.0.1] rejected WELLKNOWN acme-response
+1999-03-02 09:44:33 [127.0.0.1] WELLKNOWN badfile
+1999-03-02 09:44:33 [127.0.0.1] -> ''
+1999-03-02 09:44:33 H=(test) [127.0.0.1] rejected WELLKNOWN badfile
+1999-03-02 09:44:33 exim x.yz daemon started: pid=p1235, no queue runs, listening for SMTP on port PORT_D port PORT_D2
+1999-03-02 09:44:33 [127.0.0.1] WELLKNOWN acme-response
+1999-03-02 09:44:33 [127.0.0.1] -> 'TESTSUITE/aux-fixed/4040/acme-response'
+1999-03-02 09:44:33 [127.0.0.1] WELLKNOWN sub/acme-response
+1999-03-02 09:44:33 [127.0.0.1] -> 'TESTSUITE/aux-fixed/4040/sub/acme-response'
+1999-03-02 09:44:33 [127.0.0.1] WELLKNOWN sub/badfile
+1999-03-02 09:44:33 [127.0.0.1] -> ''
+1999-03-02 09:44:33 H=(test) [127.0.0.1] rejected WELLKNOWN sub/badfile
+1999-03-02 09:44:33 [127.0.0.1] WELLKNOWN ../badfile
+1999-03-02 09:44:33 [127.0.0.1] -> ''
+1999-03-02 09:44:33 H=(test) [127.0.0.1] rejected WELLKNOWN ../badfile
diff --git a/test/rejectlog/4040 b/test/rejectlog/4040
new file mode 100644 (file)
index 0000000..9d4ca3a
--- /dev/null
@@ -0,0 +1,6 @@
+
+******** SERVER ********
+1999-03-02 09:44:33 H=(test) [127.0.0.1] rejected WELLKNOWN acme-response
+1999-03-02 09:44:33 H=(test) [127.0.0.1] rejected WELLKNOWN badfile
+1999-03-02 09:44:33 H=(test) [127.0.0.1] rejected WELLKNOWN sub/badfile
+1999-03-02 09:44:33 H=(test) [127.0.0.1] rejected WELLKNOWN ../badfile
index b959107c122e2aae7e25120a7996d6d5cba06fd7..683dee2d1678d9fe47fea713f5a9e1ec25b343a3 100755 (executable)
@@ -1430,6 +1430,9 @@ RESET_AFTER_EXTRA_LINE_READ:
     # DISABLE_OCSP
     next if /in hosts_requ(est|ire)_ocsp\? (no|yes)/;
 
     # DISABLE_OCSP
     next if /in hosts_requ(est|ire)_ocsp\? (no|yes)/;
 
+    # WELLKNOWN
+    next if / in wellknown_advertise_hosts\?/;
+
     # SUPPORT_PROXY
     next if /host in hosts_proxy\?/;
 
     # SUPPORT_PROXY
     next if /host in hosts_proxy\?/;
 
@@ -1458,7 +1461,10 @@ RESET_AFTER_EXTRA_LINE_READ:
     next if / in limits_advertise_hosts?\? no \(matched "!\*"\)/;
 
     # Experimental_XCLIENT
     next if / in limits_advertise_hosts?\? no \(matched "!\*"\)/;
 
     # Experimental_XCLIENT
-    next if / in hosts_xclient?\? no \(option unset\)/;
+    next if / in hosts_xclient\? no \(option unset\)/;
+
+    # Experimental_WELLKNOWN
+    next if / in hosts_wellknown\? no \(option unset\)/;
 
     # TCP Fast Open
     next if /^(ppppp )?setsockopt FASTOPEN: Network Error/;
 
     # TCP Fast Open
     next if /^(ppppp )?setsockopt FASTOPEN: Network Error/;
@@ -3663,7 +3669,7 @@ while (<EXIMINFO>)
     }
 
   elsif (/^Support for: (.*)/)
     }
 
   elsif (/^Support for: (.*)/)
-    {
+    {                  # Compile-time features - exim -bV
     print;
     @temp = split /(\s+)/, $1;
     push(@temp, ' ');
     print;
     @temp = split /(\s+)/, $1;
     push(@temp, ' ');
@@ -4230,7 +4236,7 @@ DIR: for (my $i = 0; $i < @test_dirs; $i++)
         if (!defined $parm_malware{$1}) { $wantthis = 0; last; }
         }
       elsif (/^(not )?feature (.*)$/)
         if (!defined $parm_malware{$1}) { $wantthis = 0; last; }
         }
       elsif (/^(not )?feature (.*)$/)
-        {
+        {                      #a macro name, or lack thereof - -bP macros
        # move to a subroutine?
        my $eximinfo = "$parm_exim -C $parm_cwd/test-config -DDIR=$parm_cwd -bP macro $2";
 
        # move to a subroutine?
        my $eximinfo = "$parm_exim -C $parm_cwd/test-config -DDIR=$parm_cwd -bP macro $2";
 
diff --git a/test/scripts/4040-wellknown/4040 b/test/scripts/4040-wellknown/4040
new file mode 100644 (file)
index 0000000..8ca4030
--- /dev/null
@@ -0,0 +1,157 @@
+# ESMTP WELLNOWN server response
+#
+# when WELLKNOWN leaves EXPERIMENTAL, add standalone tests
+# for ${xtextd:str} to 0002
+#
+#
+exim -DSERVER=server -bd -oX PORT_D:PORT_D2
+****
+#
+client 127.0.0.1 PORT_D
+??? 220
+EHLO test
+??? 250-
+??? 250-SIZE
+??? 250-LIMITS
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-WELLKNOWN
+??? 250 HELP
+WELLKNOWN acme-response
+??? 250-SIZE
+??? 250-
+??? 250-
+??? 250
+QUIT
+??? 221
+****
+#
+# not advertised conditional on hosts_wellknown
+client HOSTIPV4 PORT_D
+??? 220
+EHLO test
+??? 250-
+??? 250-SIZE
+??? 250-LIMITS
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250 HELP
+QUIT
+??? 221
+****
+#
+# deny by acl
+client 127.0.0.1 PORT_D2
+??? 220
+EHLO test
+??? 250-
+??? 250-SIZE
+??? 250-LIMITS
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-WELLKNOWN
+??? 250 HELP
+WELLKNOWN acme-response
+??? 550
+QUIT
+??? 221
+****
+#
+# nonexistent file
+client 127.0.0.1 PORT_D
+??? 220
+EHLO test
+??? 250-
+??? 250-SIZE
+??? 250-LIMITS
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-WELLKNOWN
+??? 250 HELP
+WELLKNOWN badfile
+??? 550
+QUIT
+??? 221
+****
+#
+killdaemon
+#
+exim -DSERVER=server -DOPT=,key=path -bd -oX PORT_D:PORT_D2
+****
+#
+# dsearch with key=path permission
+# basic good file
+client 127.0.0.1 PORT_D
+??? 220
+EHLO test
+??? 250-
+??? 250-SIZE
+??? 250-LIMITS
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-WELLKNOWN
+??? 250 HELP
+WELLKNOWN acme-response
+??? 250-SIZE
+??? 250-
+??? 250-
+??? 250
+QUIT
+??? 221
+****
+#
+# subdir/good file
+client 127.0.0.1 PORT_D
+??? 220
+EHLO test
+??? 250-
+??? 250-SIZE
+??? 250-LIMITS
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-WELLKNOWN
+??? 250 HELP
+WELLKNOWN sub/acme-response
+??? 250-SIZE
+??? 250-
+??? 250-
+??? 250
+QUIT
+??? 221
+****
+#
+# nonexistent file
+client 127.0.0.1 PORT_D
+??? 220
+EHLO test
+??? 250-
+??? 250-SIZE
+??? 250-LIMITS
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-WELLKNOWN
+??? 250 HELP
+WELLKNOWN sub/badfile
+??? 550
+QUIT
+??? 221
+****
+#
+# dotdot trap
+client 127.0.0.1 PORT_D
+??? 220
+EHLO test
+??? 250-
+??? 250-SIZE
+??? 250-LIMITS
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-WELLKNOWN
+??? 250 HELP
+WELLKNOWN ../badfile
+??? 550
+QUIT
+??? 221
+****
+#
+killdaemon
diff --git a/test/scripts/4040-wellknown/REQUIRES b/test/scripts/4040-wellknown/REQUIRES
new file mode 100644 (file)
index 0000000..457fc5f
--- /dev/null
@@ -0,0 +1 @@
+support ESMTP_Wellknown