diff --git a/postfix/HISTORY b/postfix/HISTORY index 0f63bfe14..7a041dbf1 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -8778,13 +8778,13 @@ Apologies for any names omitted. logs more specific information. File: master/master_ent.c. Reported by several people. -20031125-8 +20031125-20031201 Feature: XCLIENT support to override the SMTP server's client information for logging and/or access control. This replaces the short-lived XADDR and XLOGINFO extensions. - Based on code by Victor Duchovni, with major simplifications. - Files: smtpd/{smtpd,smtpd_check,smtpd_proxy,smtpd_xclient}.c + Remotely based on code by Victor Duchovni. Files: + smtpd/{smtpd,smtpd_check,smtpd_proxy,smtpd_xclient}.c smtp/smtp_smtp_proto.c, *qmgr/qmgr_message.c, global/deliver_request.c. diff --git a/postfix/README_FILES/XCLIENT_README b/postfix/README_FILES/XCLIENT_README index 4337221ed..c77d51535 100644 --- a/postfix/README_FILES/XCLIENT_README +++ b/postfix/README_FILES/XCLIENT_README @@ -3,49 +3,42 @@ Purpose of the XCLIENT extension to SMTP The XCLIENT command targets problems in the following areas: -1 - Access control tests. SMTP server access rules are difficult -to verify when decisions can be triggered only by remote clients. +1 - Access control tests. SMTP server access rules can be difficult +to verify when decisions can be triggered by remote clients only. In order to facilitate access rule testing, an SMTP client test program needs the ability to override the SMTP server's idea of -the SMTP client hostname, network address, and other information. +the SMTP client hostname, network address, and other information, +for the entire duration of an SMTP session. 2 - Logging after content filter. With Internet->MTA1->filter->MTA2 style content filter applications, remote client information is lost when MTA1 gives the mail to the content filter. To simplify the interpretation of MTA2 logging, it would help if MTA1 could -forward client information through the content filter to MTA2. +forward client information through the content filter to MTA2, for +a single message delivery. 3 - Post-filter access control and logging. With Internet->filter->MTA style content filter applications, the filter can be simplified if it can delegate decisions concerning mail relay and other access control to the MTA. As in the first example, this requires that the filter can override the MTA's idea of the SMTP client hostname, -network address, and other information. - -The preceding suggests that there is a need for two functions: - -1 - Override the MTA's idea of SMTP client information for access - control and other purposes. This function is generally useful - for trouble shooting. The implementation can be relatively - straightforward because it updates already existing attributes. - -2 - Forward remote client information for logging purposes. This - function is limited mainly to environments that use SMTP-based - content filters. The implementation requires more invasive - changes to the MTA to store the additional attributes and to - choose between the normal attributes and the forwarded ones. +network address, and other information, for the entire duration of +an SMTP session. Command overview ================ -The EHLO keyword associated with this extension is XCLIENT. +XCLIENT is an extension to SMTP. The EHLO keyword associated with +this extension is XCLIENT. The XCLIENT OVERRIDE command updates the remote client attributes that the MTA normally uses for access control, message headers, -logging and so on, while the XCLIENT FORWARD command maintains an -additional set of attributes that is meant to be used for logging -purposes. In the absence of forwarded attributes the MTA must use -the normal remote client attribute values. +logging and so on, for the duration of an entire SMTP session. + +The XCLIENT FORWARD command maintains an additional set of attributes +that concern only one message delivery attempt. In the absence of +forwarded attributes the MTA must use the normal remote client +attribute values. The general command syntax is described below. Upper case and quoted strings specify terminals, lowercase strings specify meta @@ -59,7 +52,7 @@ case, they are in fact case insensitive. attribute = name"="value - name = ( CLIENT_NAME | CLIENT_ADDR | CLIENT_CODE | PROTOCOL | HELO_NAME ) + name = ( CLIENT_NAME|CLIENT_ADDR|CLIENT_CODE|CLIENT_PROTO|CLIENT_HELO ) value = ( { empty } | xtext ) @@ -88,10 +81,11 @@ Specific usage scenarios This section discusses the semantics of XCLIENT requests. Specific syntax details are given in the next section. -The XCLIENT OVERRIDE request modifies the attributes that the MTA -normally uses for access control, message headers, logging, and -for other purposes. Attributes that are not specified in XCLIENT -OVERRIDE requests are not modified. +The XCLIENT OVERRIDE request modifies remote client attributes that +the MTA normally uses for access control, message headers, logging, +and for other purposes, for the duraction of the entire SMTP session. +Attributes that are not specified in XCLIENT OVERRIDE requests are +not modified. The following example overrides only the client hostname and network address, leaving unchanged all other client attributes such as the @@ -100,21 +94,23 @@ mail protocol or the hostname given in the HELO command: XCLIENT OVERRIDE CLIENT_NAME=spike.porcupine.org XCLIENT OVERRIDE CLIENT_ADDR=168.100.189.2 -The XCLIENT FORWARD request specifies surrogate client attributes for -logging purposes. In the absence of any XCLIENT FORWARD attributes, the -MTA must use the normal client attributes. +The XCLIENT FORWARD request specifies remote client attributes +concerning only one message delivery attempt. The attributes are +discarded after the next MAIL FROM transaction finishes. In the +absence of any XCLIENT FORWARD attributes, the MTA must use the +normal client attributes. If only a subset of all possible XCLIENT FORWARD attributes is -specified, the unspecified attributes must either not be logged at -all, or they must be logged as if they are unknown. This avoids -the logging of attributes from mixed origins. +specified, the unspecified attributes must be treated as if they +are unknown. The implementation must not replace missing XCLIENT +FORWARD attributes by normal attributes. The following example updates all forwarded client attributes that are defined in this document, leaving none at their default unknown value: XCLIENT FORWARD CLIENT_NAME=spike.porcupine.org CLIENT_ADDR=168.100.189.2 - XCLIENT FORWARD HELO_NAME=spike.porcupine.org PROTOCOL=ESMTP + XCLIENT FORWARD CLIENT_HELO=spike.porcupine.org CLIENT_PROTO=ESMTP Note 1: attributes specified with successive XCLIENT commands accumulate. @@ -126,34 +122,36 @@ Attribute value details ======================= Attribute values are encoded as RFC 1891 xtext strings. To explicitly -specify that an attribute value is unknown, the value must be empty; -the client must not send its own internal representation of unknown -information. +specify that an attribute value is unavailable, the value must be +empty; the client must not send its own internal representation of +unavailable information. -CLIENT_CODE specifies CLIENT_NAME hostname lookup status information. -Values are OK (success), TEMP (temporary lookup failure) or PERM -(permanent lookup failure). When CLIENT_CODE is set to any value -other than OK, the CLIENT_NAME attribute is automatically set to -the unknown value. +The CLIENT_CODE attribute specifies CLIENT_NAME hostname lookup +status information. Values are OK (success), TEMP (temporary lookup +failure) or PERM (permanent lookup failure). When CLIENT_CODE is +set to any value other than OK, the CLIENT_NAME attribute is +automatically set to the unknown value. -CLIENT_NAME should specify a syntactically valid domain name and -not a numerical address. When a null client name is specified -(i.e. the client name is unknown), the CLIENT_CODE attribute is -implicitly set to PERM. When a valid domain name is specified, -CLIENT_CODE is implicitly set to OK. The server may process a -syntactically invalid domain name as if it were unknown. +The CLIENT_NAME attribute should specify a syntactically valid +domain name and not a numerical address. When a null client name +is specified (i.e. the client name is unknown), the CLIENT_CODE +attribute is implicitly set to PERM. When a valid domain name is +specified, CLIENT_CODE is implicitly set to OK. The server may +process a syntactically invalid domain name as if it were unknown. -CLIENT_ADDR must specify a numerical network address without []. +The CLIENT_ADDR attribute must specify a numerical network address +without []. -PROTOCOL is a string of up to 64 printable characters, where -printable is defined by the ANSI C isascii() and isprint() predicates. +The CLIENT_PROTO attribute should be a string of up to 64 printable +characters, where printable is defined by the ANSI C isascii() and +isprint() predicates. -HELO_NAME should be a syntactically valid HELO parameter value. +The CLIENT_HELO attribute should be a syntactically valid HELO +parameter value. -Note 3: syntactically valid CLIENT_NAME and HELO_NAME attributes -can be up to 255 characters long. The client must not send XCLIENT OVERRIDE -or XCLIENT FORWARD commands that exceed the 512 character limit of SMTP -commands. +Note 3: syntactically valid CLIENT_NAME and CLIENT_HELO attributes +can be up to 255 characters long. The client must not send XCLIENT +commands that exceed the 512 character limit of SMTP commands. Note 4: attribute values may end up in Received: or other message headers. The receiving MTA may substitute characters in order to @@ -175,6 +173,5 @@ SMTP connection caching SMTP connection caching makes it possible to deliver multiple messages within the same SMTP session. Thus, one persistent SMTP session with a content filter can carry messages from unrelated -clients. Applications should ensure that XCLIENT information from -one remote client is properly updated before commencing delivery -of mail from a different remote client. +clients. The XCLIENT FORWARD attributes are reset after the MAIL +FROM command completes, so there is no risk of information leakage. diff --git a/postfix/html/qmqpd.8.html b/postfix/html/qmqpd.8.html index 58dece64f..77e965271 100644 --- a/postfix/html/qmqpd.8.html +++ b/postfix/html/qmqpd.8.html @@ -1,4 +1,4 @@ -
+  
 QMQPD(8)                                                 QMQPD(8)
 
 NAME
@@ -44,72 +44,72 @@ QMQPD(8)                                                 QMQPD(8)
        command after a configuration change.
 
 Miscellaneous
-       debug_peer_level
+       debug_peer_level
               Increment in verbose logging level  when  a  remote
-              host  matches  a  pattern  in  the  debug_peer_list
+              host  matches  a  pattern  in  the  debug_peer_list
               parameter.
 
-       debug_peer_list
+       debug_peer_list
               List of domain or network patterns. When  a  remote
               host  matches  a pattern, increase the verbose log-
               ging  level  by  the  amount   specified   in   the
-              debug_peer_level parameter.
+              debug_peer_level parameter.
 
-       hopcount_limit
+       hopcount_limit
               Limit the number of Received: message headers.
 
-       qmqpd_authorized_clients
+       qmqpd_authorized_clients
               A list of domain or network patterns that specifies
               what clients are allowed to use the service.
 
-       qmqpd_timeout
+       qmqpd_timeout
               Limit the time to send a  server  response  and  to
               receive a client request.
 
-       soft_bounce
+       soft_bounce
               Change  hard  (D)  reject  responses  into soft (Z)
               reject responses.  This can be useful  for  testing
               purposes.
 
 Content inspection controls
-       content_filter
+       content_filter
               The  name of a mail delivery transport that filters
               mail and that either bounces mail or re-injects the
               result  back into Postfix.  This parameter uses the
               same syntax as the right-hand  side  of  a  Postfix
               transport table.
 
-       receive_override_options
+       receive_override_options
               The  following  options  override main.cf settings.
               The options are passed on to the downstream cleanup
               server.
 
-              no_address_mappings
+              no_address_mappings
                      Disable  canonical  address mapping, virtual
                      alias map expansion,  address  masquerading,
                      and  automatic  BCC recipients. Specify this
                      if address mapping etc. are to be done after
                      an external content filter.
 
-              no_header_body_checks
+              no_header_body_checks
                      Disable  header/body_checks. Specify this if
                      header/body_checks are to be done  after  an
                      external content filter.
 
 Resource controls
-       line_length_limit
+       line_length_limit
               Limit  the  amount  of memory in bytes used for the
               handling of partial input lines, and the length  of
               sender  and  recipient  addresses that are received
               from client.
 
-       message_size_limit
+       message_size_limit
               Limit the total size in bytes of a message, includ-
               ing   on-disk  storage  for  sender  and  recipient
               address information.
 
 Tarpitting
-       qmqpd_error_delay
+       qmqpd_error_delay
               Time to wait in seconds before informing the client
               of a problem. This slows down run-away errors.
 
diff --git a/postfix/src/global/deliver_request.h b/postfix/src/global/deliver_request.h
index 2264a28d7..0c379a0e2 100644
--- a/postfix/src/global/deliver_request.h
+++ b/postfix/src/global/deliver_request.h
@@ -46,6 +46,15 @@ typedef struct DELIVER_REQUEST {
     char   *client_helo;		/* helo parameter */
 } DELIVER_REQUEST;
 
+ /*
+  * Since we can't send null pointers, null strings represent unavailable
+  * attributes instead. They're less likely to explode in our face, too.
+  */
+#define DEL_REQ_ATTR_UNAVAIL(a)	(*(a))
+
+ /*
+  * How to deliver, really?
+  */
 #define DEL_REQ_FLAG_DEFLT	(DEL_REQ_FLAG_SUCCESS | DEL_REQ_FLAG_BOUNCE)
 #define DEL_REQ_FLAG_SUCCESS	(1<<0)	/* delete successful recipients */
 #define DEL_REQ_FLAG_BOUNCE	(1<<1)	/* unimplemented */
diff --git a/postfix/src/global/mail_proto.h b/postfix/src/global/mail_proto.h
index fab5dcdaa..508b05e81 100644
--- a/postfix/src/global/mail_proto.h
+++ b/postfix/src/global/mail_proto.h
@@ -159,27 +159,33 @@ extern char *mail_pathname(const char *, const char *);
 #define XCLIENT_FORWARD		"FORWARD"	/* forward function */
 #define XCLIENT_NAME		"CLIENT_NAME"	/* client name */
 #define XCLIENT_ADDR		"CLIENT_ADDR"	/* client address */
-#define XCLIENT_PROTO		"PROTOCOL"	/* client protocol */
+#define XCLIENT_PROTO		"CLIENT_PROTO"	/* client protocol */
 #define XCLIENT_CODE		"CLIENT_CODE"	/* client name status */
-#define XCLIENT_HELO		"HELO_NAME"	/* client helo */
+#define XCLIENT_HELO		"CLIENT_HELO"	/* client helo */
 
  /*
-  * Internal forms for unknown XCLIENT information.
+  * This is how Postfix represents unknown client information within smtpd or
+  * qmqpd processes.
+  * 
+  * This is not the representation that Postfix uses in queue files, in queue
+  * manager delivery requests, nor is it the representation of information in
+  * XCLIENT commands!
   */
-#define CLIENT_NAME_UNKNOWN	"unknown"
-#define CLIENT_ADDR_UNKNOWN	"unknown"
-#define CLIENT_NAMADDR_UNKNOWN	CLIENT_NAME_UNKNOWN "[" CLIENT_ADDR_UNKNOWN "]"
-#define HELO_NAME_UNKNOWN	""	/* or NULL */
-#define PROTOCOL_UNKNOWN	"unknown"
+#define CLIENT_ATTR_UNKNOWN	"unknown"
 
- /*
-  * Internal forms: recognizing unknown XCLIENT information.
-  */
-#define IS_UNK_CLNT_NAME(v)	(!(v) || !strcmp((v), CLIENT_NAME_UNKNOWN))
-#define IS_UNK_CLNT_ADDR(v)	(!(v) || !strcmp((v), CLIENT_ADDR_UNKNOWN))
-#define IS_UNK_CLNT_NAMADDR(v)	(!(v) || !strcmp((v), CLIENT_NAMADDR_UNKNOWN))
-#define IS_UNK_HELO_NAME(v)	(!(v) || !*(v))
-#define IS_UNK_PROTOCOL(v)	(!(v) || !strcmp((v), PROTOCOL_UNKNOWN))
+#define CLIENT_NAME_UNKNOWN	CLIENT_ATTR_UNKNOWN
+#define CLIENT_ADDR_UNKNOWN	CLIENT_ATTR_UNKNOWN
+#define CLIENT_NAMADDR_UNKNOWN	CLIENT_ATTR_UNKNOWN
+#define CLIENT_HELO_UNKNOWN	0
+#define CLIENT_PROTO_UNKNOWN	CLIENT_ATTR_UNKNOWN
+
+#define IS_UNK_CLIENT_ATTR(v)	(!(v) || !strcmp((v), CLIENT_ATTR_UNKNOWN))
+
+#define IS_UNK_CLIENT_NAME(v)	IS_UNK_CLIENT_ATTR(v)
+#define IS_UNK_CLIENT_ADDR(v)	IS_UNK_CLIENT_ATTR(v)
+#define IS_UNK_CLIENT_NAMADDR(v) IS_UNK_CLIENT_ATTR(v)
+#define IS_UNK_CLIENT_HELO(v)	(!(v))
+#define IS_UNK_CLIENT_PROTO(v)	IS_UNK_CLIENT_ATTR(v)
 
 /* LICENSE
 /* .ad
diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h
index b5ae497c6..de26c8c4c 100644
--- a/postfix/src/global/mail_version.h
+++ b/postfix/src/global/mail_version.h
@@ -20,7 +20,7 @@
   * Patches change the patchlevel and the release date. Snapshots change the
   * release date only, unless they include the same bugfix as a patch release.
   */
-#define MAIL_RELEASE_DATE	"20031130"
+#define MAIL_RELEASE_DATE	"20031201"
 
 #define VAR_MAIL_VERSION	"mail_version"
 #define DEF_MAIL_VERSION	"2.0.16-" MAIL_RELEASE_DATE
diff --git a/postfix/src/nqmgr/qmgr_message.c b/postfix/src/nqmgr/qmgr_message.c
index 1dc1e410e..b8bf2afa0 100644
--- a/postfix/src/nqmgr/qmgr_message.c
+++ b/postfix/src/nqmgr/qmgr_message.c
@@ -612,13 +612,13 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
     if (message->encoding == 0)
 	message->encoding = mystrdup(MAIL_ATTR_ENC_NONE);
     if (message->client_name == 0)
-	message->client_name = mystrdup(CLIENT_NAME_UNKNOWN);
+	message->client_name = mystrdup("");
     if (message->client_addr == 0)
-	message->client_addr = mystrdup(CLIENT_ADDR_UNKNOWN);
+	message->client_addr = mystrdup("");
     if (message->client_proto == 0)
-	message->client_proto = mystrdup(PROTOCOL_UNKNOWN);
+	message->client_proto = mystrdup("");
     if (message->client_helo == 0)
-	message->client_helo = mystrdup(HELO_NAME_UNKNOWN);
+	message->client_helo = mystrdup("");
 
     /*
      * Clean up.
@@ -1130,6 +1130,14 @@ void    qmgr_message_free(QMGR_MESSAGE *message)
 	myfree(message->inspect_xport);
     if (message->redirect_addr)
 	myfree(message->redirect_addr);
+    if (message->client_name)
+	myfree(message->client_name);
+    if (message->client_addr)
+	myfree(message->client_addr);
+    if (message->client_proto)
+	myfree(message->client_proto);
+    if (message->client_helo)
+	myfree(message->client_helo);
     qmgr_rcpt_list_free(&message->rcpt_list);
     qmgr_message_count--;
     myfree((char *) message);
diff --git a/postfix/src/qmgr/qmgr_message.c b/postfix/src/qmgr/qmgr_message.c
index 70fa555db..2eb2925f7 100644
--- a/postfix/src/qmgr/qmgr_message.c
+++ b/postfix/src/qmgr/qmgr_message.c
@@ -577,13 +577,13 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
     if (message->encoding == 0)
 	message->encoding = mystrdup(MAIL_ATTR_ENC_NONE);
     if (message->client_name == 0)
-	message->client_name = mystrdup(CLIENT_NAME_UNKNOWN);
+	message->client_name = mystrdup("");
     if (message->client_addr == 0)
-	message->client_addr = mystrdup(CLIENT_ADDR_UNKNOWN);
+	message->client_addr = mystrdup("");
     if (message->client_proto == 0)
-	message->client_proto = mystrdup(PROTOCOL_UNKNOWN);
+	message->client_proto = mystrdup("");
     if (message->client_helo == 0)
-	message->client_helo = mystrdup(HELO_NAME_UNKNOWN);
+	message->client_helo = mystrdup("");
 
     /*
      * Clean up.
@@ -1024,6 +1024,14 @@ void    qmgr_message_free(QMGR_MESSAGE *message)
 	myfree(message->inspect_xport);
     if (message->redirect_addr)
 	myfree(message->redirect_addr);
+    if (message->client_name)
+	myfree(message->client_name);
+    if (message->client_addr)
+	myfree(message->client_addr);
+    if (message->client_proto)
+	myfree(message->client_proto);
+    if (message->client_helo)
+	myfree(message->client_helo);
     qmgr_rcpt_list_free(&message->rcpt_list);
     qmgr_message_count--;
     myfree((char *) message);
diff --git a/postfix/src/qmqpd/qmqpd.c b/postfix/src/qmqpd/qmqpd.c
index 1615e5157..76adcae2e 100644
--- a/postfix/src/qmqpd/qmqpd.c
+++ b/postfix/src/qmqpd/qmqpd.c
@@ -285,10 +285,12 @@ static void qmqpd_copy_sender(QMQPD_STATE *state)
 
 static void qmqpd_write_attributes(QMQPD_STATE *state)
 {
-    rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
-		MAIL_ATTR_CLIENT_NAME, state->name);
-    rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
-		MAIL_ATTR_CLIENT_ADDR, state->addr);
+    if (!IS_UNK_CLIENT_NAME(state->name))
+	rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+		    MAIL_ATTR_CLIENT_NAME, state->name);
+    if (!IS_UNK_CLIENT_ADDR(state->addr))
+	rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+		    MAIL_ATTR_CLIENT_ADDR, state->addr);
     rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
 		MAIL_ATTR_ORIGIN, state->namaddr);
     rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
diff --git a/postfix/src/smtp/smtp_proto.c b/postfix/src/smtp/smtp_proto.c
index cb403aa36..91733cbff 100644
--- a/postfix/src/smtp/smtp_proto.c
+++ b/postfix/src/smtp/smtp_proto.c
@@ -484,11 +484,14 @@ int     smtp_xfer(SMTP_STATE *state)
      * commands rejected, DATA rejected) it forces the sender to abort the
      * SMTP dialog with RSET and QUIT.
      * 
-     * Update the server's remote client information to avoid leakage of past
-     * client attributes into an unrelated mail delivery.
+     * Use the XCLIENT command to forward client attributes only when a minimal
+     * amount of information is available.
      */
     nrcpt = 0;
-    if (var_smtp_send_xclient && (state->features & SMTP_FEATURE_XCLIENT))
+    if (var_smtp_send_xclient
+	&& (state->features & SMTP_FEATURE_XCLIENT)
+	&& !DEL_REQ_ATTR_UNAVAIL(request->client_name)
+	&& !DEL_REQ_ATTR_UNAVAIL(request->client_addr))
 	recv_state = send_state = SMTP_STATE_XCLIENT_ADDR;
     else
 	recv_state = send_state = SMTP_STATE_MAIL;
@@ -516,10 +519,10 @@ int     smtp_xfer(SMTP_STATE *state)
 	case SMTP_STATE_XCLIENT_ADDR:
 	    vstring_strcpy(next_command,
 		      XCLIENT_CMD " " XCLIENT_FORWARD " " XCLIENT_NAME "=");
-	    if (!IS_UNK_CLNT_NAME(request->client_name))
+	    if (!DEL_REQ_ATTR_UNAVAIL(request->client_name))
 		xtext_quote_append(next_command, request->client_name, "");
 	    vstring_strcat(next_command, " " XCLIENT_ADDR "=");
-	    if (!IS_UNK_CLNT_ADDR(request->client_addr))
+	    if (!DEL_REQ_ATTR_UNAVAIL(request->client_addr))
 		xtext_quote_append(next_command, request->client_addr, "");
 	    next_state = SMTP_STATE_XCLIENT_HELO;
 	    break;
@@ -527,10 +530,10 @@ int     smtp_xfer(SMTP_STATE *state)
 	case SMTP_STATE_XCLIENT_HELO:
 	    vstring_strcpy(next_command,
 		      XCLIENT_CMD " " XCLIENT_FORWARD " " XCLIENT_HELO "=");
-	    if (!IS_UNK_HELO_NAME(request->client_helo))
+	    if (!DEL_REQ_ATTR_UNAVAIL(request->client_helo))
 		xtext_quote_append(next_command, request->client_helo, "");
 	    vstring_strcat(next_command, " " XCLIENT_PROTO "=");
-	    if (!IS_UNK_PROTOCOL(request->client_proto))
+	    if (!DEL_REQ_ATTR_UNAVAIL(request->client_proto))
 		xtext_quote_append(next_command, request->client_proto, "");
 	    next_state = SMTP_STATE_MAIL;
 	    break;
diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c
index 38d2424ea..a9f3db2ca 100644
--- a/postfix/src/smtpd/smtpd.c
+++ b/postfix/src/smtpd/smtpd.c
@@ -667,9 +667,12 @@ static int helo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
     rcpt_reset(state);
     state->helo_name = mystrdup(printable(argv[1].strval, '?'));
     neuter(state->helo_name, "<>()\\\";:@", '?');
-    /* Changing the protocol name breaks the unauthorized pipelining check. */
-    if (strcmp(state->protocol, MAIL_PROTO_ESMTP) != 0)
-	state->protocol = MAIL_PROTO_SMTP;
+    /* Downgrading the protocol name breaks the unauthorized pipelining test. */
+    if (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0
+	&& strcasecmp(state->protocol, MAIL_PROTO_SMTP) != 0) {
+	myfree(state->protocol);
+	state->protocol = mystrdup(MAIL_PROTO_SMTP);
+    }
     smtpd_chat_reply(state, "250 %s", var_myhostname);
     return (0);
 }
@@ -705,7 +708,10 @@ static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
     rcpt_reset(state);
     state->helo_name = mystrdup(printable(argv[1].strval, '?'));
     neuter(state->helo_name, "<>()\\\";:@", '?');
-    state->protocol = MAIL_PROTO_ESMTP;
+    if (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0) {
+	myfree(state->protocol);
+	state->protocol = mystrdup(MAIL_PROTO_ESMTP);
+    }
     smtpd_chat_reply(state, "250-%s", var_myhostname);
     smtpd_chat_reply(state, "250-PIPELINING");
     if (var_message_limit)
@@ -743,7 +749,7 @@ static void helo_reset(SMTPD_STATE *state)
 
 /* mail_open_stream - open mail queue file or IPC stream */
 
-static void mail_open_stream(SMTPD_STATE *state, SMTPD_TOKEN *argv)
+static void mail_open_stream(SMTPD_STATE *state)
 {
     char   *postdrop_command;
     int     cleanup_flags;
@@ -815,7 +821,7 @@ static void mail_open_stream(SMTPD_STATE *state, SMTPD_TOKEN *argv)
 	if (*var_filter_xport)
 	    rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", var_filter_xport);
     }
-    rec_fputs(state->cleanup, REC_TYPE_FROM, argv[2].strval);
+    rec_fputs(state->cleanup, REC_TYPE_FROM, state->sender);
     if (state->encoding != 0)
 	rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
 		    MAIL_ATTR_ENCODING, state->encoding);
@@ -824,18 +830,21 @@ static void mail_open_stream(SMTPD_STATE *state, SMTPD_TOKEN *argv)
      * Store the client attributes for logging purposes.
      */
     if (SMTPD_STAND_ALONE(state) == 0) {
-	rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
-		    MAIL_ATTR_CLIENT_NAME, FORWARD_NAME(state));
-	rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
-		    MAIL_ATTR_CLIENT_ADDR, FORWARD_ADDR(state));
-	rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
-		    MAIL_ATTR_ORIGIN, FORWARD_NAMADDR(state));
-	rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
-		    MAIL_ATTR_HELO_NAME,
-		    IS_UNK_HELO_NAME(FORWARD_HELO(state)) ?
-		    HELO_NAME_UNKNOWN : FORWARD_HELO(state));
-	rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
-		    MAIL_ATTR_PROTO_NAME, FORWARD_PROTO(state));
+	if (!IS_UNK_CLIENT_NAME(FORWARD_NAME(state)))
+	    rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+			MAIL_ATTR_CLIENT_NAME, FORWARD_NAME(state));
+	if (!IS_UNK_CLIENT_ADDR(FORWARD_ADDR(state)))
+	    rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+			MAIL_ATTR_CLIENT_ADDR, FORWARD_ADDR(state));
+	if (!IS_UNK_CLIENT_NAMADDR(FORWARD_NAMADDR(state)))
+	    rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+			MAIL_ATTR_ORIGIN, FORWARD_NAMADDR(state));
+	if (!IS_UNK_CLIENT_HELO(FORWARD_HELO(state)))
+	    rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+			MAIL_ATTR_HELO_NAME, FORWARD_HELO(state));
+	if (!IS_UNK_CLIENT_PROTO(FORWARD_PROTO(state)))
+	    rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+			MAIL_ATTR_PROTO_NAME, FORWARD_PROTO(state));
     }
     if (state->verp_delims)
 	rec_fputs(state->cleanup, REC_TYPE_VERP, state->verp_delims);
@@ -1129,6 +1138,8 @@ static void mail_reset(SMTPD_STATE *state)
 	myfree(state->proxy_mail);
 	state->proxy_mail = 0;
     }
+    if (state->xclient.used)
+	smtpd_xclient_reset(state);
 }
 
 /* rcpt_cmd - process RCPT TO command */
@@ -1201,7 +1212,7 @@ static int rcpt_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
 	    return (-1);
 	}
     } else if (state->cleanup == 0) {
-	mail_open_stream(state, argv);
+	mail_open_stream(state);
     }
     if (state->proxy && smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK,
 					"%s", STR(state->buffer)) != 0) {
@@ -1702,13 +1713,19 @@ static const struct attr_offset attr_offset = {
 #define STR_ATTR(state, func, attr) *((char **) PTR_ATTR(state, func, attr))
 #define INT_ATTR(state, func, attr) *((int *) PTR_ATTR(state, func, attr))
 
-#define UPDATE_STR(state, func, attr, value) { \
+#define RST_STR_ATTR(state, func, attr) { \
+	if (STR_ATTR(state, func, attr)) \
+	    myfree(STR_ATTR(state, func, attr)); \
+	STR_ATTR(state, func, attr) = 0; \
+    }
+
+#define UPD_STR_ATTR(state, func, attr, value) { \
 	if (STR_ATTR(state, func, attr)) \
 	    myfree(STR_ATTR(state, func, attr)); \
 	STR_ATTR(state, func, attr) = mystrdup(value); \
     }
 
-#define UPDATE_INT(state, func, attr, value) { \
+#define UPD_INT_ATTR(state, func, attr, value) { \
 	INT_ATTR(state, func, attr) = (value); \
     }
 
@@ -1755,7 +1772,8 @@ static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
 	function = FUNC_OVERRIDE;
     } else if (STREQ(arg_val, XCLIENT_FORWARD)) {
 	function = FUNC_FORWARD;
-	state->xclient.used = 1;
+	if (state->xclient.used == 0)
+	    smtpd_xclient_preset(state);
     } else {					/* error */
 	state->error_mask |= MAIL_ERROR_PROTOCOL;
 	smtpd_chat_reply(state, "501 Bad %s function: %s",
@@ -1764,7 +1782,9 @@ static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
     }
 
     /*
-     * Iterate over all NAME=VALUE attributes.
+     * Iterate over all NAME=VALUE attributes. An empty value means the
+     * information was not provided by the client and that we must not fall
+     * back to the non-XCLIENT value.
      */
     for (arg_no = 2; arg_no < argc; arg_no++) {
 	arg_val = argv[arg_no].strval;
@@ -1810,11 +1830,11 @@ static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
 				     cooked_value);
 		    return (-1);
 		}
-		UPDATE_STR(state, function, name, cooked_value);
-		UPDATE_INT(state, function, peer_code, SMTPD_PEER_CODE_OK);
+		UPD_STR_ATTR(state, function, name, cooked_value);
+		UPD_INT_ATTR(state, function, peer_code, SMTPD_PEER_CODE_OK);
 	    } else {
-		UPDATE_STR(state, function, name, CLIENT_NAME_UNKNOWN);
-		UPDATE_INT(state, function, peer_code, SMTPD_PEER_CODE_PERM);
+		UPD_STR_ATTR(state, function, name, CLIENT_NAME_UNKNOWN);
+		UPD_INT_ATTR(state, function, peer_code, SMTPD_PEER_CODE_PERM);
 	    }
 	    update_namaddr = 1;
 	}
@@ -1822,7 +1842,7 @@ static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
 	/*
 	 * CLIENT_ADDR=client network address.
 	 */
-	else if (STREQ(arg_val, "CLIENT_ADDR")) {
+	else if (STREQ(arg_val, XCLIENT_ADDR)) {
 	    if (*raw_value) {
 		if (!valid_hostaddr(cooked_value, DONT_GRIPE)) {
 		    state->error_mask |= MAIL_ERROR_PROTOCOL;
@@ -1830,9 +1850,9 @@ static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
 				     cooked_value);
 		    return (-1);
 		}
-		UPDATE_STR(state, function, addr, cooked_value);
+		UPD_STR_ATTR(state, function, addr, cooked_value);
 	    } else {
-		UPDATE_STR(state, function, name, CLIENT_ADDR_UNKNOWN);
+		UPD_STR_ATTR(state, function, addr, CLIENT_ADDR_UNKNOWN);
 	    }
 	    update_namaddr = 1;
 	}
@@ -1841,16 +1861,16 @@ static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
 	 * CLIENT_CODE=status. Reset the client hostname if the hostname
 	 * lookup status is not OK.
 	 */
-	else if (STREQ(arg_val, "CLIENT_CODE")) {
+	else if (STREQ(arg_val, XCLIENT_CODE)) {
 	    if (STREQ(cooked_value, "OK")) {
-		UPDATE_INT(state, function, peer_code, SMTPD_PEER_CODE_OK);
+		UPD_INT_ATTR(state, function, peer_code, SMTPD_PEER_CODE_OK);
 	    } else if (STREQ(cooked_value, "TEMP")) {
-		UPDATE_INT(state, function, peer_code, SMTPD_PEER_CODE_TEMP);
-		UPDATE_STR(state, function, name, CLIENT_NAME_UNKNOWN);
+		UPD_INT_ATTR(state, function, peer_code, SMTPD_PEER_CODE_TEMP);
+		UPD_STR_ATTR(state, function, name, CLIENT_NAME_UNKNOWN);
 		update_namaddr = 1;
 	    } else if (STREQ(cooked_value, "PERM")) {
-		UPDATE_INT(state, function, peer_code, SMTPD_PEER_CODE_PERM);
-		UPDATE_STR(state, function, name, CLIENT_NAME_UNKNOWN);
+		UPD_INT_ATTR(state, function, peer_code, SMTPD_PEER_CODE_PERM);
+		UPD_STR_ATTR(state, function, name, CLIENT_NAME_UNKNOWN);
 		update_namaddr = 1;
 	    } else {
 		state->error_mask |= MAIL_ERROR_PROTOCOL;
@@ -1861,12 +1881,10 @@ static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
 	}
 
 	/*
-	 * HELO_NAME=hostname. An empty value means the information was not
-	 * provided by the client and that we must not fall back to the
-	 * non-XCLIENT value. Disallow characters that could mess up our own
-	 * Received: message headers but allow [].
+	 * CLIENT_HELO=hostname. Disallow characters that could mess up our
+	 * own Received: message headers but allow [].
 	 */
-	else if (STREQ(arg_val, "HELO_NAME")) {
+	else if (STREQ(arg_val, XCLIENT_HELO)) {
 	    if (*raw_value) {
 		if (strlen(cooked_value) > VALID_HOSTNAME_LEN) {
 		    state->error_mask |= MAIL_ERROR_PROTOCOL;
@@ -1875,17 +1893,17 @@ static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
 		    return (-1);
 		}
 		neuter(cooked_value, "<>()\\\";:@", '?');
-		UPDATE_STR(state, function, helo_name, cooked_value);
+		UPD_STR_ATTR(state, function, helo_name, cooked_value);
 	    } else {
-		UPDATE_STR(state, function, helo_name, HELO_NAME_UNKNOWN);
+		RST_STR_ATTR(state, function, helo_name);
 	    }
 	}
 
 	/*
-	 * PROTOCOL=protocol name. Disallow characters that could mess up our
-	 * own Received: message headers.
+	 * CLIENT_PROTO=protocol name. Disallow characters that could mess up
+	 * our own Received: message headers.
 	 */
-	else if (STREQ(arg_val, "PROTOCOL")) {
+	else if (STREQ(arg_val, XCLIENT_PROTO)) {
 	    if (*raw_value) {
 		if (*cooked_value == 0 || strlen(cooked_value) > 64) {
 		    state->error_mask |= MAIL_ERROR_PROTOCOL;
@@ -1894,9 +1912,9 @@ static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
 		    return (-1);
 		}
 		neuter(cooked_value, "[]<>()\\\";:@", '?');
-		UPDATE_STR(state, function, protocol, cooked_value);
+		UPD_STR_ATTR(state, function, protocol, cooked_value);
 	    } else {
-		UPDATE_STR(state, function, protocol, PROTOCOL_UNKNOWN);
+		UPD_STR_ATTR(state, function, protocol, CLIENT_PROTO_UNKNOWN);
 	    }
 	}
 
@@ -2039,11 +2057,13 @@ static void smtpd_proto(SMTPD_STATE *state, const char *service)
     case 0:
 
 	/*
-	 * XXX The client connection count/rate control uses the real client
-	 * name/address to maintain consistency between connect and
-	 * disconnect events.
+	 * XXX The client connection count/rate control must be consistent in
+	 * its use of client address information in connect and disconnect
+	 * events. For now we exclude xclient authorized hosts from
+	 * connection count/rate control.
 	 */
 	if (SMTPD_STAND_ALONE(state) == 0
+	    && !xclient_allowed
 	    && anvil_clnt
 	    && !namadr_list_match(hogger_list, state->name, state->addr)
 	    && anvil_clnt_connect(anvil_clnt, service, state->addr,
@@ -2129,11 +2149,13 @@ static void smtpd_proto(SMTPD_STATE *state, const char *service)
     }
 
     /*
-     * XXX The client connection count/rate control uses the real client
-     * name/address to maintain consistency between connect and disconnect
-     * events.
+     * XXX The client connection count/rate control must be consistent in its
+     * use of client address information in connect and disconnect events.
+     * For now we exclude xclient authorized hosts from connection count/rate
+     * control.
      */
     if (SMTPD_STAND_ALONE(state) == 0
+	&& !xclient_allowed
 	&& anvil_clnt
 	&& !namadr_list_match(hogger_list, state->name, state->addr))
 	anvil_clnt_disconnect(anvil_clnt, service, state->addr);
diff --git a/postfix/src/smtpd/smtpd.h b/postfix/src/smtpd/smtpd.h
index 3b0a8d037..2dec26406 100644
--- a/postfix/src/smtpd/smtpd.h
+++ b/postfix/src/smtpd/smtpd.h
@@ -142,9 +142,7 @@ extern void smtpd_peer_reset(SMTPD_STATE *state);
 #define SMTPD_PEER_CODE_PERM	5
 
  /*
-  * XCLIENT support to override logging and/or access control attributes. It
-  * makes no sense to maintain separate attribute sets for XCLIENT LOG or
-  * XCLIENT ACL, so we set a flag to distinguish purpose.
+  * Choose between normal or forwarded attributes.
   */
 #define SMTPD_FEATURE_XCLIENT (1<<0)	/* XCLIENT supported */
 
@@ -159,6 +157,7 @@ extern void smtpd_peer_reset(SMTPD_STATE *state);
 #define FORWARD_HELO(s)		MAYBE_FORWARD((s), helo_name)
 
 extern void smtpd_xclient_init(SMTPD_STATE *state);
+extern void smtpd_xclient_preset(SMTPD_STATE *state);
 extern void smtpd_xclient_reset(SMTPD_STATE *state);
 
  /*
diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c
index ab45aa321..d22146e83 100644
--- a/postfix/src/smtpd/smtpd_check.c
+++ b/postfix/src/smtpd/smtpd_check.c
@@ -970,7 +970,7 @@ static int reject_unknown_client(SMTPD_STATE *state)
     if (msg_verbose)
 	msg_info("%s: %s %s", myname, state->name, state->addr);
 
-    if (IS_UNK_CLNT_NAME(state->name))
+    if (strcasecmp(state->name, "unknown") == 0)
 	return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
 		 "%d Client host rejected: cannot find your hostname, [%s]",
 				   state->peer_code == SMTPD_PEER_CODE_PERM ?
@@ -2524,7 +2524,7 @@ static const char *smtpd_expand_lookup(const char *name, int unused_mode,
     } else if (STREQ(name, MAIL_ATTR_CLIENT_NAME)) {
 	return (state->name);
     } else if (STREQ(name, MAIL_ATTR_HELO_NAME)) {
-	return (state->helo_name ?  state->helo_name : "");
+	return (state->helo_name ? state->helo_name : "");
     } else if (STREQN(name, MAIL_ATTR_SENDER, CONST_LEN(MAIL_ATTR_SENDER))) {
 	return (smtpd_expand_addr(state->expand_buf, state->sender,
 				  name, CONST_LEN(MAIL_ATTR_SENDER)));
@@ -2910,7 +2910,7 @@ static int check_policy_service(SMTPD_STATE *state, const char *server,
 			  ATTR_TYPE_STR, MAIL_ATTR_CLIENT_ADDR, state->addr,
 			  ATTR_TYPE_STR, MAIL_ATTR_CLIENT_NAME, state->name,
 			  ATTR_TYPE_STR, MAIL_ATTR_HELO_NAME,
-			  state->helo_name ?  state->helo_name : "",
+			  state->helo_name ? state->helo_name : "",
 			  ATTR_TYPE_STR, MAIL_ATTR_SENDER,
 			  state->sender ? state->sender : "",
 			  ATTR_TYPE_STR, MAIL_ATTR_RECIP,
@@ -3093,7 +3093,7 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions,
 			 name);
 	    else {
 		cpp += 1;
-		if (!IS_UNK_CLNT_NAME(state->name))
+		if (strcasecmp(state->name, "unknown") != 0)
 		    status = reject_rbl_domain(state, *cpp, state->name,
 					       SMTPD_NAME_CLIENT);
 	    }
diff --git a/postfix/src/smtpd/smtpd_proxy.c b/postfix/src/smtpd/smtpd_proxy.c
index f81ce2dea..35a89505c 100644
--- a/postfix/src/smtpd/smtpd_proxy.c
+++ b/postfix/src/smtpd/smtpd_proxy.c
@@ -245,26 +245,29 @@ int     smtpd_proxy_open(SMTPD_STATE *state, const char *service,
 	    state->proxy_features |= SMTPD_FEATURE_XCLIENT;
 
     /*
-     * Send all XCLIENT attributes. Transform internal forms to external
+     * Send all XCLIENT attributes, but only if we have some minimal amount
+     * of remote client information. Transform internal forms to external
      * forms and encode the result as xtext.
      */
-    if (state->proxy_features & SMTPD_FEATURE_XCLIENT) {
+    if ((state->proxy_features & SMTPD_FEATURE_XCLIENT)
+	&& (!IS_UNK_CLIENT_NAME(FORWARD_NAME(state))
+	    || !IS_UNK_CLIENT_ADDR(FORWARD_ADDR(state)))) {
 	buf = vstring_alloc(100);
 	vstring_strcpy(buf, XCLIENT_CMD " " XCLIENT_FORWARD
 		       " " XCLIENT_NAME "=");
-	if (!IS_UNK_CLNT_NAME(FORWARD_NAME(state)))
+	if (!IS_UNK_CLIENT_NAME(FORWARD_NAME(state)))
 	    xtext_quote_append(buf, FORWARD_NAME(state), "");
 	vstring_strcat(buf, " " XCLIENT_ADDR "=");
-	if (!IS_UNK_CLNT_ADDR(FORWARD_ADDR(state)))
+	if (!IS_UNK_CLIENT_ADDR(FORWARD_ADDR(state)))
 	    xtext_quote_append(buf, FORWARD_ADDR(state), "");
 	bad = smtpd_proxy_cmd(state, SMTPD_PROX_WANT_ANY, "%s", STR(buf));
 	if (bad == 0) {
 	    vstring_strcpy(buf, XCLIENT_CMD " " XCLIENT_FORWARD
 			   " " XCLIENT_HELO "=");
-	    if (!IS_UNK_HELO_NAME(FORWARD_HELO(state)))
+	    if (!IS_UNK_CLIENT_HELO(FORWARD_HELO(state)))
 		xtext_quote_append(buf, FORWARD_HELO(state), "");
 	    vstring_strcat(buf, " " XCLIENT_PROTO "=");
-	    if (!IS_UNK_PROTOCOL(FORWARD_PROTO(state)))
+	    if (!IS_UNK_CLIENT_PROTO(FORWARD_PROTO(state)))
 		xtext_quote_append(buf, FORWARD_PROTO(state), "");
 	    bad = smtpd_proxy_cmd(state, SMTPD_PROX_WANT_ANY, "%s", STR(buf));
 	}
diff --git a/postfix/src/smtpd/smtpd_state.c b/postfix/src/smtpd/smtpd_state.c
index bbfe325ea..aad931c56 100644
--- a/postfix/src/smtpd/smtpd_state.c
+++ b/postfix/src/smtpd/smtpd_state.c
@@ -88,7 +88,7 @@ void    smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream)
     state->verp_delims = 0;
     state->recipient = 0;
     state->etrn_name = 0;
-    state->protocol = MAIL_PROTO_SMTP;
+    state->protocol = mystrdup(MAIL_PROTO_SMTP);
     state->where = SMTPD_AFTER_CONNECT;
     state->recursion = 0;
     state->msg_size = 0;
@@ -140,6 +140,8 @@ void    smtpd_state_reset(SMTPD_STATE *state)
      */
     if (state->buffer)
 	vstring_free(state->buffer);
+    if (state->protocol)
+	myfree(state->protocol);
     smtpd_peer_reset(state);
     smtpd_xclient_reset(state);
     if (state->defer_if_permit.reason)
diff --git a/postfix/src/smtpd/smtpd_xclient.c b/postfix/src/smtpd/smtpd_xclient.c
index d021c4585..874b7570e 100644
--- a/postfix/src/smtpd/smtpd_xclient.c
+++ b/postfix/src/smtpd/smtpd_xclient.c
@@ -12,13 +12,14 @@
 /*	void	smtpd_xclient_reset(state)
 /*	SMTPD_STATE *state;
 /* DESCRIPTION
-/*	smtpd_xclient_init() initializes state variables that are
-/*	used for storage of XCLIENT command parameters.
-/*	These variables override specific members of the global state
-/*	structure for access control or logging purposes.
+/*	smtpd_xclient_init() zeroes the attributes for storage of XCLIENT
+/*	FORWARD command parameters.
 /*
-/*	smtpd_xclient_reset() releases memory allocated after the return
-/*	from smtpd_xclient_init().
+/*	smtpd_xclient_preset() takes the result from smtpd_xclient_init()
+/*	and sets all fields to the same "unknown" value that regular
+/*	client attributes would have.
+*/
+/*	smtpd_xclient_reset() restores the state from smtpd_xclient_init().
 /* LICENSE
 /* .ad
 /* .fi
@@ -52,21 +53,42 @@
 void    smtpd_xclient_init(SMTPD_STATE *state)
 {
     state->xclient.used = 0;
+    state->xclient.name = 0;
+    state->xclient.addr = 0;
+    state->xclient.namaddr = 0;
+    state->xclient.peer_code = 0;
+    state->xclient.protocol = 0;
+    state->xclient.helo_name = 0;
+}
+
+/* smtpd_xclient_preset - set xclient attributes to "unknown" */
+
+void    smtpd_xclient_preset(SMTPD_STATE *state)
+{
+
+    /*
+     * This is a temporary solution. Unknown forwarded attributes get the
+     * same values as unknown normal attributes, so that we don't break
+     * assumptions in pre-existing code.
+     */
+    state->xclient.used = 1;
     state->xclient.name = mystrdup(CLIENT_NAME_UNKNOWN);
     state->xclient.addr = mystrdup(CLIENT_ADDR_UNKNOWN);
     state->xclient.namaddr = mystrdup(CLIENT_NAMADDR_UNKNOWN);
-    state->xclient.peer_code = 0;
-    state->xclient.protocol = mystrdup(PROTOCOL_UNKNOWN);
-    state->xclient.helo_name = mystrdup(HELO_NAME_UNKNOWN);
+    state->xclient.protocol = mystrdup(CLIENT_PROTO_UNKNOWN);
 }
 
 /* smtpd_xclient_reset - reset XCLIENT attributes */
 
 void    smtpd_xclient_reset(SMTPD_STATE *state)
 {
-    myfree(state->xclient.name);
-    myfree(state->xclient.addr);
-    myfree(state->xclient.namaddr);
-    myfree(state->xclient.protocol);
-    myfree(state->xclient.helo_name);
+#define FREE_AND_WIPE(s) { if (s) myfree(s); s = 0; }
+
+    state->xclient.used = 0;
+    FREE_AND_WIPE(state->xclient.name);
+    FREE_AND_WIPE(state->xclient.addr);
+    FREE_AND_WIPE(state->xclient.namaddr);
+    state->xclient.peer_code = 0;
+    FREE_AND_WIPE(state->xclient.protocol);
+    FREE_AND_WIPE(state->xclient.helo_name);
 }