/*
 * $Id: 1c23f0e647307bc5bccc7f353fd289c75d998242 $
 *
 * This file is part of nsmtp.
 *
 * Copyright (c) 2007, 2008 Niklas Olmes <niklas@noxa.de>
 *                          http://www.nevm.de/nsmtp/
 *                          NEVM-RIPE
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2
 * as published by the Free Software Foundation.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <poll.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>

#include <openssl/ssl.h>

#include "alloc.h"
#include "log.h"
#include "smtp.h"
#include "config.h"

#ifndef INFTIM
#define INFTIM -1
#endif

static const char release[] = NSMTP_VERSION;
static const char rcsid[] = "$Id: 1c23f0e647307bc5bccc7f353fd289c75d998242 $";

static void sig_alarm_timeout(int);
static ssize_t smtp_getmany(const SMTP_CTX *ctx, char *, size_t);
static int xpoll(const SMTP_CTX *, int);

static int flag_timeout = 0;

void
init_smtp_sighandlers(void)
{

	flag_timeout = 0;
	(void)signal(SIGALRM, sig_alarm_timeout);
}

SMTP_CTX *
init_smtp_ctx(void)
{
	SMTP_CTX *ctx;

	ctx = nsm_calloc(1, sizeof(SMTP_CTX));

	ctx->server = nsmc_name;
	ctx->conn_id = NULL;
	ctx->client = 0;
	ctx->esmtp = 0;
	ctx->errors = 0;
	ctx->noops = 0;
	ctx->size = 0;
	ctx->helo = ctx->from = ctx->data = NULL;
	ctx->mmap = NULL;
	ctx->mmapfd = -1;
	ctx->rcpts = ctx->defers = NULL;
	ctx->lmid = NULL;
	ctx->exploded = 0;
	ctx->relay = 0;
	ctx->authinfo = ctx->recvdinfo = ctx->sslinfo = ctx->spfinfo = ctx->spfsmtp = NULL;
	ctx->ssl = NULL;
	ctx->s_buf = NULL;
	ctx->s_buf_size = ctx->s_buf_len = ctx->s_buf_off = 0;

	return ctx;
}

void
reset_smtp_ctx(SMTP_CTX *ctx)
{
	int i;

	assert(ctx != NULL);

	if (ctx->from != NULL) {
		nsm_free(ctx->from);
		ctx->from = NULL;
	}
	if (ctx->mmap != NULL && ctx->mmap != MAP_FAILED) {
		(void)munmap(ctx->mmap, MAX_SIZE);
		ctx->mmap = NULL;
		ctx->data = NULL; /* don't even try to free() data if we mmap()ed */
	}
	if (ctx->mmapfd >= 0) {
		(void)close(ctx->mmapfd);
		ctx->mmapfd = -1;
	}
	if (ctx->data != NULL) {
		nsm_free(ctx->data);
		ctx->data = NULL;
	}
	if (ctx->rcpts != NULL) {
		for (i = 0; ctx->rcpts[i] != NULL; i++)
			nsm_free(ctx->rcpts[i]);
		nsm_free(ctx->rcpts);
		ctx->rcpts = NULL;
	}
	if (ctx->defers != NULL) {
		for (i = 0; ctx->defers[i] != NULL; i++)
			nsm_free(ctx->defers[i]);
		nsm_free(ctx->defers);
		ctx->defers = NULL;
	}
	if (ctx->lmid != NULL) {
		nsm_free(ctx->lmid);
		ctx->lmid = NULL;
	}
	if (ctx->spfinfo != NULL) {
		nsm_free(ctx->spfinfo);
		ctx->spfinfo = NULL;
	}
	if (ctx->spfsmtp != NULL) {
		nsm_free(ctx->spfsmtp);
		ctx->spfsmtp = NULL;
	}
}

void
destroy_smtp_ctx(SMTP_CTX *ctx)
{

	assert(ctx != NULL);

	if (ctx->conn_id != NULL) {
		nsm_free(ctx->conn_id);
		ctx->conn_id = NULL;
	}
	if (ctx->helo != NULL) {
		nsm_free(ctx->helo);
		ctx->helo = NULL;
	}
	if (ctx->authinfo != NULL) {
		nsm_free(ctx->authinfo);
		ctx->authinfo = NULL;
	}
	if (ctx->recvdinfo != NULL) {
		nsm_free(ctx->recvdinfo);
		ctx->recvdinfo = NULL;
	}
	if (ctx->ssl != NULL) {
		SSL_free(ctx->ssl);
		ctx->ssl = NULL;
	}
	if (ctx->sslinfo != NULL) {
		nsm_free(ctx->sslinfo);
		ctx->ssl = NULL;
	}
	if (ctx->s_buf != NULL) {
		nsm_free(ctx->s_buf);
		ctx->s_buf = NULL;
	}
	reset_smtp_ctx(ctx);
	nsm_free(ctx);
}

char *
make_mid(const SMTP_CTX *ctx)
{
	char buf[256];

	(void)snprintf(buf, sizeof(buf), "%lu-%ld@%s", (unsigned long)time(NULL), random() % 1000000 + 1, ctx->server);

	return nsm_strdup(buf);
}

char *
make_822time(time_t tloc)
{
	struct tm *t;
	char buf[128];

	if ((t = localtime(&tloc)) == NULL)
		return NULL;

	*buf = '\0';
	if (strftime(buf, sizeof(buf), "%a, %d %b %Y %T %z", t) == 0)
		return NULL;

	return nsm_strdup(buf);
}

int
data_writeout(const SMTP_CTX *ctx, const void *buf, size_t count)
{
	int r;

	r = 0;

	if (flag_timeout)
		return 0;
	(void)alarm(nsmc_smtp_timeout);

	if (ctx->ssl != NULL)
		while (!flag_timeout && (r = SSL_write(ctx->ssl, buf, (int)count)) <= 0) {
			switch (SSL_get_error(ctx->ssl, r)) {
			case SSL_ERROR_WANT_WRITE:
				continue;
			default:
				xfatal("fatal: data_writeout() => internal error: SSL_get_error() returned error code (%d) which we don't recognize", SSL_get_error(ctx->ssl, r));
				/* FALLTHRU */
			case SSL_ERROR_ZERO_RETURN:
			case SSL_ERROR_SYSCALL:
			case SSL_ERROR_SSL:
				r = 0;
				break;
			}
		}
	else
		r = write(ctx->client, buf, count);

	(void)alarm(0);
	if (flag_timeout) {
		flag_timeout = 0;
		return 0;
	}

	return r;
}

int
smtp_writeout(const SMTP_CTX *ctx, const char *fmt, ...)
{
	va_list ap;
	char buf[8192];

	va_start(ap, fmt);
	(void)vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);

	return data_writeout(ctx, buf, strlen(buf));
}

void
smtp_close(SMTP_CTX *ctx)
{

	if (ctx->ssl != NULL) {
		(void)SSL_shutdown(ctx->ssl);
		SSL_free(ctx->ssl);
		ctx->ssl = NULL;
	}
	if (ctx->client != 0)
		(void)close(ctx->client);
	ctx->client = 0;
}

static void
sig_alarm_timeout(int ignore)
{

	(void)ignore;
	flag_timeout = 1;
}

static int
xpoll(const SMTP_CTX *ctx, int in)
{
	struct pollfd p;

	if (ctx->ssl != NULL)
		return 1;

	p.fd = ctx->client;
	p.events = (short)(in ? POLLIN : POLLOUT);

	switch (poll(&p, 1, (int)(1000 * nsmc_smtp_timeout))) {
	case 0:
		return 0;
	case -1:
		return -1;
	default:
		return 1;
	}
}

static ssize_t
smtp_getmany(const SMTP_CTX *ctx, char *buf, size_t size)
{
	ssize_t len;

	if (flag_timeout)
		return 0;
	(void)alarm(nsmc_smtp_timeout);

	len = -1;

	if (ctx->ssl != NULL)
		while (!flag_timeout && (len = SSL_read(ctx->ssl, buf, size)) < 1) {
			switch (SSL_get_error(ctx->ssl, len)) {
			case SSL_ERROR_WANT_READ:
				continue;
			default:
				xfatal("fatal: smtp_getmany() => internal error, SSL_get_error() returned error code (%d) which we don't recognize", SSL_get_error(ctx->ssl, len));
				/* FALLTHRU */
			case SSL_ERROR_ZERO_RETURN:
			case SSL_ERROR_SYSCALL:
			case SSL_ERROR_SSL:
				len = -1;
			}
		}
	else
		if ((len = read(ctx->client, buf, size)) < 1)
			len = -1;

	(void)alarm(0);
	if (flag_timeout) {
		flag_timeout = 0;
		len = -1;
	}

	return len;
}

ssize_t
smtp_getline(SMTP_CTX *ctx, char *buf, size_t size, int dflag)
{
	ssize_t len;
	size_t offset, i, max;

	if (ctx->s_buf == NULL) {
		ctx->s_buf_size = 65536;
		ctx->s_buf = nsm_malloc(sizeof(char) * ctx->s_buf_size);
		ctx->s_buf_len = ctx->s_buf_off = 0;
	}

	max = size > ctx->s_buf_size ? ctx->s_buf_size : size;

	/*
	 * Buffered (pre-read) data?
	 */
	if (ctx->s_buf_len > 0) {
		DEBUG(("smtp_getline() => preread: buffered data (%ld) chars", ctx->s_buf_len));

		i = 0;
		while (i < ctx->s_buf_len && ctx->s_buf[i + ctx->s_buf_off] != '\n')
			i++;

		offset = i + 1;

		if (dflag && i < ctx->s_buf_len && ctx->s_buf[i + ctx->s_buf_off] == '\n')
			i++;

		(void)memcpy(buf, ctx->s_buf + ctx->s_buf_off, i);

		if (!dflag) {
			/* Strip \n (and \r). */
			if (i > 0 && buf[i-1] == '\r')
				i--;
			buf[i] = '\0';
		}

		DEBUG(("smtp_getline() => preread: will return (%ld) chars", i));

		if (ctx->s_buf_len <= offset) {
			DEBUG(("smtp_getline() => preread: zero data left in preread buffer"));
			*ctx->s_buf = '\0';
			ctx->s_buf_len = ctx->s_buf_off = 0;
			return i;
		}
		ctx->s_buf_len -= offset;
		ctx->s_buf_off += offset;

		DEBUG(("smtp_getline() => preread: data left: buffered (%ld) chars", ctx->s_buf_len));

		return i;
	}

	if (xpoll(ctx, 1) <= 0)
		return -1;

	errno = 0;
	if (dflag) {
		if ((len = smtp_getmany(ctx, buf, max)) < 1 || errno == ENOENT) {
			*buf = '\0';
			return -1;
		}
	} else {
		if ((len = smtp_getmany(ctx, buf, max - 1)) < 1 || errno == ENOENT) {
			*buf = '\0';
			return -1;
		}
		buf[len] = '\0';
	}

	/*
	 * Search for newline, if found, break there
	 */
	for (i = 0; i < (size_t)len; i++)
		if (buf[i] == '\n') {
			offset = i + 1;
			if (dflag)
				i++;
			else {
				/* Strip \n (and \r). */
				if (i > 0 && buf[i-1] == '\r')
					i--;
				buf[i] = '\0';
			}

			if ((ctx->s_buf_len = len - offset) > 0) {
				/*
				 * We read too much, buffer rest
				 */
				ctx->s_buf_off = 0;
				(void)memcpy(ctx->s_buf, buf + offset, ctx->s_buf_len);
				DEBUG(("smtp_getline() => data left: buffered (%lu) chars", ctx->s_buf_len));
			}
			DEBUG(("smtp_getline() => newline found, returning (%lu) chars", i));
			return i;
		}

	DEBUG(("smtp_getline() => newline NOT found, returning (%lu) chars", len));
	return len;
}

void
remove_empty_rcpts(char **rcpts)
{
	int i, k;

	k = 0;
	for (i = 0; rcpts[i] != NULL; i++)
		if (*rcpts[i] == '\0') {
			nsm_free(rcpts[i]);
		} else {
			rcpts[k] = rcpts[i];
			k++;
		}
	rcpts[k] = NULL;
}
