/* Copyright 2005 The Apache Software Foundation or its licensors, as
 * applicable.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define CORE_PRIVATE
#include "apr_strings.h"
#include "apr_pools.h"
#include "apr_lib.h"
#include "apr_buckets.h"
#include "apr_errno.h"
#include "apr_tables.h"
#include "httpd.h"
#include "http_protocol.h"
#include "http_config.h"
#include "http_connection.h"
#include "http_core.h"
#include "http_request.h"
#include "http_log.h"
#include "ap_config.h"
#include "ap_mmn.h"
#include "scoreboard.h"

#include "apreq2/apreq_parser.h"

#include "smtp.h"
#include "mod_smtpd.h"
#include "smtp_filters.h"

extern module AP_MODULE_DECLARE_DATA smtpd_module;
extern ap_filter_rec_t *smtpd_data_input_filter_handle;
extern ap_filter_rec_t *smtpd_header_input_filter_handle;
extern ap_regex_t *ehlo_compiled_regex;
extern ap_regex_t *mail_compiled_regex;
extern ap_regex_t *rcpt_compiled_regex;
extern ap_regex_t *vrfy_compiled_regex;

static smtpd_bool smtpd_handler_helo(smtpd_session_rec *, char *, smtpd_bool);
static smtpd_bool smtpd_handler_mail(smtpd_session_rec *, char *);
static smtpd_bool smtpd_handler_rcpt(smtpd_session_rec *, char *);
static smtpd_bool smtpd_handler_data(smtpd_session_rec *, char *);
static smtpd_bool smtpd_handler_rset(smtpd_session_rec *, char *);
static smtpd_bool smtpd_handler_noop(smtpd_session_rec *, char *);
static smtpd_bool smtpd_handler_quit(smtpd_session_rec *, char *);
static smtpd_bool smtpd_handler_vrfy(smtpd_session_rec *, char *);
static smtpd_bool smtpd_handle_unrecognized_command(smtpd_session_rec *,
                                                    char *, char *);

static void smtpd_respond_msgs(smtpd_session_rec *scr, int status,
                               char *default_string, apr_array_header_t *msgs)
{
    if (msgs != NULL && apr_is_empty_array(msgs) == 0) {
        smtpd_respond_multiline(scr, status, msgs);
    }
    else {
        smtpd_respond_oneline(scr, status, default_string);
    }
}

static void smtpd_respond(smtpd_session_rec *scr, int status,
                          char *default_string, smtpd_return_data *hook_data)
{
    if (hook_data != NULL) {
        smtpd_respond_msgs(scr, status, default_string, hook_data->msgs);
    }
    else {
        smtpd_respond_oneline(scr, status, default_string);
    }
}

#define BUFFER_STR_LEN 1024
void smtpd_process_connection_internal(smtpd_session_rec *scr)
{
    char cmdbuff[BUFFER_STR_LEN];
    char *buffer = cmdbuff;
    char *command;
    smtpd_bool disconnect;
    smtpd_return_data *out_data;

    smtpd_run_on_connect(scr);

    switch(smtpd_run_connect(scr, &out_data)) {
    case SMTPD_DONE:
        break;
    case SMTPD_DENY:
        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, scr->s,
                     "Connection Denied");
        smtpd_respond(scr, 554, "Connection from you denied",
                      out_data);
        scr->should_disconnect = SMTPD_TRUE;
        smtpd_disallow_all_commands(scr);
        smtpd_allow_command(scr, SMTPD_COMMAND_QUIT);
        break;
    default:
        snprintf(buffer, 255, "%s mod_smtpd", scr->s->server_hostname);
        smtpd_respond_msgs(scr, 220, buffer, scr->connect_greeting);
        break;
    }

    while (smtpd_getline(scr, buffer, BUFFER_STR_LEN, NULL) == APR_SUCCESS) {
        apr_pool_clear(scr->per_command_pool);
        command = buffer;
        
        buffer = ap_strchr(buffer, ' ');
        if (buffer != NULL) {
            *buffer = '\0';
            buffer++;
        }

        ap_str_tolower(command);

        if (ap_strcmp_match("ehlo", command) == 0) {
            disconnect = smtpd_handler_helo(scr, buffer, SMTPD_TRUE);
        }
        else if (ap_strcmp_match("helo", command) == 0) {
            disconnect = smtpd_handler_helo(scr, buffer, SMTPD_FALSE);
        }
        else if (ap_strcmp_match("mail", command) == 0) {
            disconnect = smtpd_handler_mail(scr, buffer);
        }
        else if (ap_strcmp_match("rcpt", command) == 0) {
            disconnect = smtpd_handler_rcpt(scr, buffer);
        }
        else if (ap_strcmp_match("data", command) == 0) {
            disconnect = smtpd_handler_data(scr, buffer);
        }
        else if (ap_strcmp_match("rset", command) == 0) {
            disconnect = smtpd_handler_rset(scr, buffer);
        }
        else if (ap_strcmp_match("noop", command) == 0) {
            disconnect = smtpd_handler_noop(scr, buffer);
        }
        else if (ap_strcmp_match("vrfy", command) == 0) {
            disconnect = smtpd_handler_vrfy(scr, buffer);
        }
        else if (ap_strcmp_match("quit", command) == 0) {
            disconnect = smtpd_handler_quit(scr, buffer);
        }
        else {
            disconnect =
              smtpd_handle_unrecognized_command(scr, command, buffer);
        }
        
        if (disconnect == SMTPD_TRUE)
            break;

        buffer = cmdbuff;
    }

    return;
}

static smtpd_bool smtpd_handle_unrecognized_command(smtpd_session_rec *scr,
                                              char *command,
                                              char *data)
{
    smtpd_return_data *out_data;
    
    switch(smtpd_run_unrecognized_command(scr, command, data, &out_data)) {
    case SMTPD_DONE:
        break;
    case SMTPD_DENY:
        smtpd_respond(scr, 521, "Command Denied", out_data);
        break;
    case SMTPD_DENYSOFT:
        smtpd_respond(scr, 421, "Command Denied Temporarily", out_data);
        break;
    default:
        smtpd_respond_oneline(scr, 500, "Syntax error, command unrecognized");
        break;
    }

    return SMTPD_FALSE;
}

static void smtpd_default_ehlo(smtpd_session_rec *scr, char *buffer)
{
    /* RFC 2821 states that when ehlo or helo is received, reset */
    /* state */
    smtpd_reset_envelope(scr);
    
    strncpy(scr->helo, buffer, 255);
    scr->helo[255] = '\0';
    scr->protocol_type = SMTPD_PROTOCOL_ESMTP;
    
    if (scr->extensions->nelts) {
        apr_array_header_t *first;
        
        first = apr_array_make(scr->per_command_pool, 1, sizeof(char *));
        (*((char **)apr_array_push(first))) = scr->helo;
        apr_array_cat(first, scr->extensions);
        
        smtpd_respond_multiline(scr, 250, first);
    }
    else {
        smtpd_respond_oneline(scr, 250, scr->helo);
    }

}

static void smtpd_default_helo(smtpd_session_rec *scr, char *buffer)
{
    /* RFC 2821 states that when ehlo or helo is received, reset */
    /* state */
    smtpd_reset_envelope(scr);
    
    strncpy(scr->helo, buffer, 255);
    scr->helo[255] = '\0';
    scr->protocol_type = SMTPD_PROTOCOL_SMTP;

    smtpd_respond_oneline(scr, 250, scr->helo);
}


static smtpd_bool smtpd_handler_helo(smtpd_session_rec *scr, char *buffer,
                                     smtpd_bool esmtp)
{
    int error;
    smtpd_return_data *out_data = NULL;
    smtpd_bool disconnect = SMTPD_FALSE;

    if ((esmtp == SMTPD_TRUE &&
         smtpd_check_command(scr, SMTPD_COMMAND_EHLO) == FALSE) ||
        (esmtp == SMTPD_FALSE &&
         smtpd_check_command(scr, SMTPD_COMMAND_HELO) == FALSE)) {
        smtpd_respond_oneline(scr, 503, "Command not allowed");
        return disconnect;
    }

    if (buffer == NULL) {
        goto syntax_error;
    }

    error = ap_regexec(ehlo_compiled_regex, buffer, 0, NULL, 0);

    if (error) {
    syntax_error:
        smtpd_respond_oneline(scr, 501, esmtp == SMTPD_TRUE
                              ? "Syntax: EHLO hostname"
                              : "Syntax: HELO hostname");
        return disconnect;
    }
    
    switch(esmtp == SMTPD_TRUE
           ? smtpd_run_ehlo(scr, buffer, &out_data)
           : smtpd_run_helo(scr, buffer, &out_data)) {
    case SMTPD_DONE:
        break;
    case SMTPD_DISCONNECT:
        disconnect = SMTPD_TRUE;
        smtpd_respond(scr, 421, "Error: Internal", out_data);
        break;
    case SMTPD_DENY:
        smtpd_respond(scr, 550, "Denied", out_data);
        break;
    case SMTPD_DENYSOFT:
        smtpd_respond(scr, 450, "Temporarily Denied", out_data);
        break;
    default:
        if (esmtp == SMTPD_TRUE) {
            smtpd_default_ehlo(scr, buffer);
        }
        else {
            smtpd_default_helo(scr, buffer);
        }
        break;
    }

    return disconnect;
}

static void parse_out_parameters(apr_table_t *mail_parameters,
                                 char *start_parameters)
{
    char *end_para, *end_key;

    while ((start_parameters != '\0')&&
           (start_parameters != NULL)) {
        
        end_para = ap_strchr(start_parameters, ' ');
        if (end_para != NULL) {
            end_para[0] = '\0';
            end_para++;
        }            

        end_key = ap_strchr(start_parameters, '=');
        if (end_key == NULL) {
            apr_table_set(mail_parameters, start_parameters, NULL);
        } else {
            end_key[0] = '\0';
            end_key++;
            apr_table_set(mail_parameters, start_parameters, end_key);
        }
        start_parameters = end_para;
    }
}

static void smtpd_default_mail(smtpd_session_rec *scr, char *email_address,
                              apr_table_t *mail_parameters)
{
    smtpd_envelope_rec *str = scr->envelope;

    str->mail_from = apr_pstrdup(str->p, email_address);
    /* REVIEW: Should we not create the str->mail_parameters at reset time
       and just create it now with:
       str->mail_parameters = apr_table_copy(str->p, mail_parameters);
       or keep how it's done below?
    */
    mail_parameters = apr_table_copy(str->p, mail_parameters);
    apr_table_overlap(str->mail_parameters, mail_parameters,
                      APR_OVERLAP_TABLES_SET);
    str->trans_state = SMTPD_STATE_GOT_MAIL;
    smtpd_respond_oneline(scr, 250, "Ok");
}   

static smtpd_bool smtpd_handler_mail(smtpd_session_rec *scr, char *buffer)
{
    char *email_address;
    int error;
    apr_table_t *mail_parameters;
    smtpd_return_data *out_data = NULL;
    smtpd_bool disconnect = SMTPD_FALSE;

    if (smtpd_check_command(scr, SMTPD_COMMAND_MAIL) == SMTPD_FALSE) {
        smtpd_respond_oneline(scr, 503, "Command not allowed");
        return disconnect;
    }

    /* need helo before we can start a transaction */
    if (scr->protocol_type == SMTPD_PROTOCOL_UNKNOWN) {
        smtpd_respond_oneline(scr, 503, "Error: Need EHLO/HELO "
                              "before a transaction");
        return disconnect;
    }

    /* already got mail */
    if (scr->envelope->trans_state == SMTPD_STATE_GOT_MAIL) {
        smtpd_respond_oneline(scr, 503, "Error: Nested MAIL command");
        return disconnect;
    }


    if (buffer == NULL) {
        goto syntax_error;
    }

    error = ap_regexec(mail_compiled_regex, buffer, 0, NULL, 0);

    if (error) {
    syntax_error:
        smtpd_respond_oneline(scr, 501, "Syntax: MAIL FROM:<reverse-path> "
                              "mail-parameters");
        return SMTPD_FALSE;
    }

    /* logging will be robustified later 
       ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, scr->s,
       "full from_parameter: %s", loc);  
    */

    mail_parameters = apr_table_make(scr->per_command_pool, 5);

    /* parse out reverse-path */
    {
        char *start_parameters;

        email_address = buffer + sizeof("from:<") - 1;
        start_parameters = ap_strchr(email_address, '>');
        start_parameters[0] = '\0';
        
        if (start_parameters[1] != '\0') {
            parse_out_parameters(mail_parameters, start_parameters+2);
        }
    }

    switch(smtpd_run_mail(scr, email_address, mail_parameters, &out_data)) {
    case SMTPD_DONE:
        break;
    case SMTPD_DISCONNECT:
        disconnect = SMTPD_TRUE;
        smtpd_respond(scr, 421, "Error: Internal", out_data);
        break;
    case SMTPD_DENY:
        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, scr->s,
                     "deny mail from %s (%s)", email_address,
                     out_data->msgs ? ((char **)out_data->msgs->elts)[0] : "");
        smtpd_respond(scr, 550, "Denied", out_data);
        break;
    case SMTPD_DENYSOFT:
        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, scr->s,
                     "denysoft mail from %s (%s)", email_address,
                     out_data->msgs ? ((char **)out_data->msgs->elts)[0] : "");
        smtpd_respond(scr, 450, "Temporarily denied", out_data);
        break;
    default:
        smtpd_default_mail(scr, email_address, mail_parameters);
        break;
    }
    
    return disconnect;
}

static void smtpd_default_rcpt(smtpd_session_rec *scr, char *email_address,
                              apr_table_t *rcpt_parameters)
{
    smtpd_envelope_rec *str = scr->envelope;

    /* add a recipient */
    rcpt_parameters = apr_table_copy(str->p, rcpt_parameters);
    apr_table_overlap(str->rcpt_parameters, rcpt_parameters,
                      APR_OVERLAP_TABLES_SET);
    apr_table_set(str->rcpt_to, email_address, NULL);
    str->trans_state = SMTPD_STATE_GOT_RCPT;
    smtpd_respond_oneline(scr, 250, "Ok");
}

static smtpd_bool smtpd_handler_rcpt(smtpd_session_rec *scr, char *buffer)
{
    char *email_address;
    int error;
    apr_table_t *rcpt_parameters;
    smtpd_return_data *out_data = NULL;
    smtpd_bool disconnect = SMTPD_FALSE;
        
    if (smtpd_check_command(scr, SMTPD_COMMAND_RCPT) == SMTPD_FALSE) {
        smtpd_respond_oneline(scr, 503, "Command not allowed");
        return disconnect;
    }

    /* need mail first */
    if ((scr->envelope->trans_state != SMTPD_STATE_GOT_MAIL) &&
        (scr->envelope->trans_state != SMTPD_STATE_GOT_RCPT)) {
        smtpd_respond_oneline(scr, 503, "Error: need MAIL command");
        return disconnect;
    }

    if (buffer == NULL) {
        goto syntax_error;
    }

    error = ap_regexec(rcpt_compiled_regex, buffer, 0, NULL, 0);

    if (error) {
    syntax_error:
        smtpd_respond_oneline(scr, 501, "Syntax: RCPT TO:<forward-path> "
                              "rcpt-parameters");
        return disconnect;
    }

    rcpt_parameters = apr_table_make(scr->per_command_pool, 5);

    /* parse out reverse-path*/
    {
        char *start_parameters;

        email_address = buffer + sizeof("to:<") - 1;
        start_parameters = ap_strchr(email_address, '>');
        start_parameters[0] = '\0';

        if (start_parameters[1] != '\0') {
            parse_out_parameters(rcpt_parameters, start_parameters+2);
        }
    }

    switch(smtpd_run_rcpt(scr, email_address, rcpt_parameters, &out_data)) {
    case SMTPD_DONE:
        break;
    case SMTPD_DISCONNECT:
        disconnect = SMTPD_TRUE;
        smtpd_respond(scr, 421, "Error: Internal", out_data);
        break;
    case SMTPD_DENY:
        smtpd_respond(scr, 550, "Relaying denied", out_data);
        break;
    case SMTPD_DENYSOFT:
        smtpd_respond(scr, 450, "Relaying temporarily denied", out_data);
        break;
    case SMTPD_OK: /* recipient is okay */
        smtpd_default_rcpt(scr, email_address, rcpt_parameters);
        break;
    default:
        smtpd_respond_oneline(scr, 450,
                              "No plugin decided if relaying is allowed");
        break;
    }
    
    return disconnect;
}

SMTPD_DECLARE_NONSTD(int) smtpd_count_unqueued(void *rec, const char *key,
                                               const char *val) {
    int *amount = (int *) rec;

    if (val == NULL) {
        *amount += 1;
    }
    
    return !0;
}

static void smtpd_queue(smtpd_session_rec *scr)
{
    int i, amount_unqueued = 0;
    const apr_array_header_t *rcpts = apr_table_elts(scr->envelope->rcpt_to);

    smtpd_run_on_queue(scr);

    apr_table_do(smtpd_count_unqueued, &amount_unqueued,
                 scr->envelope->rcpt_to, NULL);

    /* we don't bounce any messages if all of them
       were unqueued, we just report failure to deliver */
    if (amount_unqueued == rcpts->nelts) {
        smtpd_respond_oneline(scr, 550, "Message could not be delivered");
    } else {
        if (amount_unqueued > 0) {
            ap_log_error(APLOG_MARK, APLOG_INFO, 0, scr->s,
                         "Not all recipients were queued in message "
                         "from %s.", scr->envelope->mail_from);
            smtpd_bounce_unqueued(scr);
        }

        smtpd_respond_msgs(scr, 220, "Queued", scr->queued_greeting);
    }
}

static void smtpd_add_received_header(smtpd_session_rec *scr)
{
    const char *remote_host;
    char time_string[80];
    char *received_from;
    char *protocol =
      scr->protocol_type == SMTPD_PROTOCOL_ESMTP ? "ESMTP" : "SMTP";
    char *heloed;
    apr_time_exp_t t;
    apr_size_t len;
    apr_pool_t *p;
    smtpd_envelope_rec *str = scr->envelope;
    smtpd_svr_config_rec *pConfig =
      ap_get_module_config(scr->s->module_config,
                           &smtpd_module);

    /* XXX: For now we mandatorily do a DNS lookup,
       make it so we can do this according to the 
       config rule "HostnameLookups" */
    remote_host = ap_get_remote_host(scr->c, NULL,
                                     REMOTE_DOUBLE_REV, NULL);
    
    apr_pool_create(&p, scr->p);

    heloed = 
      scr->protocol_type == SMTPD_PROTOCOL_UNKNOWN ?
      "" : apr_psprintf(p, "(HELO %s) ", scr->helo);

    apr_time_exp_lt(&t, apr_time_now());

    apr_strftime(time_string, &len, sizeof(time_string),
                 "%a, %d %b %Y %H:%M:%S %z (%Z)", &t);

    received_from = apr_psprintf(p, "from %s %s"
                                 "(%s)\r\n "
                                 "by %s (mod_smtpd/%s) with %s; %s",
                                 remote_host, heloed,
                                 scr->c->remote_ip,
                                 pConfig->sId, "0.9", protocol,
                                 time_string);

    apr_table_add(str->headers, "Received",
                  received_from);

    apr_pool_destroy(p);
}

static smtpd_bool smtpd_default_data(smtpd_session_rec *scr)
{
    int rv;
    apr_file_t *tfp;
    smtpd_envelope_rec *str = scr->envelope;
    smtpd_header_filter_ctx header_ctx = {0, scr, NULL, NULL};
    smtpd_bool disconnect = SMTPD_FALSE;
   
    /* get temporary file pointer */
    {
        const char *tempdir;
        char *tempfile;

        rv = apr_temp_dir_get(&tempdir, scr->per_command_pool);
        if (rv) {
            smtpd_respond_oneline(scr, 421, "Error: Internal");
            return SMTPD_TRUE;
        }

        tempfile = apr_psprintf(scr->per_command_pool, "%s/tmp.XXXXXX",
                                tempdir);
        rv = apr_file_mktemp(&tfp, tempfile,
                             APR_CREATE | APR_WRITE | APR_READ |
                             APR_DELONCLOSE, str->p);
    
        if (rv != APR_SUCCESS) {
            smtpd_respond_oneline(scr, 421, "Error: Internal");
            /* file error close connection */
            return SMTPD_TRUE;
        }
    }

    str->tfp = tfp;
    smtpd_respond_oneline(scr, 354, "End data with <CR><LF>.<CR><LF>");

    /* add filter that translates ".\r\n" into EOS */
    smtpd_add_input_filter_handle(smtpd_data_input_filter_handle, 0, scr);

    /* add filter that parses smtp headers */
    {
        header_ctx.rfc822_parser = apreq_parser_make(str->p,
                                                     scr->c->bucket_alloc,
                                                     "text/rfc822",
                                                     apreq_parse_headers,
                                                     1000, NULL, NULL, NULL);
        header_ctx.bb = apr_brigade_create(str->p, scr->c->bucket_alloc);
        smtpd_add_input_filter_handle(smtpd_header_input_filter_handle,
                                      &header_ctx, scr);
    }

    /* read data: wait until there is no more data
       or until we have too much data */

    {
        const char *string;
        apr_bucket_brigade *bb =
          apr_brigade_create(str->p, scr->c->bucket_alloc);
        apr_size_t len, total_data = 0;
        apr_bucket *e;
        smtpd_svr_config_rec *pConfig =
          ap_get_module_config(scr->s->module_config,
                               &smtpd_module);

        while (total_data < pConfig->max_data) {
            rv = ap_get_brigade(scr->envelope->input_filters, bb,
                                AP_MODE_GETLINE, APR_BLOCK_READ, 0);
        
            if (rv != APR_SUCCESS) {
                goto done_spooling;
            }

            for (e = APR_BRIGADE_FIRST(bb);
                 ((e != APR_BRIGADE_SENTINEL(bb)) &&
                  (total_data < pConfig->max_data));
                 e = APR_BUCKET_NEXT(e)) {

                if (APR_BUCKET_IS_EOS(e)) {
                    goto done_spooling;
                }

                rv = apr_bucket_read(e, &string, &len, APR_BLOCK_READ);
                if (rv != APR_SUCCESS) {
                    len = 0;
                    break;
                }
            
                if (!len)
                    continue;

                apr_file_write(tfp, string, &len);

                total_data += len;
            }
        }
    }

 done_spooling:
    apr_file_flush(tfp);

    smtpd_add_received_header(scr);

    /* run data_post hook */
    {
        smtpd_return_data *out_data = NULL;

        switch(smtpd_run_data_post(scr, &out_data)) {
        case SMTPD_DONE:
            break;
        case SMTPD_DISCONNECT:
            disconnect = SMTPD_TRUE;
            smtpd_respond(scr, 421, "Error: Internal", out_data);
            break;
        case SMTPD_DENY:
            smtpd_respond(scr, 552, "Message denied", out_data);
            smtpd_reset_envelope(scr);
            break;
        case SMTPD_DENYSOFT:
            smtpd_respond(scr, 452, "Message denied temporarily", out_data);
            smtpd_reset_envelope(scr);
            break;
        default:
            smtpd_queue(scr);
            smtpd_reset_envelope(scr);
            break;
        }
    }
    
    apr_file_close(tfp);
    return disconnect;
}

static smtpd_bool smtpd_handler_data(smtpd_session_rec *scr, char *buffer)
{
    smtpd_bool disconnect = SMTPD_FALSE;
    smtpd_return_data *out_data = NULL;

    if (smtpd_check_command(scr, SMTPD_COMMAND_DATA) == SMTPD_FALSE) {
        smtpd_respond_oneline(scr, 503, "Command not allowed");
        return disconnect;
    }

    /* qpsmtpd does this after calling smtpd_run_data()
       but that doesn't make sense with the rest of this code
       so we do this check here */
    if (scr->envelope->trans_state != SMTPD_STATE_GOT_RCPT) {
        smtpd_respond_oneline(scr, 503, "Error: need RCPT command");
        return disconnect;
    }

    if (buffer != NULL) {
        smtpd_respond_oneline(scr, 501, "Syntax: DATA");
        return disconnect;
    }

    switch(smtpd_run_data(scr, &out_data)) {
    case SMTPD_DONE:
        break;
    case SMTPD_DISCONNECT:
        disconnect = SMTPD_TRUE;
        smtpd_respond(scr, 421, "Error: Internal", out_data);
        break;
    case SMTPD_DENY:
        /* REVIEW: should we reset state here? */
        /* smtpd_clear_request_rec(sr); */
        smtpd_respond(scr, 554, "Message denied", out_data);
        break;
    case SMTPD_DENYSOFT:
        /* REVIEW: should we reset state here? */
        /* smtpd_clear_request_rec(sr); */
        smtpd_respond(scr, 451, "Message denied temporarily", out_data);
        break;
    default:
        disconnect = smtpd_default_data(scr);
        break;
    }

    return disconnect;
}

static smtpd_bool smtpd_handler_rset(smtpd_session_rec *scr, char *buffer)
{
    if (smtpd_check_command(scr, SMTPD_COMMAND_RSET) == SMTPD_FALSE) {
        smtpd_respond_oneline(scr, 503, "Command not allowed");
    }
    else if (buffer != NULL) {
        smtpd_respond_oneline(scr, 501, "Syntax: RSET");
    }
    else {
        smtpd_reset_envelope(scr);
        smtpd_respond_oneline(scr, 250, "Ok");
    }
    
    return SMTPD_FALSE;
}

static smtpd_bool smtpd_handler_noop(smtpd_session_rec *scr, char *buffer)
{

    if (smtpd_check_command(scr, SMTPD_COMMAND_NOOP) == SMTPD_FALSE) {
        smtpd_respond_oneline(scr, 503, "Command not allowed");
    }
    else {
        smtpd_respond_oneline(scr, 250, "Ok");
    }
    
    return SMTPD_FALSE;
}

static smtpd_bool smtpd_handler_quit(smtpd_session_rec *scr, char *buffer)
{
    smtpd_bool disconnect = SMTPD_FALSE;

    if (smtpd_check_command(scr, SMTPD_COMMAND_QUIT) == SMTPD_FALSE) {
        smtpd_respond_oneline(scr, 503, "Command not allowed");
        return disconnect;
    }
    else {
        smtpd_run_on_quit(scr);
        smtpd_respond_msgs(scr, 221, "Bye", scr->quit_greeting);
        disconnect = SMTPD_TRUE;
    }

    return disconnect;
}

static smtpd_bool smtpd_handler_vrfy(smtpd_session_rec *scr, char *buffer)
{
    int error;
    smtpd_return_data *out_data = NULL;
    smtpd_bool disconnect = SMTPD_FALSE;

    if (smtpd_check_command(scr, SMTPD_COMMAND_VRFY) == SMTPD_FALSE) {
        smtpd_respond_oneline(scr, 503, "Command not allowed");
        return disconnect;
    }

    if (buffer == NULL) {
        goto syntax_error;
    }

    error = ap_regexec(vrfy_compiled_regex, buffer, 0, NULL, 0);

    if (error) {
    syntax_error:
        smtpd_respond_oneline(scr, 501, "Syntax: VRFY String");
        return disconnect;
    }

    switch(smtpd_run_vrfy(scr, buffer, &out_data)) {
    case SMTPD_DONE:
        break;
    case SMTPD_DISCONNECT:
        disconnect = SMTPD_TRUE;
        smtpd_respond(scr, 421, "Error: Internal", out_data);
        break;
    case SMTPD_DENY:
        smtpd_respond(scr, 554, "Address denied", out_data);
        break;
    case SMTPD_OK: /* user is okay */
        smtpd_respond(scr, 250, "Address okay", out_data);
        break;
    default:
        smtpd_respond_oneline(scr, 252,
                                  "Address seems fine, "
                                  "but we might not accept it.");
        break;
    }
    
    return disconnect;
}
