/*
 * (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 <config.h>
#endif

#include <pop3lite.h>
#include <glib.h>
#include "md5-wrap.h"

#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#include "mailbox.h"
#include "mailbox-trans.h"

#ifdef HAVE_LOCKFILE
#	include <lockfile.h>
#	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;
}


syntax highlighted by Code2HTML, v. 0.9.1