/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */

#define CORE_PRIVATE
#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 "apr_md5.h"
#include "apr_pools.h"
#include "apr_strings.h"
#include "apr_buckets.h"
#include "util_filter.h"
#include "scoreboard.h"
#include "pop.h"

#include <sys/types.h>
#include <assert.h>

static void md5_convert(unsigned char digest[(2 * APR_MD5_DIGESTSIZE) + 1])
{
    char *ptr;
    int i;
    unsigned char hash[APR_MD5_DIGESTSIZE];
    const char *hex = "0123456789abcdef";
 
    memcpy(hash, digest, APR_MD5_DIGESTSIZE);
 
    for (i = 0, ptr = digest; i < APR_MD5_DIGESTSIZE; i++) {
        *ptr++ = hex[hash[i] >> 4];
        *ptr++ = hex[hash[i] & 0xF];
    }
    *ptr = '\0';
}

static char *compute_md5(request_rec *r, pop_msg *msg)
{
    apr_mmap_t *mm = NULL;
    apr_finfo_t finfo;
    pop_user_rec *ur = (pop_user_rec *)ap_get_module_config(r->request_config,
                                                              &pop_module);
    unsigned char *digest = apr_pcalloc(ur->p, 2 * APR_MD5_DIGESTSIZE + 1);

    apr_stat(&finfo, r->filename, APR_FINFO_SIZE, ur->p);
    apr_mmap_create(&mm, ur->fp, 0, 
                    finfo.size, APR_MMAP_READ, ur->p);

    apr_md5_init(ur->ctx);
    apr_md5_update(ur->ctx, (char*)mm->mm + msg->header_start, msg->msg_end - msg->header_start);
    apr_md5_final(digest, ur->ctx);
    md5_convert(digest);
    return digest;
}

int process_pop_connection_internal(request_rec *r, apr_bucket_brigade *bb)
{
    char cmdbuff[POP_STRING_LENGTH];
    char *buffer; /* a pointer to cmdbuff */
    char *command;
    int invalid_cmd = 0;
    apr_size_t len;
    pop_handler_st *handle_func;
    apr_pool_t *p;
    pop_user_rec *ur = (pop_user_rec *)ap_get_module_config(r->request_config,
                                                              &pop_module);

    apr_pool_create(&p, r->pool);
    r->uri = apr_pstrdup(r->pool, "pop:");

    ap_run_map_to_storage(r);

    while (1) {
        int res;
        buffer = cmdbuff; /* reset buffer pointer */
        apr_pool_clear(p);

        if ((invalid_cmd > MAX_INVALID_CMD) ||
            ap_rgetline(&buffer, POP_STRING_LENGTH, &len, r, 0, bb) != APR_SUCCESS)
        {
            break;
        }

        /* The command moves the pointer of buffer to the end of the extracted string */
        command = ap_getword_white_nc(p, &buffer);
        ap_str_tolower(command);
        handle_func = apr_hash_get(ap_pop_hash, command, APR_HASH_KEY_STRING);

        if (!handle_func) {
            ap_rprintf(r, "-ERR command not understood\r\n");
            ap_rflush(r);
            invalid_cmd++;
            continue;
        }
        if (!(handle_func->states & ur->state)) {
            ap_rprintf(r, "-ERR command %s not allowed in this state\r\n", command);
            ap_rflush(r);
            invalid_cmd++;
            continue;
        }
        res = handle_func->func(r, buffer);
        if (res == POP_QUIT) {
            break;
        }
    }

    return OK;
}

static pop_msg *find_message(request_rec *r, pop_mbox *mbox, int num, int report)
{
    pop_msg *msg = APR_RING_FIRST(&(mbox)->list);
    int j;

    for (j = 1; j < num; j++) {
        msg = APR_RING_NEXT(msg, link);
        if (msg == APR_RING_SENTINEL(&(mbox)->list, pop_msg, link)) {
            msg = NULL;
            break;
        } 
    }
    if (msg == NULL) {
        if (report) {
            ap_rprintf(r, "-ERR no such message, only %d messages in "
                       "maildrop\r\n",
                       APR_RING_LAST(&(mbox)->list)->id);
        }
        msg = NULL;
    }
    else if (num == 0) {
        if (report) {
            ap_rprintf(r, "-ERR no such message, messages start at 1\r\n");
        }
        msg = NULL;
    }
    else if (msg->deleted) {
        if (report) {
            ap_rprintf(r, "-ERR message %d already deleted\r\n", num);
        }
        msg = NULL;
    }
    return msg;
}

static pop_msg *generate_scan_listing(request_rec *r, pop_mbox *mbox, int num,
                                      int with_ok)
{
    /* We do not want to report errors from find_message unless we are sending
     * the ok string.
     */
    pop_msg *msg = find_message(r, mbox, num, with_ok);

    if (msg != NULL) {
        if (with_ok) {
            ap_rwrite("+OK ", strlen("+OK "), r);
        }
        ap_rprintf(r, "%d %"APR_OFF_T_FMT"\r\n",
                   num, msg->msg_end - msg->header_start);
    }
    return msg;
}

static pop_msg *generate_unique_listing(request_rec *r, pop_mbox *mbox, int num,
                                      int with_ok)
{
    /* We do not want to report errors from find_message unless we are sending
     * the ok string.
     */
    pop_msg *msg = find_message(r, mbox, num, with_ok);

    if (msg != NULL) {
        if (with_ok) {
            ap_rwrite("+OK ", strlen("+OK "), r);
        }
        ap_rprintf(r, "%d %s\r\n",
                   num, compute_md5(r, msg));
    }
    return msg;
}

static void msg_num_and_size(pop_mbox *mbox, apr_size_t *num, apr_size_t *size)
{
    pop_msg *msg;
    apr_size_t i = 0, j = 0;

    /* APR_RING_FOREACH(msg, &(mbox)->list, pop_msg, link) { */
    while ( !APR_RING_EMPTY(  &(mbox)->list, pop_msg, link )) {
        msg = APR_RING_FIRST( &(mbox)->list );

        if (!msg->deleted) {
            i++;
            j += (msg->msg_end - msg->header_start + 1);
        }
        APR_RING_REMOVE( msg, link);
    }
    *num = i;
    *size = j;
}

int ap_handle_user(request_rec *r, char *buffer)
{
    char *user;
    pop_user_rec *ur = (pop_user_rec *)ap_get_module_config(r->request_config,
                                                              &pop_module);

    user = ap_getword_white_nc(r->pool, &buffer);
    if (!strcmp(user, "")) {
        ap_rprintf(r, "-ERR Must provide user name\r\n");
        ap_rflush(r);
        return POP_USER_NOT_ALLOWED;
    }
    r->user = ur->user = apr_pstrdup(ur->p, user); 

    ap_rprintf(r, "+OK %s is welcome here\r\n", r->user);
    ap_rflush(r);    
    ur->state = USER_ACK;
    return OK;
}

int ap_handle_passwd(request_rec *r, char *buffer)
{
    char *passwd;
    int res;
    pop_user_rec *ur = (pop_user_rec *)ap_get_module_config(r->request_config,
                                                              &pop_module);

    if ((res = pop_parse_maildrop(r, &ur->mbox)) != OK) {
        ur->state = POP_AUTH;
        return res;
    }

    passwd = apr_psprintf(r->pool, "%s:%s", ur->user,
                          ap_getword_white_nc(r->pool, &buffer));
    ur->auth_string = apr_psprintf(r->connection->pool, "Basic %s",
                                   ap_pbase64encode(r->pool, passwd)); 

    apr_table_set(r->headers_in, "Authorization", ur->auth_string);

    ap_run_map_to_storage(r);

    if ((res = ap_run_check_user_id(r)) != OK) {
        ap_rprintf(r,
               "-ERR user %s not known here or invalid password\r\n", ur->user);
        ap_rflush(r);
        ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r,
                      "Unauthorized user tried to log in");
        ur->state = POP_AUTH;
        return POP_USER_NOT_ALLOWED;
    }

    ap_rprintf(r, "+OK, user %s is welcome to read his mail\r\n", ur->user);
    ap_rflush(r);

    ur->state = POP_TRANSACTION;

    return OK;
}

static int rewrite_mbox(request_rec *r)
{
    apr_file_t *f2;
    const char *filename;
    char str[8192];
    apr_size_t len, totsize;
    apr_off_t zero = 0;
    apr_status_t rv, rv2;
    apr_mmap_t *mm;
    pop_msg *msg;
    pop_user_rec *ur = (pop_user_rec *)ap_get_module_config(r->request_config,
                                                              &pop_module);

    filename = apr_pstrcat(ur->p, r->filename, ".tmp", NULL);
    rv = apr_file_open(&f2, filename, APR_READ | APR_WRITE | APR_CREATE | 
                       APR_DELONCLOSE, APR_OS_DEFAULT, ur->p);
    if (rv != APR_SUCCESS) {
        fprintf(stderr, "%d\n", rv);
    }
    len = totsize = 0;
    apr_file_seek(ur->fp, APR_SET, &zero);
    do {
        len = 8192;
        rv = apr_file_read(ur->fp, str, &len);
        totsize += len;
        rv2 = apr_file_write(f2, str, &len);
    } while (rv != APR_EOF);

    apr_file_trunc(ur->fp, 0);
    apr_mmap_create(&mm, f2, 0, totsize, APR_MMAP_READ, ur->p);

    msg = APR_RING_FIRST(&(ur->mbox)->list);
    while (msg != APR_RING_SENTINEL(&(ur->mbox)->list, pop_msg, link)) {
        if (!msg->deleted) {
            apr_size_t len = msg->msg_end - msg->header_start + 1;
            apr_file_write(ur->fp, (char *)mm->mm + msg->header_start, &len);
        }
        msg = APR_RING_NEXT(msg, link);
    }

    return 0;
}

int ap_handle_quit(request_rec *r, char *buffer)
{
    apr_size_t num, size;
    pop_user_rec *ur = (pop_user_rec *)ap_get_module_config(r->request_config,
                                                              &pop_module);

    if (ur->state == POP_TRANSACTION) {
        ur->state = UPDATE;
        
        rewrite_mbox(r); 
        msg_num_and_size(ur->mbox, &num, &size);
        apr_file_unlock(ur->fp);
        ap_rprintf(r, "+OK %s POP3 server signing off ", ap_get_server_name(r));
        if (num == 0) {
            ap_rputs("(maildrop empty)\r\n", r);
        }
        else {
            ap_rprintf(r, "(%d messages left)\r\n", num);
        }
    }
    else {
        ap_rprintf(r, "+OK %s POP3 server signing off\r\n",
                   ap_get_server_name(r));
    }
    ap_rflush(r);
    ur->state = POP_AUTH;
    return POP_QUIT;
}

int ap_handle_dele(request_rec *r, char *buffer)
{
    pop_msg *msg;
    int num;
    pop_user_rec *ur = (pop_user_rec *)ap_get_module_config(r->request_config,
                                                            &pop_module);

    num = atoi(buffer);
    msg = find_message(r, ur->mbox, num, 1);

    if (msg != NULL) {
        msg->deleted = 1;
        ap_rprintf(r, "+OK message %d deleted\r\n", num);
    }
    ap_rflush(r);
    
    return OK;
}

/* This method has been removed from POP3 in RFC 1725, but fetchmail
 * is using it, so we need to leave it in the code.  There may be other
 * POP3 clients that don't use it, but if even one does, we must continue
 * to support this field.
 */
int ap_handle_last(request_rec *r, char *buffer)
{
    pop_user_rec *ur = (pop_user_rec *)ap_get_module_config(r->request_config,
                                                            &pop_module);

    ap_rprintf(r, "+OK %d\r\n", ur->high_access);
    ap_rflush(r);

    return OK;
}

int ap_handle_list(request_rec *r, char *buffer)
{
    char *num;
    pop_user_rec *ur = (pop_user_rec *)ap_get_module_config(r->request_config,
                                                            &pop_module);

    num = ap_getword_white_nc(r->pool, &buffer);
    if (!strcmp(num, "")) {
        pop_msg *msg;
        int last_msg, i;

        if (!APR_RING_EMPTY(&(ur->mbox)->list, pop_msg, link)) {
            msg = APR_RING_LAST(&(ur->mbox)->list);
            last_msg = msg->id;
        }
        else {
            ap_rputs("-ERR No messages in maildrop\r\n", r);
            ap_rflush(r);
            return APR_SUCCESS;
        }

        apr_stat(&r->finfo, r->filename, APR_FINFO_SIZE, r->pool);

        ap_rprintf(r, "+OK %d messages (%"APR_OFF_T_FMT" octets)\r\n",
                   last_msg, r->finfo.size);
    
        for (i = 1; i <= last_msg; i++) {
            generate_scan_listing(r, ur->mbox, i, 0);
        }    
        ap_rwrite(".\r\n", strlen(".\r\n"), r);
        ap_rflush(r);    
    }
    else {
        generate_scan_listing(r, ur->mbox, atoi(num), 1);
        ap_rflush(r);    
    }        
    return OK;
}

int ap_handle_noop(request_rec *r, char *buffer)
{
    ap_rwrite( "+OK\r\n", strlen("+OK\r\n"), r);
    ap_rflush(r);    
    return OK;
}

int ap_handle_retr(request_rec *r, char *buffer)
{
    char *num;
    int i; 
    apr_size_t bytes_sent;
    pop_msg *msg = NULL;
    pop_user_rec *ur = (pop_user_rec *)ap_get_module_config(r->request_config,
                                                            &pop_module);

    num = ap_getword_white_nc(r->pool, &buffer);
    if (!strcmp(num, "")) {
        ap_rputs( "-ERR must supply a message id\r\n", r);
        ap_rflush(r);    
        return POP_BAD_MSG_NUM;
    }
    i = atoi(num);
    msg = generate_scan_listing(r, ur->mbox, i, 1);
    ap_rflush(r);    

    if (msg == NULL) {
        ap_rflush(r);    
        return POP_BAD_MSG_NUM;
    }

    ap_send_fd(ur->fp, r, msg->header_start, msg->msg_end - msg->header_start + 1, 
               &bytes_sent);
    ap_rwrite(".\r\n", strlen(".\r\n"), r);
    ap_rflush(r);
    ur->high_access = msg->id;

    return OK;
}

int ap_handle_rset(request_rec *r, char *buffer)
{
    pop_msg *msg;
    apr_size_t num, size;
    pop_user_rec *ur = (pop_user_rec *)ap_get_module_config(r->request_config,
                                                            &pop_module);
    /* APR_RING_FOREACH(msg, &(ur->mbox)->list, pop_msg, link) {*/
    while ( !APR_RING_EMPTY(  &(ur->mbox)->list, pop_msg, link )) {
        msg = APR_RING_FIRST( &(ur->mbox)->list );

        if (msg->deleted) {
            msg->deleted = 0;
        }
        APR_RING_REMOVE( msg, link);
    }
    ur->high_access = 0;

    msg_num_and_size(ur->mbox, &num, &size);

    ap_rprintf(r, "+OK maildrop has %d messages (%d octets)\r\n", num, size);
    ap_rflush(r);    
    
    return OK;
}

int ap_handle_stat(request_rec *r, char *buffer)
{
    apr_size_t num, size;
    pop_user_rec *ur = (pop_user_rec *)ap_get_module_config(r->request_config,
                                                            &pop_module);
    msg_num_and_size(ur->mbox, &num, &size);

    ap_rprintf(r, "+OK %d %d\r\n", num, size);
    ap_rflush(r);    
    return OK;
}

int ap_handle_uidl(request_rec *r, char *buffer)
{
    char *num;
    pop_user_rec *ur = (pop_user_rec *)ap_get_module_config(r->request_config,
                                                            &pop_module);

    num = ap_getword_white_nc(r->pool, &buffer);
    if (!strcmp(num, "")) {
        pop_msg *msg;
        int last_msg, i;

        if (!APR_RING_EMPTY(&(ur->mbox)->list, pop_msg, link)) {
            msg = APR_RING_LAST(&(ur->mbox)->list);
            last_msg = msg->id;
        }
        else {
            ap_rputs("-ERR No messages in maildrop\r\n", r);
            ap_rflush(r);
            return APR_SUCCESS;
        }

        for (i = 1; i <= last_msg; i++) {
            generate_unique_listing(r, ur->mbox, i, 0);
        }    
        ap_rwrite(".\r\n", strlen(".\r\n"), r);
        ap_rflush(r);    
    }
    else {
        generate_unique_listing(r, ur->mbox, atoi(num), 1);
        ap_rflush(r);    
    }        
    return OK;
}

int ap_handle_top(request_rec *r, char *buffer)
{
    const char *msgnum, *lines;
    apr_size_t bytes_sent;
    int i;
    pop_msg *msg;
    apr_off_t off;
    pop_user_rec *ur = (pop_user_rec *)ap_get_module_config(r->request_config,
                                                            &pop_module);

    msgnum = ap_getword_white_nc(r->pool, &buffer);
    lines = ap_getword_white_nc(r->pool, &buffer);
    if (!strcmp(msgnum, "") || !strcmp(lines, "")) {
        ap_rputs("-ERR TOP requires two arguments\r\n", r);
        ap_rflush(r);
        return POP_BAD_STATE;
    }

    msg = find_message(r, ur->mbox, atoi(msgnum), 1);
    if (msg == NULL) {
        ap_rflush(r);
        return POP_BAD_STATE;
    }

    ap_rputs("+OK\r\n", r);
    ap_send_fd(ur->fp, r, msg->header_start, 
               msg->header_end - msg->header_start, &bytes_sent);
    ap_rputs("\r\n", r);
    off = msg->msg_start;
    apr_file_seek(ur->fp, APR_SET, &off);
    bytes_sent = 0;

    for (i = 0; i < atoi(lines); i++) {
        char str[8192];
        int bytes = 8192;

        apr_file_gets(str, bytes, ur->fp);
        bytes_sent += strlen(str);
        ap_rwrite(str, strlen(str), r);
        if (bytes_sent >= (msg->msg_end - msg->msg_start)) {
            break; 
        }
    }
    ap_rputs(".\r\n", r);
    ap_rflush(r);

    return OK;
}
