/* ====================================================================
* 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
* .
*
*/
#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
#include
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;
}