/* * (POP3Lite) MailBox - 3lite POP3 Daemon (mailbox driver) * Copyright (C) 2000, 2001 Gergely Nagy <8@free.bsd.hu> * * This file is part of POP3Lite. * * POP3Lite is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * POP3Lite is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include "md5-wrap.h" #include #include #include #include #include #include #include #include #include #include #include "mailbox.h" #include "mailbox-trans.h" #ifdef HAVE_LOCKFILE # include # include "mailbox-p.h" #endif static const char rcsid[]="$Id: mailbox-trans.c,v 1.16.2.1 2001/08/19 04:18:12 algernon Exp $"; extern int errno; /* * FORWARD DECLARATIONS */ static char *TRANS_read_next_message ( int fd, size_t size ); static CommandResponse *TRANS_do_retrieve ( P3LControl *control, const char *args, gboolean is_top ); CommandResponse * mailbox_trans_init ( P3LControl *control ) { int mailboxfd; MailInfo *minfo; off_t offset=0; md5_context context; struct stat stbuf; P3LString **mailbox_split; char *mailbox, *mailbox_fn, *buffer; size_t size, new_size, i, lines, cline=0; P3LHook_mailbox_mail_parse parse_hook=NULL; mode_t old_umask; #ifdef DEBUG control->system->log ( control, LOG_DEBUG, "%s:%d: TRANSACTION state started (%s)", __FILE__, __LINE__, P3L_GET_DATA ( "USER" ) ); #endif g_hash_table_insert ( control->data, "BANNER", p3l_received_header ( control->data ) ); mailbox_fn = P3L_CALL_HOOK ( P3LHook_get_mailbox, "GET-MAILBOX" ) ( control ); g_hash_table_insert ( control->data, "MAILBOX", g_strdup ( mailbox_fn ) ); parse_hook = (P3LHook_mailbox_mail_parse) g_hash_table_lookup ( control->hooks, "MAILBOX-PARSE-MAIL" ); /* * Open, lock & parse mailbox */ if ( ( mailboxfd = open ( mailbox_fn, O_RDWR ) ) < 0 ) { old_umask = umask ( 000 ); if ( ( mailboxfd = open ( mailbox_fn, O_RDWR | O_CREAT, 0660 ) ) < 0 ) { umask ( old_umask ); control->system->log ( control, LOG_ERR, "Can't open mailbox '%s': %s", mailbox_fn, g_strerror ( errno ) ); return p3l_respond ( POP3_FATAL, "can't open mailbox" ); } umask ( old_umask ); } if ( p3l_lock_fd ( mailboxfd, mailbox_fn ) != 0 ) { close ( mailboxfd ); control->system->log ( control, LOG_WARNING, "Can't lock mailbox: '%s': %s", mailbox_fn, g_strerror ( errno ) ); return p3l_respond ( POP3_FATAL, "can't lock mailbox" ); } if ( fstat ( mailboxfd, &stbuf ) < 0 ) { p3l_unlock_fd ( mailboxfd, mailbox_fn ); close ( mailboxfd ); control->system->log ( control, LOG_ERR, "fstat failed on mailbox '%s': %s", mailbox_fn, g_strerror ( errno ) ); return p3l_respond ( POP3_FATAL, "fstat failed" ); } if ( !S_ISREG ( stbuf.st_mode ) ) { p3l_unlock_fd ( mailboxfd, mailbox_fn ); close ( mailboxfd ); control->system->log ( control, LOG_ALERT, "Mailbox is not a regular file: %s", mailbox_fn ); return p3l_respond ( POP3_FATAL, "mailbox is not a regular file" ); } #ifdef HAVE_LOCKFILE p3l_register_alarm ( mailbox_alarm_handler, 60 ); #endif g_hash_table_insert ( control->data, "MBSIZE", (gpointer) (gint) stbuf.st_size ); g_hash_table_insert ( control->data, "MBTIME", (gpointer) stbuf.st_mtime ); mailbox = p3l_read_fd ( mailboxfd, &size ); mailbox_split = p3l_split_lines ( mailbox, size, &lines ); i = 0; buffer = NULL; size = 0; offset = 0; for ( i = 0 ; i < lines; i++ ) { if ( ( mailbox_split[i] == NULL ) || ( mailbox_split[i] != NULL && i > 0 && ! memcmp ( "From ", mailbox_split[i]->str, 5 ) ) ) { minfo = (MailInfo *) g_malloc ( sizeof ( MailInfo ) ); minfo->driver_data = g_hash_table_new ( (GHashFunc) g_str_hash, (GCompareFunc) g_str_equal ); g_hash_table_insert ( minfo->driver_data, "offset", (gpointer) (gint) offset ); g_hash_table_insert ( minfo->driver_data, "real_size", (gpointer) size ); minfo->deleted = FALSE; if ( parse_hook != NULL ) (*parse_hook) ( control, buffer, minfo ); __md5_init_ctx ( &context ); __md5_process_bytes ( buffer, size, &context); __md5_finish_ctx ( &context, &(minfo->digest)); buffer = p3l_parse_buffer ( buffer, control->data, size, &new_size ); minfo->virtual_size = new_size + cline + 1; control->msg_info=g_list_append ( control->msg_info, minfo ); g_free ( buffer ); buffer = NULL; offset = size; size = 0; cline = 0; } cline++; if ( mailbox_split[i] == NULL ) break; buffer = (char *) g_realloc ( buffer, size + mailbox_split[i]->length ); memcpy ( buffer+size, mailbox_split[i]->str, mailbox_split[i]->length ); size+=mailbox_split[i]->length; } if ( size > 0 ) { minfo = (MailInfo *) g_malloc ( sizeof ( MailInfo ) ); minfo->driver_data = g_hash_table_new ( (GHashFunc) g_str_hash, (GCompareFunc) g_str_equal ); g_hash_table_insert ( minfo->driver_data, "offset", (gpointer) (gint) offset ); g_hash_table_insert ( minfo->driver_data, "real_size", (gpointer) size ); minfo->deleted = FALSE; if ( parse_hook != NULL ) (*parse_hook) ( control, buffer, minfo ); __md5_init_ctx ( &context ); __md5_process_bytes ( buffer, size, &context ); __md5_finish_ctx ( &context, &(minfo->digest) ); control->msg_info=g_list_append ( control->msg_info, minfo ); buffer = p3l_parse_buffer ( buffer, control->data, size, &new_size ); minfo->virtual_size = new_size + cline + 1; } g_free ( buffer ); g_free ( mailbox ); g_hash_table_insert ( control->data, "MAILBOXFD", GINT_TO_POINTER ( mailboxfd ) ); return p3l_respond ( POP3_OK, "Congratulations!" ); } CommandResponse * mailbox_cmd_trans_dele ( P3LControl *control, const char *args ) { unsigned long msg; MailInfo *minfo; #ifdef DEBUG control->system->log ( control, LOG_DEBUG, "%s:%d: handling DELE (%s)", __FILE__, __LINE__, P3L_GET_DATA ( "USER" ) ); #endif if ( args == NULL ) return p3l_respond ( POP3_ERR, "No such message" ); if ( ! p3l_is_numeric ( args ) ) return p3l_respond ( POP3_ERR, "Invalid argument" ); msg = strtoul ( args, (char **) NULL, 10 ); minfo = g_list_nth_data ( control->msg_info, msg - 1 ); if ( minfo != NULL ) { if ( minfo->deleted == TRUE ) return p3l_respond ( POP3_ERR, "Message already deleted" ); else { minfo->deleted = TRUE; return p3l_respond ( POP3_OK, g_strdup_printf ( "Message %lu marked for deletion", msg ) ); } } else return p3l_respond ( POP3_ERR, "No such message" ); } CommandResponse * mailbox_cmd_trans_retr ( P3LControl *control, const char *args ) { #ifdef DEBUG control->system->log ( control, LOG_DEBUG, "%s:%d: handling RETR (%s)", __FILE__, __LINE__, P3L_GET_DATA ( "USER" ) ); #endif return TRANS_do_retrieve ( control, args, FALSE ); } CommandResponse * mailbox_cmd_trans_top ( P3LControl *control, const char *args ) { #ifdef DEBUG control->system->log ( control, LOG_DEBUG, "%s:%d: handling TOP (%s)", __FILE__, __LINE__, P3L_GET_DATA ( "USER" ) ); #endif return TRANS_do_retrieve ( control, args, TRUE ); } /** * TRANS_do_retrieve: RETR/TOP uniform handler * @control: the main control struct * @args: arguments, as passed by the client * @is_top: TRUE if we're handling TOP, FALSE if we're hadnling RETR * * This is an uniform handler for the RETR and TOP commands. This will * do the hard job by reading the specified message from the mailbox * file, and echoing it back to the client. If is_top is TRUE, then it * will only echo the specified number of lines from the mail BODY. * * Returns: the final response, probalby an +OK **/ CommandResponse * TRANS_do_retrieve ( P3LControl *control, const char *args, gboolean is_top ) { unsigned long num, msg, x; size_t max_lines, lines, new_size, size; MailInfo *minfo, *tinfo; int mailboxfd; char *buffer, **argv; gboolean is_body=FALSE; P3LString **message; struct stat stbuf; char *mailbox = P3L_GET_DATA ( "MAILBOX" ); max_lines = 0; if ( args == NULL ) { return p3l_respond ( POP3_ERR, "No such message" ); } if ( is_top ) { argv = g_strsplit (args, " ", 1); if ( argv[0] == NULL ) return p3l_respond ( POP3_ERR, "No such message" ); if ( argv[1] == NULL ) return p3l_respond ( POP3_ERR, "Not enough arguments" ); argv[0]=g_strstrip(argv[0]); argv[1]=g_strstrip(argv[1]); if ( ! p3l_is_numeric ( argv[0] ) ) return p3l_respond ( POP3_ERR, "Invalid argument" ); if ( ! p3l_is_numeric ( argv[1] ) ) return p3l_respond ( POP3_ERR, "Invalid argument" ); msg = strtoul ( argv[0], (char **) NULL, 10 ) - 1; max_lines = strtoul ( argv[1], (char **) NULL, 10 ); g_strfreev ( argv ); } else msg = strtoul ( args, (char **) NULL, 10 ) - 1; mailboxfd = GPOINTER_TO_INT ( g_hash_table_lookup ( control->data, "MAILBOXFD" ) ); if ( fstat ( mailboxfd, &stbuf ) < 0 ) { #ifdef HAVE_LOCKFILE p3l_unregister_alarm ( mailbox_alarm_handler ); #endif p3l_unlock_fd ( mailboxfd, mailbox ); close ( mailboxfd ); control->system->log ( control, LOG_ERR, "fstat failed on mailbox '%s': %s", mailbox, g_strerror ( errno ) ); return p3l_respond ( POP3_FATAL, "fstat failed" ); } if ( !S_ISREG ( stbuf.st_mode ) ) { #ifdef HAVE_LOCKFILE p3l_unregister_alarm ( mailbox_alarm_handler ); #endif p3l_unlock_fd ( mailboxfd, mailbox ); close ( mailboxfd ); control->system->log ( control, LOG_ALERT, "Mailbox is not a regular file: %s", mailbox ); return p3l_respond ( POP3_FATAL, "mailbox is not a regular file" ); } if ( stbuf.st_size != (off_t) GPOINTER_TO_INT ( g_hash_table_lookup ( control->data, "MBSIZE" ) ) || stbuf.st_mtime != (time_t) g_hash_table_lookup ( control->data, "MBTIME" ) ) { #ifdef HAVE_LOCKFILE p3l_unregister_alarm ( mailbox_alarm_handler ); #endif p3l_unlock_fd ( mailboxfd, mailbox ); close ( mailboxfd ); control->system->log ( control, LOG_ERR, "Mailbox content changed: %s", mailbox ); return p3l_respond ( POP3_FATAL, "mailbox content changed" ); } minfo = g_list_nth_data ( control->msg_info, msg ); if ( minfo == NULL || ( minfo && minfo->deleted == TRUE ) ) return p3l_respond ( POP3_ERR, "No such message" ); control->send_response ( control, POP3_OK, g_strdup_printf ( "%u octects", minfo->virtual_size ) ); lseek ( mailboxfd, 0, SEEK_SET ); for ( x = 0; x <= msg; x++ ) { tinfo = g_list_nth_data ( control->msg_info, x ); lseek ( mailboxfd, (off_t) GPOINTER_TO_INT ( g_hash_table_lookup ( tinfo->driver_data, "offset" ) ), SEEK_CUR ); } size = (size_t) g_hash_table_lookup ( minfo->driver_data, "real_size" ); buffer = TRANS_read_next_message ( mailboxfd, (ssize_t) size ); buffer = p3l_parse_buffer ( buffer, control->data, size, &new_size ); message = p3l_split_lines ( buffer, new_size, &lines ); if ( ! is_top ) max_lines = lines; num = 0; x = num; do { if ( message[x] == NULL ) break; if ( is_body ) { if ( max_lines == 0 ) break; else num++; } if ( message[x]->length == 1 && ! memcmp ( message[x]->str, "\n", 1 ) ) is_body = TRUE; if ( message[x]->str[0] == '.' ) /* Line begins with a dot */ control->send_raw ( control, ".", 1 ); /* ... prepend another one */ control->send_raw ( control, message[x]->str, message[x]->length - 1 ); control->send_raw ( control, "\r\n", 2 ); x++; } while ( (num < max_lines && num < lines ) || max_lines == 0 ); g_free ( buffer ); return p3l_respond ( POP3_ANSWERED, "."); } /** * TRANS_read_next_message: read the next message from the mailbox * @fd: file descriptor * @size: message size * * This little function is responsible for retrieving the next message * from a mailbox. * * Returns: a newly allocated buffer containing the message **/ static char * TRANS_read_next_message ( int fd, size_t size ) { char *buffer; buffer = (char *) g_malloc ( size + 2 ); read ( fd, buffer, size ); return buffer; }