+ /* 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 rationalise. 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. Ditto for a lazy-close
+ verify. */
+
+ if ( (cutthrough.delivery || options & vopt_callout_hold)
+ && 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 %s\n",
+ cutthrough.delivery
+ ? "cutthrough delivery" : "potential further verifies and delivery");
+
+ cutthrough.callout_hold_only = !cutthrough.delivery;
+ cutthrough.is_tls = tls_out.active >= 0;
+ cutthrough.fd = sx.outblock.sock; /* We assume no buffer in use in the outblock */
+ cutthrough.nrcpt = 1;
+ cutthrough.transport = addr->transport->name;
+ cutthrough.interface = interface;
+ cutthrough.snd_port = sending_port;
+ cutthrough.peer_options = smtp_peer_options;
+ cutthrough.host = *host;
+ {
+ int oldpool = store_pool;
+ store_pool = POOL_PERM;
+ cutthrough.snd_ip = string_copy(sending_ip_address);
+ cutthrough.host.name = string_copy(host->name);
+ cutthrough.host.address = string_copy(host->address);
+ store_pool = oldpool;
+ }
+ 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 verifies that were incompatible */
+ if (options & vopt_callout_recipsender)
+ cancel_cutthrough_connection(TRUE, US"not usable for cutthrough");
+ if (sx.send_quit)
+ {
+ (void) smtp_write_command(&sx.outblock, SCMD_FLUSH, "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, TLS_SHUTDOWN_NOWAIT);
+#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. */