/*
    Copyright (C) 1999,2003 by Wolfgang Zekoll <wzk@quietsche-entchen.de>
 
    This software 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.
 */


#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <errno.h>
#include <syslog.h>
#include <sys/wait.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>

#include "smtp.h"
#include "acp.h"
#include "lib.h"



static struct _tmpfile {
    char	filename[200];
    struct _tmpfile *next;
    } *lasttmp = NULL;

static int count =		0;
static int pid =		0;

static char prefix[200] =	"";


static int unlink_existing(char *filename)
{
	struct stat sbuf;

	if (stat(filename, &sbuf) == 0) {
		if (unlink(filename) != 0)
			return (1);
		}
	
	return (0);
}


static void cleanup(void)
{
	struct stat sbuf;
	struct _tmpfile *tmp;

	if (pid == 0  ||  pid != getpid())
		return;

	tmp = lasttmp;
	while (tmp != NULL) {
		if (stat(tmp->filename, &sbuf) == 0) {
			unlink(tmp->filename);
			}

		/*
		 * cleanup() is called on program termination
		 * so we do not need to free the memory.
		 */

		tmp = tmp->next;
		}

	return;
}

static int init_tmplist()
{
	char	*p;
	struct _tmpfile *tmp, *next;

	if (pid == 0) {
		pid = getpid();
		atexit(cleanup);

		if ((p = getenv("TMPPREFIX")) == NULL  ||  *p == 0)
			strcpy(prefix, "smtp-");
		else
			copy_string(prefix, p, sizeof(prefix));
		}
	else if (pid != getpid()) {

		/*
		 * we are in a child process - let's erase the parent's
		 * tempfile list and create our own.
		 */

		pid     = getpid();
		count   = 0;

		tmp = lasttmp;
		while (tmp != NULL) {
			next = tmp->next;
			free(tmp);

			tmp = next;
			}
			
		lasttmp = NULL;
		if (atexit(cleanup) != 0) {
			syslog(LOG_NOTICE, "-ERR: can't register cleaup()");
			exit (1);
			}
		}
		
	return (0);
}

char *gettmpfile(char *filename, int size)
{
	int	fd;
	struct _tmpfile *tmp;

	init_tmplist();
	if ((tmp = malloc(sizeof(struct _tmpfile))) == NULL) {
		syslog(LOG_NOTICE, "-ERR: memory allocation error");
		exit (1);
		}
		
	snprintf (tmp->filename, sizeof(tmp->filename) - 2, "/tmp/%s%05d.%03d.tmp",
			prefix, pid, count++);
	
	tmp->next = lasttmp;
	lasttmp = tmp;

	unlink_existing(tmp->filename);
	if ((fd = open(tmp->filename, O_CREAT | O_WRONLY | O_TRUNC, 0600)) < 0) {
		syslog(LOG_NOTICE, "-ERR: can't open tmpfile: %s, error= %s", tmp->filename, strerror(errno));
		exit (1);
		}

	close (fd);
	if (filename != NULL)
		copy_string(filename, tmp->filename, size);

	return (tmp->filename);
}


int receivemail(smtp_t *x)
{
	char	*p, line[2048];
	FILE	*fp;

	if ((fp = fopen(x->spoolfile, "w")) == NULL) {
		syslog(LOG_NOTICE, "can't open spoolfile: %s, error= %s", x->spoolfile, strerror(errno));
		return (1);
		}

	while (1) {
		if (cfgets(x, line, sizeof(line), x->config->timeout) == NULL) {
			syslog(LOG_NOTICE, "client terminated while sending data");
			return (-1);
			}

		p = noctrl(line);
		if (*p == '.') {
			p++;
			if (*p == 0)
				break;
			}

		fprintf (fp, "%s\n", p);
		}

	fclose (fp);
	return (0);
}


static int setvar(smtp_t *x, char *var, char *value)
{
	char	varname[200];

	snprintf (varname, sizeof(varname) - 2, "%s%s", x->config->varname, var);
	setenv(varname, value != NULL? value: "", 1);

	return (0);
}

static int set_variables(smtp_t *x)
{
	int	c, i;
	char	val[200];

	setvar(x, "SERVER", x->servername);

	setvar(x, "CLIENT", x->ipnum);
	setvar(x, "CLIENTNAME", x->client);

	setvar(x, "ORIGINALSENDER", x->origsender);
	setvar(x, "SENDER", x->sender);

	snprintf (val, sizeof(val) - 2, "%d", x->nrcpt);
	setvar(x, "NRCPT", val);
	setvar(x, "RCPT", x->rcpt.list);

	/*
	 * Export the recipient list as rc/akanga compatible list.  Since
	 * we are in a forked process we can overwrite some data.
	 */

	for (i=0; (c = x->rcpt.list[i]) != 0; i++) {
		if (c == ' ')
			x->rcpt.list[i] = '\001';
		}

	setvar(x, "RCPTV", x->rcpt.list);

	return (0);
}

static int set_ccpvars(smtp_t *x)
{
	char	val[80];
	unsigned long size;
	struct stat sbuf;

	setvar(x, "MAIL", x->spoolfile);

	size = 0;
	if (stat(x->spoolfile, &sbuf) == 0)
		size = sbuf.st_size;

	snprintf (val, sizeof(val) - 2, "%ld", size);
	setvar(x, "SIZE", val);

	return (0);
}

static char *readline(int fd, char *line, int size)
{
	int	len;

	*line = 0;
	
	if ((len = read(fd, line, size - 2)) < 0)
		len = 0;

	line[len] = 0;
	noctrl(line);

	close(fd);
	return (line);
}

static int getstatus(smtp_t *x, int rc, char *line, int size)
{
	int	status;

	status = atoi(line);
	if (status == 0)
		*line = 0;
	else if (status == 250  ||  status == 354) {
		if (rc != 0)
			*line = 0;
		}
	else if (status >= 400  &&  status < 600)
		/* ok */ ;
	else {
		/*
		 * Everything else is an invalid response code.
		 */

		copy_string(line, "421 server error", size);
		return (0);
		}

	if (*line == 0) {
		if (rc == 0)
			copy_string(line, "250 ok", size);
		else if (rc == 1)
			copy_string(line, "451 processing aborted", size);
		else
			copy_string(line, "554 mail rejected", size); 
		}

	return (0);
}

int run_acp(smtp_t *x, char *line, int size)
{
	int	rc, pid, pfd[2], lfd[2];

	/*
	 * Do nothing if the acp is not set.
	 */

	if (*x->config->acp == 0) {
		copy_string(line, "250 ok", size);
		return (250);
		}


	rc = 0;
	if (pipe(pfd) != 0  ||  pipe(lfd)) {
		syslog(LOG_NOTICE, "-ERR: can't pipe: %m");
		exit (1);
		}
	else if ((pid = fork()) < 0) {
		syslog(LOG_NOTICE, "-ERR: can't fork ccp: %m");
		exit (1);
		}
	else if (pid == 0) {
		int	argc;
		char	*argv[32];

		dup2(pfd[1], 2);	/* stderr nach SMTP Client */
		close(pfd[0]);

		dup2(lfd[1], 1);	/* stdout nach syslog */
		close(lfd[0]);

		close(0);
		set_variables(x);

		copy_string(line, x->config->acp, size);
		argc = split(line, argv, ' ', 30);
		argv[argc] = NULL;
		execvp(argv[0], argv);

		syslog(LOG_NOTICE, "-ERR: can't exec acp %s: %s", argv[0], strerror(errno));
		exit (1);
		}
	else {
		/*
		 * Nicht gebrauchte fd's schliessen.
		 */

		close(pfd[1]);
		close(lfd[1]);

		readline(lfd[0], line, size);
		if (*line != 0)
			syslog(LOG_NOTICE, "%s", line);



		readline(pfd[0], line, size);
		if (waitpid(pid, &rc, 0) < 0) {
			syslog(LOG_NOTICE, "-ERR: error while waiting for ccp: %m");
			exit (1);
			}

		rc = WIFEXITED(rc) != 0? WEXITSTATUS(rc): 1;
		getstatus(x, rc, line, size);
		}

	return (0);
}

int run_ccp(smtp_t *x, char *line, int size)
{
	int	rc, pid, pfd[2], lfd[2];

	/*
	 * Wenn kein ccp angegeben ist ist alles erlaubt.
	 */

	if (*x->config->ccp == 0) {
		copy_string(line, "250 ok", size);
		return (250);
		}


	rc = 0;
	if (pipe(pfd) != 0  ||  pipe(lfd)) {
		syslog(LOG_NOTICE, "-ERR: can't pipe: %m");
		exit (1);
		}
	else if ((pid = fork()) < 0) {
		syslog(LOG_NOTICE, "-ERR: can't fork ccp: %m");
		exit (1);
		}
	else if (pid == 0) {
		int	argc;
		char	*argv[32];

		dup2(pfd[1], 2);	/* stderr nach SMTP Client */
		close(pfd[0]);

		dup2(lfd[1], 1);	/* stdout nach syslog */
		close(lfd[0]);

		close(0);
		set_variables(x);
		set_ccpvars(x);

		copy_string(line, x->config->ccp, size);
		argc = split(line, argv, ' ', 30);
		argv[argc] = NULL;
		execvp(argv[0], argv);

		syslog(LOG_NOTICE, "-ERR: can't exec ccp %s: %s", argv[0], strerror(errno));
		exit (1);
		}
	else {
		/*
		 * Nicht gebrauchte fd's schliessen.
		 */

		close(pfd[1]);
		close(lfd[1]);


		/*
		 * syslog Meldung lesen und entsprechende pipe schliessen.
		 */

/*		*line = 0;
 *		if ((len = read(lfd[0], line, size - 2)) < 0)
 *			len = 0;
 *
 *		line[len] = 0;
 *		noctrl(line);
 *		close(lfd[0]);
 */

		readline(lfd[0], line, size);
		if (*line != 0)
			syslog(LOG_NOTICE, "%s", line);



		/*
		 * Fehlermeldung lesen, pipe schliessen.
		 */

/*		*line = 0;
 *		if ((len = read(pfd[0], line, size - 2)) < 0)
 *			len = 0;
 *
 *		line[len] = 0;
 *		noctrl(line);
 *		close(pfd[0]);
 */

		readline(pfd[0], line, size);


		/*
		 * return code holen.
		 */

		if (waitpid(pid, &rc, 0) < 0) {
			syslog(LOG_NOTICE, "-ERR: error while waiting for ccp: %m");
			exit (1);
			}

		rc = WIFEXITED(rc) != 0? WEXITSTATUS(rc): 1;
		getstatus(x, rc, line, size);
		}

	return (0);
}



syntax highlighted by Code2HTML, v. 0.9.1