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