
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "lib.h"
#define DMID 16
#include "debug.h"
#ifdef USE_DFOPEN
#include "dfopen.h"
#endif
#include "mdrop.h"
#include "lprintf.h"
#define _MDROP_PATH_SEP	'/'			/* character to use when separating paths */


/* deal with Visual C++ ugliness */
#ifdef WIN32
#define strncasecmp strnicmp
#define snprintf _snprintf
#endif

char *nullok(char *s);
/*	_md_make_filename() constructs a maildrop filename from 'user', 'path',
 *	'domain_prefix', hashing on level 'hash_spool' and encoding	invalid
 *	characters in 'invalid'.
 *
 *	Upon success, the maildrop filename is placed in 'dest_filename', and
 *	'dest_filename' is returned.
 *
 *	Upon failure, NULL is returned.
 */
char *_md_make_filename(char *dest_filename, char *user, char *path, const char *domain_prefix, char *invalid, int hash_spool, char *drop_name, char *reason);


/*	_md_ncpy copies string 'src' into string 'dst', to a maximum of 'len' chars.
 *	This is safer and more efficient than ncpy, which it replaces.
 */
char *_md_ncpy(char *dst,const char *src, int len);


/*	_md_ncat copies concatenates string 'src' to string 'dst', to a maximum of
 *	'len' chars. This is safer and more efficient than ncat, which it
 *	replaces.
 */
char *_md_ncat(char *dst,const char *src, int len);


/*	mdrop_open() opens a maildrop for user 'user', domain 'domain', in path
 *	'path'.  If 'drop_filename' is not NULL, then 'drop_filename' is used
 *	rather than creating a drop filename from the aforementioned components.
 *
 *	If non-empty, 'domain_prefix' is prepended to the username 'user'.
 *	mdrop_open() hashes the specified path using 'user' as the username and
 *	'hash_spool' as the hashing level.  When hashing, all characters in the
 *	string pointed to by 'invalid' are considered to be invalid.
 *
 *	mdrop_open() places all maildrop info
 *	in the maildrop structure pointed to by 'mdrop'.
 *
 *	The results of calling mdrop_open() on an unopened 'mdrop' are undefined.
 *
 *	mdrop_open() returns one of the following conditions:
 *
 *		MDROP_OKAY				maildrop was successfully opened
 *		MDROP_FAIL_INIT			failed to initialise maildrop structure
 *		MDROP_FAIL_FILE_IO		specified maildrop can't be opened
 *
 *	If mdrop_open() succeeds, the maildrop structure pointed to by 'mdrop' will
 *	be initialised, and mdrop_open() will return MDROP_OKAY.  Once initialised,
 *	the maildrop structure may not be initialised again until it has been
 *	closed with mdrop_close().
 *
 *	If mdrop_open() fails, mdrop_open() will return MDROP_FAIL_FILE_IO.
 *
 *	If mdrop_open() returns MDROP_FAIL_FILE_IO, and 'failure' is not NULL,
 *	*'failure' will be set to errno, thus allowing the calling function to
 *	determine the reason for failure in detail.
 *
 *	If 'mdrop', 'user', 'path', 'domain_prefix' or 'invalid' are NULL,
 *	mdrop_open() will fail, returning MDROP_FAIL_INIT.
 */
int	mdrop_open(struct maildrop *mdrop, char *user, char *path, const char *domain_prefix, char *invalid, int hash_spool, int *failure, char *drop_filename, char *reason)
{
	char	search_buffer[10240];
	*failure = 0;
	mdrop->inhead = TRUE;
	mdrop->content_len = 0;
	mdrop->upto = 0;

	if (drop_filename!=NULL) if (lib_strlen(drop_filename)==0) drop_filename = NULL;
	if ( _md_make_filename(mdrop->maildrop_filename, user, path, domain_prefix, invalid, hash_spool, drop_filename,reason) == NULL )
	{
		return MDROP_FAIL_INIT;
	}

	/* try to open it */
//	imsg("mdrop: Opening mail drop (%s)",mdrop->maildrop_filename);
	mdrop->maildrop_file = fopen(mdrop->maildrop_filename, "r");
	if ( mdrop->maildrop_file == NULL )
	{
		if ( failure != NULL ) *failure = errno;
		lprintf(reason,100,"Open (%s) %s",mdrop->maildrop_filename,strerror(errno));
		return MDROP_FAIL_FILE_IO;
	}
	if (lib_file_osize(mdrop->maildrop_file)==0) {
		/* Empty drop file, try .old file */
		fclose(mdrop->maildrop_file);
		strcat(mdrop->maildrop_filename,".old");
		mdrop->maildrop_file = fopen(mdrop->maildrop_filename, "r");
		if ((mdrop->maildrop_file==NULL)) {
			strcpy(reason,"No drop file found");
			return MDROP_FAIL_INIT;
		}
		if (lib_file_osize(mdrop->maildrop_file)==0) {
			fclose(mdrop->maildrop_file); mdrop->maildrop_file = NULL;
			strcpy(reason,"No drop file found or all empty");
			return MDROP_FAIL_INIT;
		}
	}

	/* assume we start above the first message */
	mdrop->status = MDROP_STATUS_EOM;
	fgets(search_buffer, 10239, mdrop->maildrop_file);

	return MDROP_OKAY;
}


/*	mdrop_dele() deletes the maildrop for user 'user', domain 'domain', in path
 *	'path'.  If 'drop_filename' is not NULL, then 'drop_filename' is used
 *	rather than creating a drop filename from the aforementioned components.
 *
 *	If mdrop_dele() fails, mdrop_dele() will return MDROP_FAIL_FILE_IO.
 *
 *	If mdrop_dele() returns MDROP_FAIL_FILE_IO, and 'failure' is not NULL,
 *	*'failure' will be set to errno, thus allowing the calling function to
 *	determine the reason for failure in detail.
 *
 *	If 'mdrop', 'user', 'path', 'domain_prefix' or 'invalid' are NULL,
 *	mdrop_open() will fail, returning MDROP_FAIL.
 */
int	mdrop_dele(char *user, char *path, const char *domain_prefix, char *invalid, int hash_spool, int *failure, char *drop_filename)
{
	char maildrop_filename[MDROP_SIZE];
	char reason[MDROP_SIZE];
	if ( _md_make_filename(maildrop_filename, user, path, domain_prefix, invalid, hash_spool, drop_filename,reason) == NULL )
	{
		return MDROP_FAIL_INIT;
	}
	/* try to kill it */
	if ( unlink(maildrop_filename) == -1 )
	{
		if ( failure != NULL ) *failure = errno;
		return MDROP_FAIL_FILE_IO;
	}

	return MDROP_OKAY;
}


/*	mdrop_get_filename() returns a pointer to a string containing the filename
 *	of the maildrop contained within 'mdrop'.
 *
 *	Note that the filename is only set after a call to mdrop_open() - calling
 *	mdrop_get_filename() on a maildrop which has not been opened, or had an
 *	attempt at opening fail, will invoke undefined behaviour.
 *
 *	mdrop_get_filename() will return NULL if 'mdrop' is NULL.
 */
const char *mdrop_get_filename(struct maildrop *mdrop)
{
	if ( mdrop == NULL ) return NULL;

	return mdrop->maildrop_filename;
}


/*	mdrop_close() closes maildrop structure 'mdrop'.
 *
 *	If mdrop_close() succeeds, the maildrop structure pointed to by 'mdrop' will
 *	be closed, and mdrop_close() will return MDROP_OKAY.  Any open files will be
 *	closed, and all allocated memory will be freed.  The maildrop structure
 *	pointed to by 'mdrop' will then be free for use by mdrop_close().
 *
 *	The results of calling mdrop_close() on an unopened 'mdrop' are undefined.
 *
 *	If mdrop_close() fails, or if 'mdrop' is NULL, mdrop_close() will return
 *	MDROP_FAIL.
 */
int	mdrop_close(struct maildrop *mdrop)
{
	if ( mdrop == NULL ) return MDROP_FAIL;

	if ( fclose(mdrop->maildrop_file) == 0 )
	{
		*mdrop->maildrop_filename = '\0';
		mdrop->status = MDROP_STATUS_CLOSED;
		return MDROP_OKAY;
	}
	else
	{
		return MDROP_FAIL;
	}
}


/*	mdrop_status() returns an integer specifying the status of the maildrop
 *	pointed to by 'mdrop'.
 *
 *	The results of calling mdrop_open() on an unopened 'mdrop' are undefined.
 *
 *	The possible return values of mdrop_status() are:
 *
 *		MDROP_STATUS_IN_MSG		maildrop is open, and currently in the midst of a message
 *		MDROP_STATUS_EOF		maildrop is open, and currently at the end of the file
 *		MDROP_STATUS_EOM		maildrop is open, and currently at the end of a message
 *		MDROP_STATUS_ERROR		maildrop is not open, due to an unspecified error
 *		MDROP_CLOSED			maildrop has been closed by mdrop_close()
 *		MDROP_FAIL				'mdrop' is NULL.
 */
int mdrop_status(struct maildrop *mdrop)
{
	if ( mdrop == NULL ) return MDROP_FAIL;

	return mdrop->status;
}


/*	mdrop_next_message() moves to the next message in the maildrop pointed to by
 *	'mdrop'.
 *
 *	The results of calling mdrop_open() on an unopened 'mdrop' are undefined.
 *
 *	If mdrop_next_message() succeeds, mdrop_next_message() returns MDROP_OKAY if
 *	there is another message, or MDROP_FAIL_EOF if at the end of the last message.
 *
 *	If mdrop_next_message() fails, mdrop_next_message() will return MDROP_FAIL_FILE_IO.
 *
 *	mdrop_next_message() returns MDROP_FAIL if 'mdrop' is NULL.
 */
int	mdrop_next_message(struct maildrop *mdrop)
{
	char	search_buffer[10240];
	char	*result;

	mdrop->content_len = 0;
	mdrop->upto = 0;
	if ( mdrop == NULL ) return MDROP_FAIL;

	/* simply return if we're at the end of the maildrop */
	if ( mdrop->status == MDROP_STATUS_EOF ) return MDROP_FAIL_EOF;

	/* if we're at the end of the message, assume that the next line is the beginning of a new message, so step past it and return */
	if ( mdrop->status == MDROP_STATUS_EOM )
	{
		/* step past 'From ' separator */
//		fgets(search_buffer, 10239, mdrop->maildrop_file);
// Removed by chrisp, since this is never the case, the from has always gone already 
		mdrop->status = MDROP_STATUS_IN_MSG;
		mdrop->inhead = TRUE;
		mdrop->content_len = 0;
		mdrop->upto = 0;
		return MDROP_OKAY;
	}

	/* otherwise, search until we find either EOF, or the start of a new message */
	while ( 1 )
	{
		result = fgets(search_buffer, 10239, mdrop->maildrop_file);

		if ( result == NULL )
		{
			/* assume we've hit the end of the file here */
			mdrop->status = MDROP_STATUS_EOF;
			return MDROP_FAIL_EOF;
		}

		if ( strncmp(search_buffer, "From ", 5) == 0 )
		{
			/* we've found the start of another message */
			mdrop->inhead = TRUE;
			mdrop->content_len = 0;
			mdrop->upto = 0;
			mdrop->status = MDROP_STATUS_IN_MSG;
			return MDROP_OKAY;
		}
	}
}


/*	mdrop_gets() attempts to read up to one character less than 'size' characters
 *	from maildrop 'mdrop'.
 *
 *	Reading stops at the end of the current message, or a newline.  Newlines are
 *	stored in the buffer; they are not discarded.
 *
 *	The results of calling mdrop_open() on an unopened 'mdrop' are undefined.
 *
 *	If mdrop_gets() succeeds, it returns 'buf', and returns NULL on failure or
 *	upon being passed a NULL 'mdrop'.
 */
static int do_content=FALSE;
int mdrop_use_content(int x)
{
	do_content = x;
	return TRUE;
}
char *mdrop_gets(char *buf, int size, struct maildrop *mdrop)
{
	if ( mdrop == NULL ) return NULL;

	if ( mdrop->status != MDROP_STATUS_IN_MSG )
	{
		return NULL;
	}

	/* if we get here then mdrop is hopefully valid, and we're in a message */
	if ( fgets(buf, size, mdrop->maildrop_file) == NULL )
	{
		if ( feof(mdrop->maildrop_file) )
		{
			mdrop->status = MDROP_STATUS_EOF;
		}
		else
		{
			mdrop->status = MDROP_STATUS_ERROR;
		}

		return NULL;
	}

	if (buf[0]=='\n' || buf[0]=='\r') {
		mdrop->inhead = FALSE;
	}
	if (mdrop->inhead) {
		if (strncmp(buf, "Content-Length: ", 16) == 0) {
			mdrop->content_len = atoi(buf+16);
			mdrop->upto = - (int) lib_strlen(buf);
//			imsg("mdrop: content len found (%d)\n",mdrop->content_len);
		}
	}
	mdrop->upto += lib_strlen(buf);
	if (do_content) if (mdrop->upto<mdrop->content_len-1) {
		return buf;
	}
	if ( strncmp(buf, "From ", 5) == 0 )
	{
//		imsg("mdrop: content len (%d) do it (%d) upto (%d)\n",mdrop->content_len,do_content,mdrop->upto);
		/* we've hit the end of the message */
		mdrop->status = MDROP_STATUS_EOM;

		*buf = '\0';
	}

	return buf;
}


/*	_md_ncpy copies string 'src' into string 'dst', to a maximum of 'len' chars.
 *	This is safer and more efficient than ncpy, which it replaces.
 */
char *_md_ncpy(char *dst,const char *src, int len)
{
	char *xdst = dst;	/* temporary pointer to destination */
	int xlen = len;		/* position of null terminator */

    /* copy source string over destination string */
	for (;(*src !=0) && (len>0); ) {
		*dst++  = *src++;
		len--;
	}

	/* enure that destination string is safely terminated */
	*dst++ = 0;
	xdst[xlen] = 0;

	return xdst;
}


/*	_md_ncat copies concatenates string 'src' to string 'dst', to a maximum of
 *	'len' chars. This is safer and more efficient than ncat, which it
 *	replaces.
 */
char *_md_ncat(char *dst,const char *src, int len)
{
	char *xdst = dst;	/* temporary pointer to destination */
	int xlen = len;		/* position of null terminator */

	/* find end of destination string */
	for (;(*dst !=0) && (len>0); dst++) {
		len--;
	}

    /* append source string to end of destination string */
	for (;(*src !=0) && (len>0); ) {
		*dst++  = *src++;
		len--;
	}

	/* enure that destination string is safely terminated */
	*dst++ = 0;
    xdst[xlen] = 0;

	return xdst;
}


/*	_md_make_filename() constructs a maildrop filename from 'user', 'path',
 *	'domain_prefix', hashing on level 'hash_spool' and encoding	invalid
 *	characters in 'invalid'.
 *
 *	Upon success, the maildrop filename is placed in 'dest_filename', and
 *	'dest_filename' is returned.
 *
 *	Upon failure, NULL is returned.
 */
char *_md_make_filename(char *dest_filename, char *user, char *path, const char *domain_prefix, char *invalid, int hash_spool, char *drop_name, char *reason)
{
	char encoded_username[NEWHASH_BFSZ];
	char maildrop_basepath[NEWHASH_BFSZ];
	char tmp[BFSZ];

	if (drop_name!=NULL) if (lib_strlen(drop_name)==0) drop_name=NULL;
	/* ensure we haven't been passed NULL strings */
	if ( !user || !path || !domain_prefix || !invalid )
	{
		strcpy(reason,"Passed a null pointer");
		return NULL;
	}

	/* generate an encoded username */
	_md_ncpy(encoded_username, user, NEWHASH_BFSZ - 1);
	encode_invalid(encoded_username, invalid,tmp);


	if ( drop_name )
	{
		_md_ncpy(dest_filename, drop_name, MDROP_SIZE - 1);
	}
	else
	{
		/* generate path to bin directory */
		_md_ncpy(maildrop_basepath, path, NEWHASH_BFSZ - 1);
		hash_new(maildrop_basepath, user, invalid, hash_spool, 0, NULL);
		snprintf(dest_filename, MDROP_SIZE - 1, "%s%s%s", lib_addsep(maildrop_basepath,tmp), domain_prefix,encoded_username);
	}

	return dest_filename;
}


/*	The following main() function is only included if _MDROP_TESTING is defined.
 *	It produces an executable that dumps all messages in a maildrop to console,
 *	separated by lines of dashes.  The syntax is:
 *
 *		mdrop username path domain_prefix hashing_level
 *
 *	For example:
 *
 * 		./mdrop test0 /var/spool/mail "" 2
 */
#ifdef _MDROP_TESTING
int main(int argc, char *argv[])
{
	struct maildrop		my_maildrop;
	int					result;
	int					error;
	char				buffer[1024];

	if ( argc < 5 )
	{
		printf("insufficient arguments\n");
		return EXIT_FAILURE;
	}

	/* try to open maildrop */
	result = mdrop_open(&my_maildrop, argv[1], argv[2], argv[3], ".\\/", atoi(argv[4]), &error, NULL);
	if ( result != MDROP_OKAY )
	{
		fprintf(stderr, "maildrop '%s' failed to open", nullok(mdrop_get_filename(&my_maildrop)));
		if ( result == MDROP_FAIL_FILE_IO )
		{
			fprintf(stderr, ": %s", strerror(error));
		}
		fprintf(stderr, "\n");
		return EXIT_FAILURE;
	}
	else
	{
		/* display success */
		printf("maildrop '%s' opened\n", nullok(mdrop_get_filename(&my_maildrop)));
	}

	/* print contents of all messages */
	while ( 1 )
	{
		if ( mdrop_next_message(&my_maildrop) != MDROP_OKAY )
		{
			fprintf(stderr, "failed to get next message in maildrop\n");
			break;
		}

		printf("--------------------------------------------------------\n");
		while ( mdrop_gets(buffer, 1023, &my_maildrop) != NULL )
		{
			printf("%s", buffer);
		}
		printf("--------------------------------------------------------\n");
	}

	/* try to close maildrop */
	if ( mdrop_close(&my_maildrop) == MDROP_OKAY )
	{
		printf("maildrop closed\n");
	}
	else
	{
		fprintf(stderr, "maildrop '%s' failed to close\n", nullok(mdrop_get_filename(&my_maildrop)));
		return EXIT_FAILURE;
	}

	/* if specified, delete maildrop */
	if ( argc > 5 )
	{
		if ( strcmp(argv[5], "dele") == 0 )
		{
			result = mdrop_dele(argv[1], argv[2], argv[3], ".\\/", atoi(argv[4]), &error, NULL);
			if ( result != MDROP_OKAY )
			{
				fprintf(stderr, "maildrop failed to delete");
				if ( result == MDROP_FAIL_FILE_IO )
				{
					fprintf(stderr, ": %s", strerror(error));
				}
				fprintf(stderr, "\n");
				return EXIT_FAILURE;
			}
			else
			{
				/* display success */
				printf("maildrop deleted\n");
			}
		}
	}

	return EXIT_SUCCESS;
}
#endif

