/*
 Copyright (C) 2004 IC & S dbmail@ic-s.nl

 This program 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.

 This program 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/* $Id: lmtp.c 1700 2005-03-21 19:08:03Z aaron $
 *
 * implementation for lmtp commands according to RFC 1081 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "dbmail.h"
#include "lmtp.h"
#include "pipe.h"
#include "header.h"
#include "db.h"
#include "dsn.h"
#include "debug.h"
#include "dbmailtypes.h"
#include "auth.h"
#include "clientinfo.h"
#include "lmtp.h"
#ifdef PROC_TITLES
#include "proctitleutils.h"
#endif

#define INCOMING_BUFFER_SIZE 512
#define MESSAGE_MAX_LINE_SIZE 1024

/* default timeout for server daemon */
#define DEFAULT_SERVER_TIMEOUT 300

/* max_errors defines the maximum number of allowed failures */
#define MAX_ERRORS 3

/* max_in_buffer defines the maximum number of bytes that are allowed to be
 * in the incoming buffer */
#define MAX_IN_BUFFER 255

/* These are needed across multiple calls to lmtp() */
static struct list from, rcpt;

/* allowed lmtp commands */
static const char *const commands[] = {
	"LHLO", "QUIT", "RSET", "DATA", "MAIL",
	"VRFY", "EXPN", "HELP", "NOOP", "RCPT"
};

static const char validchars[] =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    "_.!@#$%^&*()-+=~[]{}<>:;\\\"`'/ ";

static char myhostname[64];

/**
 * read the whole message from a network connection
 * \param[in] instream input stream
 * \param[out] whole_message will hold the complete email-message
 * \param[out] whole_message_size will hold the size of the message
 * \return
 *     - -1 on error
 *     -  1 on success
 */
static int read_whole_message_network(FILE *instream,
		char **whole_message, u64_t *whole_message_size,
		const char *prepend_format, ...) PRINTF_ARGS(4, 5);

/**
 * \function lmtp_error
 *
 * report an LMTP error
 * \param session current LMTP session
 * \param stream stream to right to
 * \param formatstring format string
 * \param ... values to fill up formatstring
 */
int lmtp_error(PopSession_t * session, void *stream,
	       const char *formatstring, ...) PRINTF_ARGS(3, 4);

/**
 * initialize a new session. Sets all relevant variables in session
 * \param[in,out] session to initialize
 */
void lmtp_init(PopSession_t *session) 
{
	/* setting Session variables */
	session->state = STRT;
	session->error_count = 0;

	session->username = NULL;
	session->password = NULL;

	session->SessionResult = 0;

	/* reset counters */
	session->totalsize = 0;
	session->virtual_totalsize = 0;
	session->totalmessages = 0;
	session->virtual_totalmessages = 0;

	/* set the lists to zero length */
	list_init(&rcpt);
	list_init(&from);
}

int lmtp_reset(PopSession_t * session)
{
	if (list_totalnodes(&rcpt) > 0) {
		dsnuser_free_list(&rcpt);
	}
	list_init(&rcpt);

	if (list_totalnodes(&from) > 0) {
		list_freelist(&from.start);
	}
	list_init(&from);

	session->state = LHLO;

	return 1;
}


int lmtp_handle_connection(clientinfo_t * ci)
{
	/*
	   Handles connection and calls
	   lmtp command handler
	 */

	int done = 1;		/* loop state */
	char *buffer = NULL;	/* connection buffer */
	int cnt;		/* counter */

	PopSession_t session;	/* current connection session */
	
	lmtp_init(&session);

	/* getting hostname */
	gethostname(myhostname, 64);
	myhostname[63] = 0;	/* make sure string is terminated */

	buffer = (char *) dm_malloc(INCOMING_BUFFER_SIZE * sizeof(char));

	if (!buffer) {
		trace(TRACE_MESSAGE,
		      "lmtp_handle_connection(): Could not allocate buffer");
		return 0;
	}

	if (ci->tx) {
		/* sending greeting */
		ci_write(ci->tx,
			"220 %s DBMail LMTP service ready to rock\r\n",
			myhostname);
		fflush(ci->tx);
	} else {
		trace(TRACE_MESSAGE,
		      "lmtp_handle_connection(): TX stream is null!");
		dm_free(buffer);
		return 0;
	}

	lmtp_reset(&session);
	while (done > 0) {

		if (db_check_connection()) {
			trace(TRACE_ERROR,"%s,%s: database has gone away", __FILE__, __func__);
			done=-1;
			continue;
		}

		/* set the timeout counter */
		alarm(ci->timeout);

		/* clear the buffer */
		memset(buffer, 0, INCOMING_BUFFER_SIZE);

		for (cnt = 0; cnt < INCOMING_BUFFER_SIZE - 1; cnt++) {
			do {
				clearerr(ci->rx);
				fread(&buffer[cnt], 1, 1, ci->rx);

				/* leave, an alarm has occured during fread */
				if (!ci->rx) {
					dm_free(buffer);
					return 0;
				}
			} while (ferror(ci->rx) && errno == EINTR);

			if (buffer[cnt] == '\n' || feof(ci->rx)
			    || ferror(ci->rx)) {
				buffer[cnt + 1] = '\0';
				break;
			}
		}

		if (feof(ci->rx) || ferror(ci->rx)) {
			/* check client eof  */
			done = -1;
		} else {
			/* reset function handle timeout */
			alarm(0);
			/* handle lmtp commands */
			done =
			    lmtp(ci->tx, ci->rx, buffer, ci->ip, &session);
		}
		fflush(ci->tx);
	}

	/* memory cleanup */
	lmtp_reset(&session);
	dm_free(buffer);
	buffer = NULL;

	/* reset timers */
	alarm(0);
	__debug_dumpallocs();

	return 0;
}

int lmtp_error(PopSession_t * session, void *stream,
	       const char *formatstring, ...)
{
	va_list argp;

	if (session->error_count >= MAX_ERRORS) {
		trace(TRACE_MESSAGE,
		      "lmtp_error(): too many errors (MAX_ERRORS is %d)",
		      MAX_ERRORS);
		ci_write((FILE *) stream,
			"500 Too many errors, closing connection.\r\n");
		session->SessionResult = 2;	/* possible flood */
		lmtp_reset(session);
		return -3;
	} else {
		va_start(argp, formatstring);
		if (vfprintf((FILE *) stream, formatstring, argp) < 0) {
			va_end(argp);
			trace(TRACE_ERROR, "%s,%s: error writing to stream",
			      __FILE__, __func__);
			return -1;
		}
		va_end(argp);
	}

	trace(TRACE_DEBUG, "lmtp_error(): an invalid command was issued");
	session->error_count++;
	return 1;
}


int lmtp(void *stream, void *instream, char *buffer,
	 char *client_ip UNUSED, PopSession_t * session)
{
	/* returns values:
	 *  0 to quit
	 * -1 on failure
	 *  1 on success */
	char *command, *value;
	int cmdtype;
	int indx = 0;

	/* buffer overflow attempt */
	if (strlen(buffer) > MAX_IN_BUFFER) {
		trace(TRACE_DEBUG, "lmtp(): buffer overflow attempt");
		return -3;
	}

	/* check for command issued */
	while (strchr(validchars, buffer[indx]))
		indx++;

	/* end buffer */
	buffer[indx] = '\0';

	trace(TRACE_DEBUG, "lmtp(): incoming buffer: [%s]", buffer);

	command = buffer;

	value = strstr(command, " ");	/* look for the separator */

	if (value != NULL) {
		*value = '\0';	/* set a \0 on the command end */
		value++;	/* skip space */

		if (strlen(value) == 0) {
			value = NULL;	/* no value specified */
		} else {
			trace(TRACE_DEBUG,
			      "lmtp(): command issued :cmd [%s], value [%s]\n",
			      command, value);
		}
	}

	for (cmdtype = LMTP_STRT; cmdtype < LMTP_END; cmdtype++)
		if (strcasecmp(command, commands[cmdtype]) == 0)
			break;

	trace(TRACE_DEBUG, "lmtp(): command looked up as commandtype %d",
	      cmdtype);

	/* commands that are allowed to have no arguments */
	if ((value == NULL) &&
	    !((cmdtype == LMTP_LHLO) || (cmdtype == LMTP_DATA) ||
	      (cmdtype == LMTP_RSET) || (cmdtype == LMTP_QUIT) ||
	      (cmdtype == LMTP_NOOP) || (cmdtype == LMTP_HELP) )) {
		trace(TRACE_ERROR, "ARGUMENT %d", cmdtype);
		return lmtp_error(session, stream,
				  "500 This command requires an argument.\r\n");
	}

	switch (cmdtype) {
	case LMTP_QUIT:
		{
			ci_write((FILE *) stream, "221 %s BYE\r\n",
				 myhostname);
			lmtp_reset(session);
			return 0;	/* return 0 to cause the connection to close */
		}
	case LMTP_NOOP:
		{
			ci_write((FILE *) stream, "250 OK\r\n");
			return 1;
		}
	case LMTP_RSET:
		{
			ci_write((FILE *) stream, "250 OK\r\n");
			lmtp_reset(session);
			return 1;
		}
	case LMTP_LHLO:
		{
			/* Reply wth our hostname and a list of features.
			 * The RFC requires a couple of SMTP extensions
			 * with a MUST statement, so just hardcode them.
			 * */
			ci_write((FILE *) stream,
				 "250-%s\r\n"
				 "250-PIPELINING\r\n"
				 "250-ENHANCEDSTATUSCODES\r\n"
				 /* This is a SHOULD implement:
				  * "250-8BITMIME\r\n"
				  * Might as well do these, too:
				  * "250-CHUNKING\r\n"
				  * "250-BINARYMIME\r\n"
				  * */
				 "250 SIZE\r\n", myhostname);
			lmtp_reset(session);
			return 1;
		}
	case LMTP_HELP:
		{
			int helpcmd;

			if (value == NULL)
				helpcmd = LMTP_END;
			else
				for (helpcmd = LMTP_STRT;
				     helpcmd < LMTP_END; helpcmd++)
					if (strcasecmp
					    (value,
					     commands[helpcmd]) == 0)
						break;

			trace(TRACE_DEBUG,
			      "lmtp(): LMTP_HELP requested for commandtype %d",
			      helpcmd);

			if ((helpcmd == LMTP_LHLO)
			    || (helpcmd == LMTP_DATA)
			    || (helpcmd == LMTP_RSET)
			    || (helpcmd == LMTP_QUIT)
			    || (helpcmd == LMTP_NOOP)
			    || (helpcmd == LMTP_HELP)) {
				ci_write((FILE *) stream, "%s",
					 LMTP_HELP_TEXT[helpcmd]);
			} else {
				ci_write((FILE *) stream, "%s",
					LMTP_HELP_TEXT[LMTP_END]);
			}

			return 1;
		}
	case LMTP_VRFY:
		{
			/* RFC 2821 says this SHOULD be implemented...
			 * and the goal is to say if the given address
			 * is a valid delivery address at this server. */
			ci_write((FILE *) stream,
				"502 Command not implemented\r\n");
			return 1;
		}
	case LMTP_EXPN:
		{
			/* RFC 2821 says this SHOULD be implemented...
			 * and the goal is to return the membership
			 * of the specified mailing list. */
			ci_write((FILE *) stream,
				"502 Command not implemented\r\n");
			return 1;
		}
	case LMTP_MAIL:
		{
			/* We need to LHLO first because the client
			 * needs to know what extensions we support.
			 * */
			if (session->state != LHLO) {
				ci_write((FILE *) stream,
					"550 Command out of sequence.\r\n");
			} else if (list_totalnodes(&from) > 0) {
				ci_write((FILE *) stream,
					"500 Sender already received. Use RSET to clear.\r\n");
				trace(TRACE_ERROR, "%s,%s: Sender already received: %s",
				      __FILE__, __func__, (char *)(list_getstart(&from)->data));
			} else {
				/* First look for an email address.
				 * Don't bother verifying or whatever,
				 * just find something between angle brackets!
				 * */
				int goodtogo = 1;
				size_t tmplen = 0, tmppos = 0;
				char *tmpaddr = NULL, *tmpbody = NULL;

				find_bounded(value, '<', '>', &tmpaddr,
					     &tmplen, &tmppos);

				/* Second look for a BODY keyword.
				 * See if it has an argument, and if we
				 * support that feature. Don't give an OK
				 * if we can't handle it yet, like 8BIT!
				 * */

				/* Find the '=' following the address
				 * then advance one character past it
				 * (but only if there's more string!)
				 * */
				tmpbody = strstr(value + tmppos, "=");
				if (tmpbody != NULL)
					if (strlen(tmpbody))
						tmpbody++;

				/* This is all a bit nested now... */
				if (tmplen < 1 && tmpaddr == NULL) {
					ci_write((FILE *) stream,
						"500 No address found.\r\n");
					goodtogo = 0;
				} else if (tmpbody != NULL) {
					/* See RFC 3030 for the best
					 * description of this stuff.
					 * */
					if (strlen(tmpbody) < 4) {
						/* Caught */
					} else if (0 ==
						   strcasecmp(tmpbody,
							      "7BIT")) {
						/* Sure fine go ahead. */
						goodtogo = 1;	// Not that it wasn't 1 already ;-)
					}
					/* 8BITMIME corresponds to RFC 1652,
					 * BINARYMIME corresponds to RFC 3030.
					 * */
					else if (strlen(tmpbody) < 8) {
						/* Caught */
					} else if (0 ==
						   strcasecmp(tmpbody,
							      "8BITMIME"))
					{
						/* We can't do this yet. */
						/* session->state = BIT8;
						 * */
						ci_write((FILE *) stream,
							"500 Please use 7BIT MIME only.\r\n");
						goodtogo = 0;
					} else if (strlen(tmpbody) < 10) {
						/* Caught */
					} else if (0 ==
						   strcasecmp(tmpbody,
							      "BINARYMIME"))
					{
						/* We can't do this yet. */
						/* session->state = BDAT;
						 * */
						ci_write((FILE *) stream,
							"500 Please use 7BIT MIME only.\r\n");
						goodtogo = 0;
					}
				}

				if (goodtogo) {
					/* Sure fine go ahead. */
					list_nodeadd(&from, tmpaddr, strlen(tmpaddr)+1);
					ci_write((FILE *) stream,
						"250 Sender <%s> OK\r\n",
						(char *)(list_getstart(&from)->data));
				} else {
					if (tmpaddr != NULL)
						dm_free(tmpaddr);
				}
			}
			return 1;
		}
	case LMTP_RCPT:
		{
			if (session->state != LHLO) {
				ci_write((FILE *) stream,
					"550 Command out of sequence.\r\n");
			} else {
				size_t tmplen = 0, tmppos = 0;
				char *tmpaddr = NULL;

				find_bounded(value, '<', '>', &tmpaddr,
					     &tmplen, &tmppos);

				if (tmplen < 1) {
					ci_write((FILE *) stream,
						"500 No address found.\r\n");
				} else {
					/* Note that this is not a pointer, but really is on the stack!
					 * Because list_nodeadd() memcpy's the structure, we don't need
					 * it to live any longer than the duration of this stack frame. */
					deliver_to_user_t dsnuser;

					dsnuser_init(&dsnuser);

					/* find_bounded() allocated tmpaddr for us, and that's ok
					 * since dsnuser_free() will free it for us later on. */
					dsnuser.address = tmpaddr;

					if (dsnuser_resolve(&dsnuser) != 0) {
						trace(TRACE_ERROR,
						      "main(): dsnuser_resolve_list failed");
						ci_write((FILE *) stream,
							"430 Temporary failure in recipient lookup\r\n");
						dsnuser_free(&dsnuser);
						return 1;
					}

					/* Class 2 means the address was deliverable in some way. */
					switch (dsnuser.dsn.class) {
					case DSN_CLASS_OK:
						ci_write((FILE *) stream,
							"250 Recipient <%s> OK\r\n",
							dsnuser.address);
						/* A successfully found recipient goes onto the list.
						 * The struct will be free'd from lmtp_reset(). */
						list_nodeadd(&rcpt, &dsnuser,
							     sizeof(deliver_to_user_t));
						break;
					default:
						ci_write((FILE *) stream,
							"550 Recipient <%s> FAIL\r\n",
							dsnuser.address);
						/* If the user wasn't added, free the non-entry. */
						dsnuser_free(&dsnuser);
						break;
					}
				}
			}
			return 1;
		}
		/* Here's where it gets really exciting! */
	case LMTP_DATA:
		{
			// if (session->state != DATA || session->state != BIT8)
			if (session->state != LHLO) {
				ci_write((FILE *) stream,
					"550 Command out of sequence\r\n");
			} else if (list_totalnodes(&rcpt) < 1) {
				ci_write((FILE *) stream,
					"503 No valid recipients\r\n");
			} else {
				if (list_totalnodes(&rcpt) > 0 && list_totalnodes(&from) > 0) {
					trace(TRACE_DEBUG,
					      "main(): requesting sender to begin message.");
					ci_write((FILE *) stream,
						"354 Start mail input; end with <CRLF>.<CRLF>\r\n");
				} else {
					if (list_totalnodes(&rcpt) < 1) {
						trace(TRACE_DEBUG,
						      "main(): no valid recipients found, cancel message.");
						ci_write((FILE *) stream,
							"503 No valid recipients\r\n");
					}
					if (list_totalnodes(&from) < 1) {
						trace(TRACE_DEBUG,
						      "main(): no sender provided, session cancelled.");
						ci_write((FILE *) stream,
							"554 No valid sender.\r\n");
					}
					return 1;
				}

				/* Anonymous Block */
				{
					char *whole_message = NULL;
					char *header = NULL;
					const char *body;
					u64_t whole_message_size;
					u64_t headersize = 0;
					u64_t headerrfcsize = 0;
					u64_t body_size = 0;
					u64_t body_rfcsize = 0;
					u64_t dummyidx = 0, dummysize = 0;
					struct list headerfields;
					struct element *element;

					if (read_whole_message_network(
						    (FILE *) instream,
						    &whole_message,
						    &whole_message_size,
						    "Return-Path: %s\r\n",
						    (char *)(list_getstart(&from)->data)) < 0) {
						trace(TRACE_ERROR,
						      "%s,%s: read_whole_message_network() failed",
						      __FILE__, __func__);
						discard_client_input((FILE *) instream);
						ci_write((FILE *) stream,
							"500 Error reading message");
						return 1;
					}
					
					trace(TRACE_DEBUG, "%s,%s: whole message = %s",
					    __FILE__, __func__, whole_message);
					if (whole_message == NULL) {
						trace(TRACE_ERROR, "%s,%s message is NULL!",
						    __FILE__, __func__);
						discard_client_input(
							(FILE *) instream);
						ci_write((FILE *) stream,
							"500 Error reading header\r\n");
						return 1;
					}

					if (split_message(whole_message, 
							  whole_message_size - 1,
							  &header,
							  &headersize,
							  &headerrfcsize,
							  &body,
							  &body_size,
							  &body_rfcsize) < 0) {
						trace(TRACE_ERROR, "%s,%s: split_message() failed",
						      __FILE__, __func__);
						dm_free(whole_message);
						discard_client_input((FILE *) instream);
						ci_write((FILE *) stream,
							"500 Error in message");
						return 1;
					}
					if (headersize > READ_BLOCK_SIZE) {
						trace(TRACE_ERROR,
						      "main(): header is too "
						      "big");
						discard_client_input
							((FILE *)
							     instream);
						ci_write((FILE *)
							stream,
							"500 Error reading header, "
							"header too big.\r\n");
						dm_free(whole_message);
						return 1;
					}
					/* Parse the list and scan for field and content.
					 * headerfields is filled here, and needs to be freed. */

					list_init(&headerfields);
					if (mime_readheader(header, &dummyidx,
					                    &headerfields,
					                    &dummysize) < 0) {
						trace(TRACE_ERROR,
						      "main(): fatal error from mime_readheader()");
						discard_client_input((FILE
								      *)
								     instream);
						ci_write((FILE *) stream,
							"500 Error reading header.\r\n");
						dm_free(whole_message);
						return 1;
					}

					if (insert_messages(
						    header, body,
						    headersize, headerrfcsize,
						    body_size, body_rfcsize,
						    &headerfields, &rcpt,
						    &from) == -1) {
						ci_write((FILE *) stream,
							"503 Message not received\r\n");
					} else {
						/* The DATA command itself it not given a reply except
						 * that of the status of each of the remaining recipients. */
						const char *class, *subject, *detail;

						/* The replies MUST be in the order received */
						rcpt.start =
						    dbmail_list_reverse(rcpt.start);

						for (element = list_getstart(&rcpt);
						     element != NULL;
						     element = element->nextnode) {
							deliver_to_user_t * dsnuser =
							    (deliver_to_user_t *) element->data;
							dsn_tostring(dsnuser->dsn, &class, &subject, &detail);

							/* Give a simple OK, otherwise a detailed message. */
							switch (dsnuser->dsn.class) {
								case DSN_CLASS_OK:
									ci_write((FILE *)stream, "%d%d%d Recipient <%s> OK\r\n",
									        dsnuser->dsn.class, dsnuser->dsn.subject, dsnuser->dsn.detail,
									        dsnuser->address);
									break;
								default:
									ci_write((FILE *)stream, "%d%d%d Recipient <%s> %s %s %s\r\n",
									        dsnuser->dsn.class, dsnuser->dsn.subject, dsnuser->dsn.detail,
									        dsnuser->address, class, subject, detail);
							}
						}
					}

					if (header != NULL)
						dm_free(header);
					if (whole_message != NULL)
						dm_free(whole_message);
					list_freelist(&headerfields.start);
				}
				/* Reset the session after a successful delivery;
				 * MTA's like Exim prefer to immediately begin the
				 * next delivery without an RSET or a reconnect. */
				lmtp_reset(session);
			}
			return 1;
		}
	default:
		{
			return lmtp_error(session, stream,
					  "500 What are you trying to say here?\r\n");
		}
	}
	return 1;
}

int read_whole_message_network(FILE *instream,
		char **whole_message, u64_t *whole_message_size,
		const char *prepend_format, ...)
{
	char *tmpmessage = NULL;
	char tmpline[MESSAGE_MAX_LINE_SIZE + 1];
	
	size_t line_size = 0;
	size_t total_size = 0;
	size_t current_pos = 0;
	int error = 0;
	va_list argp;

	memset(tmpline, '\0', MESSAGE_MAX_LINE_SIZE + 1);

	/* This adds the Return-Path header and any other
	 * important headers we might need; see RFC 2076. */
	va_start(argp, prepend_format);
	line_size = vsnprintf(tmpline, MESSAGE_MAX_LINE_SIZE, prepend_format, argp);
	va_end(argp);

	do {
		line_size = strlen(tmpline);
		
		/* It sometimes happens that we read a line of size 0,
		   which is odd.. For now, we just step over it. */
		if (line_size < 2)
			continue;

		/* check for '.\r\n' */
		if (line_size == 3 && strncmp(tmpline, ".\r\n", 3) == 0) 
			break;
		
		/* change the \r\n ending to \n */
		
		if (!(tmpmessage = dm_realloc(tmpmessage, 
					   total_size + line_size - 1))) {
			error = 1;
			break;
		}
		
		if (!(memcpy((void *) &tmpmessage[current_pos], 
			     (void *) tmpline, line_size -2))) {
			error = 1;
			break;
		}
		total_size += line_size - 1;
		current_pos += line_size - 2;
		tmpmessage[current_pos++] = '\n';
				
		memset(tmpline, '\0', MESSAGE_MAX_LINE_SIZE + 1);
	}
	while (fgets(tmpline, MESSAGE_MAX_LINE_SIZE, instream) != NULL);
		
	if (ferror(instream)) {
		trace(TRACE_ERROR, "%s,%s: error reading instream",
		      __FILE__, __func__);
		error = 1;
	}
	if (feof(instream)) {
		trace(TRACE_ERROR, "%s,%s: unexpected EOF in instream",
		      __FILE__, __func__);
		error = 1;
	}

	total_size += 1;
	if (!(tmpmessage = dm_realloc(tmpmessage, total_size))) {
		trace(TRACE_ERROR, "%s.%s: realloc failed",
		      __FILE__, __func__);
		error = 1;
	} else
		tmpmessage[current_pos] = '\0';

	if (error) {
		trace(TRACE_ERROR, "%s,%s: error reading message from "
		      "instream", __FILE__, __func__);
		dm_free(tmpmessage);
		return -1;
	}

	*whole_message = tmpmessage;
	*whole_message_size = total_size;
	return 1;
}



syntax highlighted by Code2HTML, v. 0.9.1