- }
-
- /* Apply an ACL check if one is defined */
-
- if (acl_smtp_starttls != NULL)
- {
- rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls, &user_msg,
- &log_msg);
- if (rc != OK)
- {
- done = smtp_handle_acl_fail(ACL_WHERE_STARTTLS, rc, user_msg, log_msg);
- break;
- }
- }
-
- /* RFC 2487 is not clear on when this command may be sent, though it
- does state that all information previously obtained from the client
- must be discarded if a TLS session is started. It seems reasonble to
- do an implied RSET when STARTTLS is received. */
-
- incomplete_transaction_log(US"STARTTLS");
- smtp_reset(reset_point);
- toomany = FALSE;
- cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
-
- /* Attempt to start up a TLS session, and if successful, discard all
- knowledge that was obtained previously. At least, that's what the RFC says,
- and that's what happens by default. However, in order to work round YAEB,
- there is an option to remember the esmtp state. Sigh.
-
- We must allow for an extra EHLO command and an extra AUTH command after
- STARTTLS that don't add to the nonmail command count. */
-
- if ((rc = tls_server_start(tls_require_ciphers)) == OK)
- {
- if (!tls_remember_esmtp)
- helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE;
- cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
- cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE;
- if (sender_helo_name != NULL)
- {
- store_free(sender_helo_name);
- sender_helo_name = NULL;
- host_build_sender_fullhost(); /* Rebuild */
- set_process_info("handling incoming TLS connection from %s",
- host_and_ident(FALSE));
- }
- received_protocol = (esmtp?
- protocols[pextend + pcrpted +
- ((sender_host_authenticated != NULL)? pauthed : 0)]
- :
- protocols[pnormal + pcrpted])
- +
- ((sender_host_address != NULL)? pnlocal : 0);
-
- sender_host_authenticated = NULL;
- authenticated_id = NULL;
- sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
- DEBUG(D_tls) debug_printf("TLS active\n");
- break; /* Successful STARTTLS */
- }
-
- /* Some local configuration problem was discovered before actually trying
- to do a TLS handshake; give a temporary error. */
-
- else if (rc == DEFER)
- {
- smtp_printf("454 TLS currently unavailable\r\n");
- break;
- }
-
- /* Hard failure. Reject everything except QUIT or closed connection. One
- cause for failure is a nested STARTTLS, in which case tls_active remains
- set, but we must still reject all incoming commands. */
-
- DEBUG(D_tls) debug_printf("TLS failed to start\n");
- while (done <= 0)
- {
- switch(smtp_read_command(FALSE))
- {
- case EOF_CMD:
- log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF",
- smtp_get_connection_info());
- done = 2;
- break;
-
- case QUIT_CMD:
- smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
- log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
- smtp_get_connection_info());
- done = 2;
- break;
-
- default:
- smtp_printf("554 Security failure\r\n");
- break;
- }
- }
- tls_close(TRUE);
- break;
- #endif
-
-
- /* The ACL for QUIT is provided for gathering statistical information or
- similar; it does not affect the response code, but it can supply a custom
- message. */
-
- case QUIT_CMD:
- incomplete_transaction_log(US"QUIT");
-
- if (acl_smtp_quit != NULL)
- {
- rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit,&user_msg,&log_msg);
- if (rc == ERROR)
- log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
- log_msg);
- }
- else user_msg = NULL;
-
- if (user_msg == NULL)
- smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
- else
- smtp_printf("221 %s\r\n", user_msg);
-
- #ifdef SUPPORT_TLS
- tls_close(TRUE);
- #endif
-
- done = 2;
- log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
- smtp_get_connection_info());
- break;
-
-
- case RSET_CMD:
- incomplete_transaction_log(US"RSET");
- smtp_reset(reset_point);
- toomany = FALSE;
- smtp_printf("250 Reset OK\r\n");
- cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
- break;
-
-
- case NOOP_CMD:
- smtp_printf("250 OK\r\n");
- break;
-
-
- /* Show ETRN/EXPN/VRFY if there's
- an ACL for checking hosts; if actually used, a check will be done for
- permitted hosts. */
-
- case HELP_CMD:
- smtp_printf("214-Commands supported:\r\n");
- {
- uschar buffer[256];
- buffer[0] = 0;
- Ustrcat(buffer, " AUTH");
- #ifdef SUPPORT_TLS
- Ustrcat(buffer, " STARTTLS");
- #endif
- Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA");
- Ustrcat(buffer, " NOOP QUIT RSET HELP");
- if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
- if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
- if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
- smtp_printf("214%s\r\n", buffer);
- }
- break;
-
-
- case EOF_CMD:
- incomplete_transaction_log(US"connection lost");
- smtp_printf("421 %s lost input connection\r\n", smtp_active_hostname);
-
- /* Don't log by default unless in the middle of a message, as some mailers
- just drop the call rather than sending QUIT, and it clutters up the logs.
- */
-
- if (sender_address != NULL || recipients_count > 0)
- log_write(L_lost_incoming_connection,
- LOG_MAIN,
- "unexpected %s while reading SMTP command from %s%s",
- sender_host_unknown? "EOF" : "disconnection",
- host_and_ident(FALSE), smtp_read_error);
-
- else log_write(L_smtp_connection, LOG_MAIN, "%s lost%s",
- smtp_get_connection_info(), smtp_read_error);
-
- done = 1;
- break;
-
-
- case ETRN_CMD:
- if (sender_address != NULL)
- {
- done = synprot_error(L_smtp_protocol_error, 503, NULL,
- US"ETRN is not permitted inside a transaction");
- break;
- }
-
- log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_command_argument,
- host_and_ident(FALSE));
-
- rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn, &user_msg, &log_msg);
- if (rc != OK)
- {
- done = smtp_handle_acl_fail(ACL_WHERE_ETRN, rc, user_msg, log_msg);
- break;
- }
-
- /* Compute the serialization key for this command. */
-
- etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_command_argument);
-
- /* If a command has been specified for running as a result of ETRN, we
- permit any argument to ETRN. If not, only the # standard form is permitted,
- since that is strictly the only kind of ETRN that can be implemented
- according to the RFC. */
-
- if (smtp_etrn_command != NULL)
- {
- uschar *error;
- BOOL rc;
- etrn_command = smtp_etrn_command;
- deliver_domain = smtp_command_argument;
- rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL,
- US"ETRN processing", &error);
- deliver_domain = NULL;
- if (!rc)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
- error);
- smtp_printf("458 Internal failure\r\n");
- break;
- }
- }
-
- /* Else set up to call Exim with the -R option. */
-
- else
- {
- if (*smtp_command_argument++ != '#')
- {
- done = synprot_error(L_smtp_syntax_error, 501, NULL,
- US"argument must begin with #");
- break;
- }
- etrn_command = US"exim -R";
- argv = child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE, 2, US"-R",
- smtp_command_argument);
- }
-
- /* If we are host-testing, don't actually do anything. */
-
- if (host_checking)
- {
- HDEBUG(D_any)
- {
- debug_printf("ETRN command is: %s\n", etrn_command);
- debug_printf("ETRN command execution skipped\n");
- }
- smtp_printf("250 OK\r\n");
- break;
- }
-
-
- /* If ETRN queue runs are to be serialized, check the database to
- ensure one isn't already running. */
-
- if (smtp_etrn_serialize && !enq_start(etrn_serialize_key))
- {
- smtp_printf("458 Already processing %s\r\n", smtp_command_argument);
- break;
- }
-
- /* Fork a child process and run the command. We don't want to have to
- wait for the process at any point, so set SIGCHLD to SIG_IGN before
- forking. It should be set that way anyway for external incoming SMTP,
- but we save and restore to be tidy. If serialization is required, we
- actually run the command in yet another process, so we can wait for it
- to complete and then remove the serialization lock. */
-
- oldsignal = signal(SIGCHLD, SIG_IGN);
-
- if ((pid = fork()) == 0)
- {
- smtp_input = FALSE; /* This process is not associated with the */
- (void)fclose(smtp_in); /* SMTP call any more. */
- (void)fclose(smtp_out);
-
- signal(SIGCHLD, SIG_DFL); /* Want to catch child */
-
- /* If not serializing, do the exec right away. Otherwise, fork down
- into another process. */
-
- if (!smtp_etrn_serialize || (pid = fork()) == 0)
- {
- DEBUG(D_exec) debug_print_argv(argv);
- exim_nullstd(); /* Ensure std{in,out,err} exist */
- execv(CS argv[0], (char *const *)argv);
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "exec of \"%s\" (ETRN) failed: %s",
- etrn_command, strerror(errno));
- _exit(EXIT_FAILURE); /* paranoia */
- }
-
- /* Obey this if smtp_serialize and the 2nd fork yielded non-zero. That
- is, we are in the first subprocess, after forking again. All we can do
- for a failing fork is to log it. Otherwise, wait for the 2nd process to
- complete, before removing the serialization. */
-
- if (pid < 0)
- log_write(0, LOG_MAIN|LOG_PANIC, "2nd fork for serialized ETRN "
- "failed: %s", strerror(errno));
- else
- {
- int status;
- DEBUG(D_any) debug_printf("waiting for serialized ETRN process %d\n",
- (int)pid);
- (void)wait(&status);
- DEBUG(D_any) debug_printf("serialized ETRN process %d ended\n",
- (int)pid);
- }
-
- enq_end(etrn_serialize_key);
- _exit(EXIT_SUCCESS);
- }
-
- /* Back in the top level SMTP process. Check that we started a subprocess
- and restore the signal state. */
-
- if (pid < 0)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
- strerror(errno));
- smtp_printf("458 Unable to fork process\r\n");
- if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
- }
- else smtp_printf("250 OK\r\n");
-
- signal(SIGCHLD, oldsignal);
- break;
-
-
- case BADARG_CMD:
- done = synprot_error(L_smtp_syntax_error, 501, NULL,
- US"unexpected argument data");
- break;
-
-
- /* This currently happens only for NULLs, but could be extended. */
-
- case BADCHAR_CMD:
- done = synprot_error(L_smtp_syntax_error, 0, NULL, /* Just logs */
- US"NULL character(s) present (shown as '?')");
- smtp_printf("501 NULL characters are not allowed in SMTP commands\r\n");
- break;
-
-
- case BADSYN_CMD:
- if (smtp_inend >= smtp_inbuffer + in_buffer_size)
- smtp_inend = smtp_inbuffer + in_buffer_size - 1;
- c = smtp_inend - smtp_inptr;
- if (c > 150) c = 150;
- smtp_inptr[c] = 0;
- incomplete_transaction_log(US"sync failure");
- log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
- "(next input sent too soon: pipelining was%s advertised): "
- "rejected \"%s\" %s next input=\"%s\"",
- pipelining_advertised? "" : " not",
- cmd_buffer, host_and_ident(TRUE),
- string_printing(smtp_inptr));
- smtp_printf("554 SMTP synchronization error\r\n");
- done = 1; /* Pretend eof - drops connection */
- break;
-
-
- case TOO_MANY_NONMAIL_CMD:
- incomplete_transaction_log(US"too many non-mail commands");
- log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
- "nonmail commands (last was \"%.*s\")", host_and_ident(FALSE),
- smtp_command_argument - cmd_buffer, cmd_buffer);
- smtp_printf("554 Too many nonmail commands\r\n");
- done = 1; /* Pretend eof - drops connection */
- break;
-
-
- default:
- if (unknown_command_count++ >= smtp_max_unknown_commands)
- {
- log_write(L_smtp_syntax_error, LOG_MAIN,
- "SMTP syntax error in \"%s\" %s %s",
- string_printing(cmd_buffer), host_and_ident(TRUE),
- US"unrecognized command");
- incomplete_transaction_log(US"unrecognized command");
- smtp_printf("500 Too many unrecognized commands\r\n");
- done = 2;
- log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
- "unrecognized commands (last was \"%s\")", host_and_ident(FALSE),
- cmd_buffer);
- }
- else
- done = synprot_error(L_smtp_syntax_error, 500, NULL,
- US"unrecognized command");
- break;