Gitweb:
https://git.exim.org/exim.git/commitdiff/e0b3815dc398d7abaa1f81c9f26b1c9b050e94c0
Commit: e0b3815dc398d7abaa1f81c9f26b1c9b050e94c0
Parent: b66eb2387fe955529001a76daab428d6dfb50c89
Author: Jeremy Harris <jgh146exb@???>
AuthorDate: Thu May 30 16:20:52 2024 +0100
Committer: Jeremy Harris <jgh146exb@???>
CommitDate: Thu May 30 16:32:57 2024 +0100
SMTP WELLKNOWN extension
---
doc/doc-docbook/spec.xfpt | 110 ++++-
doc/doc-txt/NewStuff | 4 +
doc/doc-txt/experimental-spec.txt | 3 +
doc/doc-txt/id-wellknown.txt | 145 ++++++
src/src/EDITME | 3 +
src/src/acl.c | 124 ++++-
src/src/auths/xtextencode.c | 2 +
src/src/config.h.defaults | 1 +
src/src/exim.c | 3 +
src/src/expand.c | 859 +++++++++++++++++-----------------
src/src/globals.c | 16 +-
src/src/globals.h | 8 +
src/src/macro_predef.c | 3 +
src/src/macros.h | 11 +
src/src/readconf.c | 6 +
src/src/smtp_in.c | 86 +++-
src/util/mailtest | 486 +++++++++++++++++++
test/aux-fixed/4040/acme-response | 3 +
test/aux-fixed/4040/sub/acme-response | 3 +
test/confs/4040 | 29 ++
test/log/4040 | 21 +
test/rejectlog/4040 | 6 +
test/runtest | 12 +-
test/scripts/4040-wellknown/4040 | 157 +++++++
test/scripts/4040-wellknown/REQUIRES | 1 +
25 files changed, 1631 insertions(+), 471 deletions(-)
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index cea683810..ef0270540 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -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>>&.
+
+
+.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
@@ -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_wellknown%& "ACL for WELLKNOWN"
.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 &%wellknown_advertise_hosts%& "advertise WELLKNOWN to these hosts"
.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.
+.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
@@ -18913,6 +18935,14 @@ absolute and untainted.
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.
@@ -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 "WELLKNOWN" "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_wellknown%& "ACL for WELLKNOWN"
.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.
+.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
@@ -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"&.
-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>>&.
+
+.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
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index b0702eea2..3253b90aa 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -24,6 +24,10 @@ Version 4.98
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
------------
diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt
index aa3a278fd..56ee10f82 100644
--- a/doc/doc-txt/experimental-spec.txt
+++ b/doc/doc-txt/experimental-spec.txt
@@ -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
+
+
+
--------------------------------------------------------------
End of file
--------------------------------------------------------------
diff --git a/doc/doc-txt/id-wellknown.txt b/doc/doc-txt/id-wellknown.txt
new file mode 100644
index 000000000..14d89cbb1
--- /dev/null
+++ b/doc/doc-txt/id-wellknown.txt
@@ -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.
diff --git a/src/src/EDITME b/src/src/EDITME
index 4a33677d5..1440b4b44 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -591,6 +591,9 @@ DISABLE_MAL_MKS=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
diff --git a/src/src/acl.c b/src/src/acl.c
index 4e88fc1ac..bfb32df6f 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -57,9 +57,7 @@ static int msgcond[] = {
#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,
@@ -119,7 +117,8 @@ enum { ACLC_ACL,
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
@@ -149,7 +148,7 @@ static condition_def conditions[] = {
[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
@@ -188,7 +187,7 @@ static condition_def conditions[] = {
#ifdef EXPERIMENTAL_DCC
[ACLC_DCC] = { US"dcc", TRUE, FALSE,
- (unsigned int)
+ (unsigned)
~(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,
- (unsigned int)
+ (unsigned)
~(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,
- (unsigned int)
+ (unsigned)
~(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,
- (unsigned int)
+ (unsigned)
~(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,
- (unsigned int)
+ (unsigned)
~(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,
- (unsigned int)
+ (unsigned)
~(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,
- (unsigned int)
+ (unsigned)
~(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,
- (unsigned int) ~(ACL_BIT_DATA |
+ (unsigned) ~(ACL_BIT_DATA |
# ifndef DISABLE_PRDR
ACL_BIT_PRDR |
# endif
@@ -370,8 +369,7 @@ for (condition_def * c = conditions; c < conditions + nelem(conditions); c++)
#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,
@@ -411,6 +409,9 @@ enum {
#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)
- }
+ },
+#endif
+#ifndef DISABLE_WELLKNOWN
+[CONTROL_WELLKNOWN] =
+ { US"wellknown", TRUE, (unsigned) ~ACL_BIT_WELLKNOWN
+ },
#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");
- return FALSE;;
+ return FALSE;
}
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 *
*************************************************/
@@ -3311,7 +3391,7 @@ for (; cb; cb = cb->next)
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 */
@@ -3323,6 +3403,7 @@ for (; cb; cb = cb->next)
return ERROR;
}
+ /*XXX ought to sort these, just for sanity */
switch(control_type)
{
case CONTROL_AUTH_UNADVERTISED:
@@ -3668,8 +3749,13 @@ for (; cb; cb = cb->next)
break;
}
return ERROR;
-#endif
+#endif /*I18N*/
+#ifndef DISABLE_WELLKNOWN
+ case CONTROL_WELLKNOWN:
+ rc = *p == '/' ? wellknown_process(p+1, log_msgptr) : FAIL;
+ break;
+#endif
}
break;
}
diff --git a/src/src/auths/xtextencode.c b/src/src/auths/xtextencode.c
index 75be18161..00f36e544 100644
--- a/src/src/auths/xtextencode.c
+++ b/src/src/auths/xtextencode.c
@@ -40,3 +40,5 @@ return string_from_gstring(g);
/* End of xtextencode.c */
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index c08f1874d..22749b174 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -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_WELLKNOWN
#define ENABLE_DISABLE_FSYNC
diff --git a/src/src/exim.c b/src/src/exim.c
index 040df2cd0..b78839b57 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -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_WELLKNOWN
+ g = string_cat(g, US" ESMTP_Wellknown");
+#endif
#ifndef DISABLE_EVENT
g = string_cat(g, US" Event");
#endif
diff --git a/src/src/expand.c b/src/src/expand.c
index 1d121756d..4bc680544 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -259,7 +259,9 @@ static uschar *op_table_main[] = {
US"strlen",
US"substr",
US"uc",
- US"utf8clean" };
+ US"utf8clean",
+ US"xtextd",
+ };
enum {
EOP_ADDRESS = nelem(op_table_underscore),
@@ -307,7 +309,9 @@ enum {
EOP_STRLEN,
EOP_SUBSTR,
EOP_UC,
- EOP_UTF8CLEAN };
+ EOP_UTF8CLEAN,
+ EOP_XTEXTD,
+ };
/* Table of condition names, and corresponding switch numbers. The names must
@@ -7326,19 +7330,20 @@ NOT_ITEM: ;
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;
}
+ {
+ uschar * s = sub;
+ }
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;
}
@@ -7774,7 +7779,6 @@ NOT_ITEM: ;
}
else
yield = string_cat(yield, sub);
- break;
}
/* quote_lookuptype does lookup-specific quoting */
@@ -7806,526 +7810,533 @@ NOT_ITEM: ;
}
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
{
- 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
- 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 */
- /* 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
- 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
- uschar * s = b64encode(CUS sub, Ustrlen(sub));
+ uschar * s = b64encode(CUS sub, Ustrlen(sub));
#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;
+ 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;
diff --git a/src/src/globals.c b/src/src/globals.c
index 4e5fd2991..8230fe9cb 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -467,7 +467,7 @@ uschar *acl_smtp_quit = 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
@@ -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",
+#ifndef DISABLE_WELLKNOWN
+ [ACL_WHERE_WELLKNOWN] = US"WELLKNOWN",
+#endif
[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",
+#ifndef DISABLE_WELLKNOWN
+ [ACL_WHERE_WELLKNOWN] =US"550",
+#endif
[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"*";
-#ifdef EXPERIMENTAL_WELLKNOWN
-uschar *hosts_wellknown = 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;
+#ifndef DISABLE_WELLKNOWN
+uschar *wellknown_advertise_hosts = NULL;
+uschar *wellknown_response = NULL;
+#endif
+
/* End of globals.c */
/* vi: aw ai sw=2
*/
diff --git a/src/src/globals.h b/src/src/globals.h
index 30c8bbad4..febc30c84 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -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 */
+#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 */
@@ -1134,4 +1137,9 @@ extern uschar *version_string; /* Version string */
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 */
diff --git a/src/src/macro_predef.c b/src/src/macro_predef.c
index 9b354d345..55401e316 100644
--- a/src/src/macro_predef.c
+++ b/src/src/macro_predef.c
@@ -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_WELLKNOWN
+ builtin_macro_create(US"_HAVE_WELLKNOWN");
+#endif
#ifdef EXPERIMENTAL_XCLIENT
builtin_macro_create(US"_HAVE_XCLIENT");
#endif
diff --git a/src/src/macros.h b/src/src/macros.h
index 2938b2523..16d1503f2 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -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,
+#ifndef DISABLE_WELLKNOWN
+ SCH_WELLKNOWN,
+#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,
+#ifndef DISABLE_WELLKNOWN
+ ACL_WHERE_WELLKNOWN,
+#endif
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)
+#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)
@@ -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 */
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/readconf.c b/src/src/readconf.c
index d87a56f3d..aef07184a 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -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} },
+#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} },
@@ -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} },
+#ifndef DISABLE_WELLKNOWN
+ { "wellknown_advertise_hosts",opt_stringptr, {&wellknown_advertise_hosts} },
+#endif
{ "write_rejectlog", opt_bool, {&write_rejectlog} },
};
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index ff50c80f9..c941d115c 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -86,6 +86,9 @@ enum {
/* 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 */
@@ -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,
- TOO_MANY_NONMAIL_CMD };
+ TOO_MANY_NONMAIL_CMD
+};
/* 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 },
- { "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. */
@@ -253,6 +260,9 @@ uschar * smtp_names[] =
[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
@@ -1946,7 +1956,7 @@ while (done <= 0)
case HELP_CMD:
case NOOP_CMD:
case ETRN_CMD:
-#ifdef EXPERIMENTAL_WELLKNOWN
+#ifndef DISABLE_WELLKNOWN
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. */
-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;
@@ -2852,7 +2860,6 @@ for (;;)
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);
-/* 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
@@ -2996,7 +3004,7 @@ switch (where)
{
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);
}
@@ -3375,7 +3383,7 @@ Returns: nothing
*/
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);
@@ -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)
{
@@ -3686,9 +3724,8 @@ while (done <= 0)
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;
@@ -4213,12 +4250,12 @@ while (done <= 0)
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. */
-#ifndef DISABLE_TLS
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
+#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. */
@@ -4299,6 +4343,14 @@ while (done <= 0)
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:
{
@@ -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);
+#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);
diff --git a/src/util/mailtest b/src/util/mailtest
new file mode 100755
index 000000000..0c50d93f5
--- /dev/null
+++ b/src/util/mailtest
@@ -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@??? [ 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@???>
+
+=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
index 000000000..d3f618d8c
--- /dev/null
+++ b/test/aux-fixed/4040/acme-response
@@ -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
index 000000000..d3f618d8c
--- /dev/null
+++ b/test/aux-fixed/4040/sub/acme-response
@@ -0,0 +1,3 @@
+line 1
+line 2
+last line
diff --git a/test/confs/4040 b/test/confs/4040
new file mode 100644
index 000000000..c5c6c3c0e
--- /dev/null
+++ b/test/confs/4040
@@ -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
index 000000000..4274a7a75
--- /dev/null
+++ b/test/log/4040
@@ -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
index 000000000..9d4ca3aa6
--- /dev/null
+++ b/test/rejectlog/4040
@@ -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
diff --git a/test/runtest b/test/runtest
index b959107c1..683dee2d1 100755
--- a/test/runtest
+++ b/test/runtest
@@ -1430,6 +1430,9 @@ RESET_AFTER_EXTRA_LINE_READ:
# 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\?/;
@@ -1458,7 +1461,10 @@ RESET_AFTER_EXTRA_LINE_READ:
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/;
@@ -3663,7 +3669,7 @@ while (<EXIMINFO>)
}
elsif (/^Support for: (.*)/)
- {
+ { # Compile-time features - exim -bV
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 (.*)$/)
- {
+ { #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";
diff --git a/test/scripts/4040-wellknown/4040 b/test/scripts/4040-wellknown/4040
new file mode 100644
index 000000000..8ca40306f
--- /dev/null
+++ b/test/scripts/4040-wellknown/4040
@@ -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
index 000000000..457fc5f74
--- /dev/null
+++ b/test/scripts/4040-wellknown/REQUIRES
@@ -0,0 +1 @@
+support ESMTP_Wellknown
--
## subscription configuration (requires account):
##
https://lists.exim.org/mailman3/postorius/lists/exim-cvs.lists.exim.org/
## unsubscribe (doesn't require an account):
## exim-cvs-unsubscribe@???
## Exim details at
http://www.exim.org/
## Please use the Wiki with this list -
http://wiki.exim.org/