+ if (smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0)
+ switch(addr->transport_return)
+ {
+ case PENDING_OK:
+ new_domain_record.random_result = ccache_accept;
+ break;
+ case FAIL:
+ new_domain_record.random_result = ccache_reject;
+
+ /* Between each check, issue RSET, because some servers accept only
+ one recipient after MAIL FROM:<>.
+ XXX We don't care about that for postmaster_full. Should we? */
+
+ if ((done =
+ smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0 &&
+ smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
+ '2', callout)))
+ break;
+
+ HDEBUG(D_acl|D_v)
+ debug_printf_indent("problem after random/rset/mfrom; reopen conn\n");
+ random_local_part = NULL;
+#ifdef SUPPORT_TLS
+ tls_close(FALSE, TRUE);
+#endif
+ HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
+ (void)close(sx.inblock.sock);
+ sx.inblock.sock = sx.outblock.sock = -1;
+#ifndef DISABLE_EVENT
+ (void) event_raise(addr->transport->event_action,
+ US"tcp:close", NULL);
+#endif
+ addr->address = main_address;
+ addr->transport_return = PENDING_DEFER;
+ sx.first_addr = sx.sync_addr = addr;
+ sx.ok = FALSE;
+ sx.send_rset = TRUE;
+ sx.completed_addr = FALSE;
+ goto tls_retry_connection;
+ }
+
+ /* Re-setup for main verify, or for the error message when failing */
+ addr->address = main_address;
+ addr->transport_return = PENDING_DEFER;
+ sx.first_addr = sx.sync_addr = addr;
+ sx.ok = FALSE;
+ sx.send_rset = TRUE;
+ sx.completed_addr = FALSE;
+ }
+ else
+ done = TRUE;
+
+ /* Main verify. If the host is accepting all local parts, as determined
+ by the "random" check, we don't need to waste time doing any further
+ checking. */
+
+ if (done)
+ {
+ done = FALSE;
+ switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
+ {
+ case 0: switch(addr->transport_return) /* ok so far */
+ {
+ case PENDING_OK: done = TRUE;
+ new_address_record.result = ccache_accept;
+ break;
+ case FAIL: done = TRUE;
+ yield = FAIL;
+ *failure_ptr = US"recipient";
+ new_address_record.result = ccache_reject;
+ break;
+ default: break;
+ }
+ break;
+
+ case -1: /* MAIL response error */
+ *failure_ptr = US"mail";
+ if (errno == 0 && sx.buffer[0] == '5')
+ {
+ setflag(addr, af_verify_nsfail);
+ if (from_address[0] == 0)
+ new_domain_record.result = ccache_reject_mfnull;
+ }
+ break;
+ /* non-MAIL read i/o error */
+ /* non-MAIL response timeout */
+ /* internal error; channel still usable */
+ default: break; /* transmit failed */
+ }
+ }
+
+ addr->auth_sndr = client_authenticated_sender;
+
+ deliver_host = deliver_host_address = NULL;
+ deliver_domain = save_deliver_domain;
+
+ /* Do postmaster check if requested; if a full check is required, we
+ check for RCPT TO:<postmaster> (no domain) in accordance with RFC 821. */
+
+ if (done && pm_mailfrom)
+ {
+ /* Could possibly shift before main verify, just above, and be ok
+ for cutthrough. But no way to handle a subsequent rcpt, so just
+ refuse any */
+ cancel_cutthrough_connection("postmaster verify");
+ HDEBUG(D_acl|D_v) debug_printf_indent("Cutthrough cancelled by presence of postmaster verify\n");
+
+ done = smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0
+ && smtp_read_response(&sx.inblock, sx.buffer,
+ sizeof(sx.buffer), '2', callout);
+
+ if (done)
+ {
+ uschar * main_address = addr->address;
+
+ /*XXX oops, affixes */
+ addr->address = string_sprintf("postmaster@%.1000s", addr->domain);
+ addr->transport_return = PENDING_DEFER;
+
+ sx.from_addr = pm_mailfrom;
+ sx.first_addr = sx.sync_addr = addr;
+ sx.ok = FALSE;
+ sx.send_rset = TRUE;
+ sx.completed_addr = FALSE;
+
+ if( smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0
+ && addr->transport_return == PENDING_OK
+ )
+ done = TRUE;
+ else
+ done = (options & vopt_callout_fullpm) != 0
+ && smtp_write_command(&sx.outblock, FALSE,
+ "RCPT TO:<postmaster>\r\n") >= 0
+ && smtp_read_response(&sx.inblock, sx.buffer,
+ sizeof(sx.buffer), '2', callout);
+
+ /* Sort out the cache record */
+
+ new_domain_record.postmaster_stamp = time(NULL);
+
+ if (done)
+ new_domain_record.postmaster_result = ccache_accept;
+ else if (errno == 0 && sx.buffer[0] == '5')
+ {
+ *failure_ptr = US"postmaster";
+ setflag(addr, af_verify_pmfail);
+ new_domain_record.postmaster_result = ccache_reject;
+ }
+
+ addr->address = main_address;
+ }
+ }
+ /* For any failure of the main check, other than a negative response, we just
+ close the connection and carry on. We can identify a negative response by the
+ fact that errno is zero. For I/O errors it will be non-zero
+
+ Set up different error texts for logging and for sending back to the caller
+ as an SMTP response. Log in all cases, using a one-line format. For sender
+ callouts, give a full response to the caller, but for recipient callouts,
+ don't give the IP address because this may be an internal host whose identity
+ is not to be widely broadcast. */
+
+no_conn:
+ switch(errno)
+ {
+ case ETIMEDOUT:
+ HDEBUG(D_verify) debug_printf("SMTP timeout\n");
+ sx.send_quit = FALSE;
+ break;
+
+#ifdef SUPPORT_I18N
+ case ERRNO_UTF8_FWD:
+ {
+ extern int acl_where; /* src/acl.c */
+ errno = 0;
+ addr->message = string_sprintf(
+ "response to \"EHLO\" did not include SMTPUTF8");
+ addr->user_message = acl_where == ACL_WHERE_RCPT
+ ? US"533 no support for internationalised mailbox name"
+ : US"550 mailbox unavailable";
+ yield = FAIL;
+ done = TRUE;
+ }
+ break;
+#endif
+ case ECONNREFUSED:
+ sx.send_quit = FALSE;
+ break;
+
+ case 0:
+ if (*sx.buffer == 0) Ustrcpy(sx.buffer, US"connection dropped");
+
+ /*XXX test here is ugly; seem to have a split of responsibility for
+ building this message. Need to reationalise. Where is it done
+ before here, and when not?
+ Not == 5xx resp to MAIL on main-verify
+ */
+ if (!addr->message) addr->message =
+ string_sprintf("response to \"%s\" was: %s",
+ big_buffer, string_printing(sx.buffer));
+
+ addr->user_message = options & vopt_is_recipient
+ ? string_sprintf("Callout verification failed:\n%s", sx.buffer)
+ : string_sprintf("Called: %s\nSent: %s\nResponse: %s",
+ host->address, big_buffer, sx.buffer);
+
+ /* Hard rejection ends the process */
+
+ if (sx.buffer[0] == '5') /* Address rejected */
+ {
+ yield = FAIL;
+ done = TRUE;
+ }
+ break;
+ }
+
+ /* End the SMTP conversation and close the connection. */
+
+ /* Cutthrough - on a successful connect and recipient-verify with
+ use-sender and we are 1st rcpt and have no cutthrough conn so far
+ here is where we want to leave the conn open */
+ if ( cutthrough.delivery
+ && rcpt_count == 1
+ && done
+ && yield == OK
+ && (options & (vopt_callout_recipsender|vopt_callout_recippmaster|vopt_success_on_redirect))
+ == vopt_callout_recipsender
+ && !random_local_part
+ && !pm_mailfrom
+ && cutthrough.fd < 0
+ && !sx.lmtp
+ )
+ {
+ HDEBUG(D_acl|D_v) debug_printf_indent("holding verify callout open for cutthrough delivery\n");
+
+ cutthrough.fd = sx.outblock.sock; /* We assume no buffer in use in the outblock */
+ cutthrough.nrcpt = 1;
+ cutthrough.interface = interface;
+ cutthrough.host = *host;
+ cutthrough.addr = *addr; /* Save the address_item for later logging */
+ cutthrough.addr.next = NULL;
+ cutthrough.addr.host_used = &cutthrough.host;
+ if (addr->parent)
+ *(cutthrough.addr.parent = store_get(sizeof(address_item))) =
+ *addr->parent;
+ ctblock.buffer = ctbuffer;
+ ctblock.buffersize = sizeof(ctbuffer);
+ ctblock.ptr = ctbuffer;
+ /* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
+ ctblock.sock = cutthrough.fd;
+ }
+ else
+ {
+ /* Ensure no cutthrough on multiple address verifies */
+ if (options & vopt_callout_recipsender)
+ cancel_cutthrough_connection("not usable for cutthrough");
+ if (sx.send_quit)
+ {
+ (void) smtp_write_command(&sx.outblock, FALSE, "QUIT\r\n");
+
+ /* Wait a short time for response, and discard it */
+ smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
+ '2', 1);
+ }
+
+ if (sx.inblock.sock >= 0)
+ {
+#ifdef SUPPORT_TLS
+ tls_close(FALSE, TRUE);
+#endif
+ HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
+ (void)close(sx.inblock.sock);
+ sx.inblock.sock = sx.outblock.sock = -1;
+#ifndef DISABLE_EVENT
+ (void) event_raise(addr->transport->event_action, US"tcp:close", NULL);
+#endif
+ }
+ }
+
+ if (!done || yield != OK)
+ addr->message = string_sprintf("%s [%s] : %s", host->name, host->address,
+ addr->message);
+ } /* Loop through all hosts, while !done */
+ }
+
+/* If we get here with done == TRUE, a successful callout happened, and yield
+will be set OK or FAIL according to the response to the RCPT command.
+Otherwise, we looped through the hosts but couldn't complete the business.
+However, there may be domain-specific information to cache in both cases. */
+
+if (!(options & vopt_callout_no_cache))
+ cache_callout_write(&new_domain_record, addr->domain,
+ done, &new_address_record, address_key);
+
+/* Failure to connect to any host, or any response other than 2xx or 5xx is a
+temporary error. If there was only one host, and a response was received, leave
+it alone if supplying details. Otherwise, give a generic response. */
+
+if (!done)
+ {
+ uschar * dullmsg = string_sprintf("Could not complete %s verify callout",
+ options & vopt_is_recipient ? "recipient" : "sender");
+ yield = DEFER;
+
+ addr->message = host_list->next || !addr->message
+ ? dullmsg : string_sprintf("%s: %s", dullmsg, addr->message);
+
+ addr->user_message = smtp_return_error_details
+ ? string_sprintf("%s for <%s>.\n"
+ "The mail server(s) for the domain may be temporarily unreachable, or\n"
+ "they may be permanently unreachable from this server. In the latter case,\n%s",
+ dullmsg, addr->address,
+ options & vopt_is_recipient
+ ? "the address will never be accepted."
+ : "you need to change the address or create an MX record for its domain\n"
+ "if it is supposed to be generally accessible from the Internet.\n"
+ "Talk to your mail administrator for details.")
+ : dullmsg;
+
+ /* Force a specific error code */
+
+ addr->basic_errno = ERRNO_CALLOUTDEFER;
+ }
+
+/* Come here from within the cache-reading code on fast-track exit. */
+
+END_CALLOUT:
+tls_modify_variables(&tls_in);
+return yield;
+}
+
+
+
+/* Called after recipient-acl to get a cutthrough connection open when
+ one was requested and a recipient-verify wasn't subsequently done.
+*/
+int
+open_cutthrough_connection( address_item * addr )
+{
+address_item addr2;
+int rc;
+
+/* Use a recipient-verify-callout to set up the cutthrough connection. */
+/* We must use a copy of the address for verification, because it might
+get rewritten. */
+
+addr2 = *addr;
+HDEBUG(D_acl) debug_printf_indent("----------- %s cutthrough setup ------------\n",
+ rcpt_count > 1 ? "more" : "start");
+rc = verify_address(&addr2, NULL,
+ vopt_is_recipient | vopt_callout_recipsender | vopt_callout_no_cache,
+ CUTTHROUGH_CMD_TIMEOUT, -1, -1,
+ NULL, NULL, NULL);
+addr->message = addr2.message;
+addr->user_message = addr2.user_message;
+HDEBUG(D_acl) debug_printf_indent("----------- end cutthrough setup ------------\n");
+return rc;
+}
+
+
+
+/* Send given number of bytes from the buffer */
+static BOOL
+cutthrough_send(int n)
+{
+if(cutthrough.fd < 0)
+ return TRUE;
+
+if(
+#ifdef SUPPORT_TLS
+ (tls_out.active == cutthrough.fd) ? tls_write(FALSE, ctblock.buffer, n) :
+#endif
+ send(cutthrough.fd, ctblock.buffer, n, 0) > 0
+ )
+{
+ transport_count += n;
+ ctblock.ptr= ctblock.buffer;
+ return TRUE;
+}