/*
 *   cddbd - CD Database Protocol Server
 *
 *   Copyright (C) 1996-1997  Steve Scherf
 *   Email: steve@moonsoft.com
 *   Moondog Software Productions - makers of fine public domain software.
 *
 *   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.
 *
 */

#ifndef LINT
static char *_mail_c_ident_ = "@(#)$Id: mail.c,v 1.6 1997/09/14 03:43:44 steve Exp steve $";
#endif

#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include "list.h"
#include "crc.h"
#include "cddbd.h"


/* Preprocessor definitions. */
#define SCAN_ADDR	0
#define SCAN_NAME	1
#define SCAN_MISC	2


/* Prototypes. */
int cddbd_submit(FILE *, email_hdr_t *, int, char *);
int cddbd_email_cmd(FILE *, email_hdr_t *, int, char *);
int get_charset(char *, email_hdr_t *, char *);
int get_encoding(char *, email_hdr_t *, char *);
int parse_header(FILE *, email_hdr_t *, char *);
int return_mail(FILE *, email_hdr_t *, int, char *);
int scan_from(char *, char *, int);

void cpy_sender(char *, char *, char *, char *);

FILE *get_body(FILE *, email_hdr_t *, char *, char *);


/* Global variables. */
char *boundary = "boundary";
char *charset = "charset";
char *content_encoding = "Content-Transfer-Encoding";
char *content_len = "Content-Length";
char *content_type = "Content-Type";
char *expires = "Expires";
char *from = "From";
char *mime_ver = "Mime-Version";
char *multi_alt = "multipart/alternative";
char *rpath = "Return-Path";
char *subj = "Subject";
char *text_plain = "text/plain";
char *to = "To";
char *x_cddbd_crc = "X-Cddbd-CRC";
char *x_cddbd_from = "X-Cddbd-From";
char *x_cddbd_echo = "X-Cddbd-Echo";
char *x_cddbd_note = "X-Cddbd-Note";
char *x_sender = "X-Sender";

charset_t char_types[] = {
	"us-ascii",		0,
	"iso-8859",		CT_8BIT,
	"iso-8859-1",		CT_8BIT,
	"iso-8859-2",		CT_8BIT,
	"x-iso8859",		CT_8BIT,
	"x-iso88591",		CT_8BIT,
	"x-iso88592",		CT_8BIT,
	"x-roman-8",		CT_8BIT,
	{ 0 }
};

char *note = "Note: %s\n\n";
char *sub_rej_subj = "Rejected CDDB submission";
char *sub_send_subj = "cddb %255s %08x";
char *test_rej_subj = "Rejected CDDB test submission";
char *test_ok_subj = "Successful CDDB test submission";
char *cmd_send_subj = "cddb #command %16s";
char *cmd_send_subj2 = "cddb #command %.16s";
char *cmd_ok_subj = "cddb #response ok %s";
char *cmd_rej_subj = "cddb #response failed %s";
char *gen_rej_subj = "Rejected CDDB email";
char *gen_incl = "> %s\n";
char *gen_incl_blank = ">\n";
char *null_incl = "%s\n";
char *null_incl_blank = "\n";

char *test_ok_body[] = {
	"Your CDDB test submission was accepted.\n",
	"\n",
	"The test submission follows:\n",
	0
};

char *test_ok_sig[] = {
	"\n",
	"Response generated by the CDDB daemon.\n",
	"\n",
	0
};

char *sub_rej_body[] = {
	"Your CDDB submission was rejected for the following reason:\n",
	"\n",
	"%s\n",
	"\n",
	"Please fix the problem before you resubmit it. Only the first\n",
	"error in your submission was noted - there may be others. If you\n",
	"continue to have trouble, it may be a due to a bug in your CD\n"
	"player software. If you suspect this, try acquiring a newer version.",
	"\n\n",
	"The rejected submission follows:\n",
	0
};

char *gen_rej_body[] = {
	"Your mail to the CDDB daemon failed for the following reason:\n",
	"\n",
	"%s\n",
	"\n",
	"Please fix the problem before you resend it. If you continue to\n",
	"have trouble, it may be a due to a bug in the software used to\n",
	"send the mail.",
	"\n\n",
	"The rejected mail follows:\n",
	0
};

char *gen_rej_sig[] = {
	"\n",
	"If you need assistance, send email to: %s\n",
	"\n",
	"Response generated by the CDDB daemon.\n",
	"\n",
	0
};


char *start_type[] = {
	"<",
	"(\"",
	""
};


char *end_type[] = {
	">",
	")\"",
	"<(\""
};


void
cddbd_mail(flags)
{
	int ret;
	FILE *fp;
	FILE *tfp;
	email_hdr_t eh;
	char *tmail;
	char *tmail2;
	char buf[CDDBBUFSIZ];
	char errstr[CDDBBUFSIZ];

	errstr[0] = '\0';

	/* Create a temporary file for the mail. */
	tmail = cddbd_mktemp();

	if((fp = fopen(tmail, "w+")) == NULL) {
		cddbd_snprintf(buf, sizeof(buf),
		    "Can't open mail tmp file %s (%d)", tmail, errno);

		cddbd_log(LOG_ERR | LOG_MAIL, "%s", buf);

		/* Print to stderr so the bounced mail gets it. */
		fprintf(stderr, "%s\n", buf);

		quit(QUIT_ERR);
	}

	/* Put the mail into the temp file. */
	while(fgets(buf, sizeof(buf), stdin) != NULL) {
		if(fputs(buf, fp) == EOF) {
			cddbd_snprintf(buf, sizeof(buf),
			    "Can't write mail tmp file %s (%d)", tmail, errno);

			cddbd_log(LOG_ERR | LOG_MAIL, "%s", buf);

			/* Print to stderr so the bounced mail gets it. */
			fprintf(stderr, "%s\n", buf);

			quit(QUIT_ERR);
		}
	}

	/* Put the pointer back to the start of the file for reading. */
	rewind(fp);
	ret = parse_header(fp, &eh, errstr);

	tmail2 = cddbd_mktemp();

	if(ret == EE_OK && (tfp = get_body(fp, &eh, tmail2, errstr)) == NULL)
		ret = EE_ERROR;

	if(ret == EE_OK) {
		switch(eh.eh_class) {
		case EC_SUBMIT:
			ret = cddbd_submit(tfp, &eh, flags, errstr);

			/* Give back happy mail. */
			if(ret == EE_OK && (flags & MF_TEST)) {
				ret = return_mail(fp, &eh, flags, errstr);

				if(ret != EE_OK) {
					cddbd_log(LOG_ERR | LOG_MAIL,
					    "Couldn't send test response.");
				}
			}

			break;

		case EC_COMMAND:
			ret = cddbd_email_cmd(tfp, &eh, flags, errstr);
			break;

		case EC_NONE:
		default:
			ret = EE_ERROR;
			break;
		}
	}

	if(ret != EE_OK) {
		if(eh.eh_flags & EH_RCPT)
			/* We have a sender, give him mail directly. */
			ret = return_mail(fp, &eh, (flags | MF_FAIL), errstr);

		if(ret != EE_OK) {
			/* We have no sender, give him mail indirectly. */
			cddbd_snprintf(buf, sizeof(buf),
			    "Unable to process email: %s", errstr);

			cddbd_log(LOG_ERR | LOG_MAIL, "%s", buf);

			/* Print to stderr so the bounced mail gets it. */
			fprintf(stderr, "%s\n", buf);
		}
	}

	fclose(fp);
	fclose(tfp);

	cddbd_freetemp(tmail);
	cddbd_freetemp(tmail2);

	if(ret == EE_OK)
		quit(QUIT_OK);
	else
		quit(QUIT_ERR);
}


int
cddbd_submit(FILE *fp, email_hdr_t *eh, int flags, char *errstr)
{
	int i;
	int len;
	int ret;
	int dbflags;
	db_t *db;
	char buf[CDDBBUFSIZ];
	char buf2[CDDBBUFSIZ];
	struct stat sbuf;

	/* Set the interface type. */
	interface = IF_SUBMIT;

	if(!(flags & MF_TEST))
		hperm = *ck_host_perms(interface);

	if((ret = validate_email(fp, eh, errstr)) != EE_OK)
		return ret;

	if(!WRITE_OK(hperm) && !(flags & MF_TEST)) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "Email submissions disallowed");

		return EE_ERROR;
	}

	if(categ_index(eh->eh_category) < 0) {
		cddbd_snprintf(errstr, CDDBBUFSIZ, "Invalid DB category: %s\n"
		    "Valid categories are:", eh->eh_category);

		for(i = 0, len = strlen(errstr); categlist[i] != 0; i++) {
			cddbd_snprintf(&errstr[len], (CDDBBUFSIZ - len),
			    " %s", categlist[i]);
			len += strlen(categlist[i]) + 1;
		}

		return EE_ERROR;
	}

	dbflags = 0;

	if(!(flags & MF_TEST))
		dbflags |= DF_CK_SUBMIT;

	rewind(fp);

	db = db_read(fp, buf, (DF_MAIL | DF_SUBMITTER | dbflags));
	if(db == 0) {
		cddbd_snprintf(errstr, CDDBBUFSIZ, "Invalid DB submission: %s",
		    buf);

		return EE_ERROR;
	}

	if(!(flags & MF_TEST)) {
		/* Check for postdir, and create if it doesn't exist. */
		if(stat(postdir, &sbuf)) {
			if(mkdir(postdir, (mode_t)db_dir_mode)) {
				cddbd_log(LOG_ERR,
				    "Failed to create post dir %s.", postdir);

				cddbd_snprintf(errstr, CDDBBUFSIZ,
				   "Internal server file error");

				return EE_ERROR;
			}

			(void)cddbd_fix_file(postdir, db_dir_mode, db_uid,
			    db_gid);
		}
		else if(!S_ISDIR(sbuf.st_mode)) {
			cddbd_log(LOG_ERR, "%s is not a directory.", postdir);

			cddbd_snprintf(errstr, CDDBBUFSIZ,
			    "Internal server file error");

			return EE_ERROR;
		}

		cddbd_snprintf(buf2, sizeof(buf), "%s/%s", postdir,
		    eh->eh_category);

		if(!db_post(db, buf2, eh->eh_discid, buf)) {
			if(db_errno != DE_INVALID) {
				cddbd_snprintf(errstr, CDDBBUFSIZ,
				    "Internal DB server error: %s", buf);
			}
			else
				cddbd_snprintf(errstr, CDDBBUFSIZ,
				    "Invalid DB file: %s", buf);

			return EE_ERROR;
		}

		cddbd_log(LOG_INFO | LOG_WRITE,
		    "Write (via SMTP - %s): %s %08x", eh->eh_to,
		    eh->eh_category, eh->eh_discid);
	}
	else {
		cddbd_log(LOG_INFO, "Test email submission (from %s): %s %08x",
		    eh->eh_to, eh->eh_category, eh->eh_discid);
	}

	return EE_OK;
}


int
cddbd_email_cmd(FILE *fp, email_hdr_t *eh, int flags, char *errstr)
{
	int ret;
	int found;
	pid_t f;
	char *tcmd;
	char buf[CDDBCMDLEN];
	struct stat sbuf;

	/* Set the interface type. */
	interface = IF_EMAIL;

	hperm = *ck_host_perms(interface);

	if(flags & MF_TEST) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "Email commands not accepted at this address");

		return EE_ERROR;
	}

	if((ret = validate_email(fp, eh, errstr)) != EE_OK)
		return ret;

	found = 0;
	rewind(fp);

	while(fgets(buf, sizeof(buf), fp))
		if(!strncmp(buf, asy_prefix[0], strlen(asy_prefix[0]))) {
			found++;
			break;
		}

	if(!found) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "No CDDBP command found in mail body");

		return EE_ERROR;
	}

	/* Create a temporary file for the mail. */
	tcmd = cddbd_mktemp();

	if((fp = fopen(tcmd, "w+")) == NULL) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "Internal server error: can't open temp file %s", tcmd);

		return EE_ERROR;
	}

	f = cddbd_fork();

	/* We're the child. Do the command. */
	if(f == 0) {
		dup2(fileno(fp), 1);
		dup2(fileno(fp), 2);
		fclose(fp);
		close(0);

		_quit(cddbd_async_command(buf), 0);
	}

	if(f < 0) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "Internal server error: can't fork");

		fclose(fp);
		cddbd_freetemp(tcmd);

		return EE_ERROR;
	}

	if(wait(0) == -1 && errno != ECHILD) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "Internal server error: failed to wait on child (%d)",
		    errno);

		fclose(fp);
		cddbd_freetemp(tcmd);

		return EE_ERROR;
	}

	if(stat(tcmd, &sbuf)) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "Internal server error: can't stat output file %s (%d)",
		    tcmd, errno);

		fclose(fp);
		cddbd_freetemp(tcmd);

		return EE_ERROR;
	}

	/* Put something in the file if it's empty. */
	if(sbuf.st_size == 0) {
		fprintf(fp, "403 Server error.\n");
		fflush(fp);
	}

	ret = return_mail(fp, eh, (flags | MF_ENC), errstr);
	if(ret != EE_OK) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "Internal server error: can't generate response mail");
	}

	fclose(fp);
	cddbd_freetemp(tcmd);

	/* Reset the interface type so we don't log a second hello. */
	interface = IF_SUBMIT;

	return ret;
}


int
validate_email(FILE *fp, email_hdr_t *eh, char *errstr)
{
	uint32_t crc;
	uint32_t mcrc;
	ct_key_t *key;

	if(eh->eh_flags & EH_CRC) {
		if(PASSWD_REQ(hperm)) {
			if((key = getpasswd(hperm.hp_pwdlbl)) == NULL) {
				cddbd_snprintf(errstr, CDDBBUFSIZ,
				    "Validation string lookup failed");

				return EE_ERROR;
			}
		}
		else
			key = 0;

		if(crc_email(&crc, fp, eh->eh_rcpt, eh->eh_subj, errstr) !=
		    EE_OK)
			return EE_ERROR;

		mcrc = strtocrc(eh->eh_crc, 0, key);

#if 1
		if(crc != mcrc) {
#else
		if(key && crc != mcrc) {
#endif
#if 1
			cddbd_snprintf(errstr, CDDBBUFSIZ,
			    "CRC in email (%08X) differs from expected CRC "
			    "(%08X)", mcrc, crc);

			return EE_ERROR;
#else
			cddbd_log(LOG_ERR | LOG_PASSWD,
			    "CRC in email (%08X) differs from expected CRC "
			    "(%08X)", mcrc, crc);
#endif
		}

		hperm.hp_passwd = HP_PASSWD_OK;

		return EE_OK;
	}
	else if(hperm.hp_passwd == HP_PASSWD_REQ) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "Required CRC missing from email header");

		return EE_ERROR;
	}

	return EE_OK;
}


int
crc_email(uint32_t *crc, FILE *fp, char *rcpt, char *subj, char *errstr)
{
	uint32_t len;
	uint32_t tcrc[3];

	rewind(fp);

	if(crc32(CRC_FILE, fp, &tcrc[0], &len) == -1 ||
	    crc32(CRC_STRING, rcpt, &tcrc[1], &len) == -1 ||
	    crc32(CRC_STRING, subj, &tcrc[2], &len) == -1) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "Can't calculate email CRC (%d)", errno);

		return EE_ERROR;
	}

	*crc = tcrc[0] ^ tcrc[1] ^ tcrc[2];

	return EE_OK;
}


FILE *
get_body(FILE *fp, email_hdr_t *eh, char *tmail, char *errstr)
{
	int len;
	int found;
	FILE *tfp;
	char *p;
	char buf[CDDBBUFSIZ];
	int (*efunc)();

	if((tfp = fopen(tmail, "w+")) == NULL) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "Can't open mail tmp file %s (%d)", tmail, errno);

		return NULL;
	}

	efunc = encoding_types[eh->eh_encoding].en_decode;
	if(efunc != 0 && (efunc)(EO_START, 0, 0, 0) != EN_OK) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "Failed to initiate email decoding.");

		return NULL;
	}

	found = 0;

	/* Put the mail into the temp file. */
	while(fgets(buf, sizeof(buf), fp) != NULL) {
		if((eh->eh_flags & EH_BOUNDARY) &&
		    strstr(buf, eh->eh_boundary) != NULL) {
			found++;
			break;
		}

		len = strlen(buf);

		if(efunc != 0) {
			if((efunc)(EO_DECODE, buf, &p, &len) != EN_OK) {
				cddbd_snprintf(errstr, CDDBBUFSIZ,
				    "Failed to perform email decoding.");

				return NULL;
			}
		}
		else
			p = buf;

		if(len != 0 && fwrite(p, 1, len, tfp) != len) {
			cddbd_snprintf(errstr, CDDBBUFSIZ,
			    "Can't write mail tmp file %s (%d)",
			    tmail, errno);

			return NULL;
		}
	}

	if(efunc != 0 && (efunc)(EO_END, 0, 0, 0) != EN_OK) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "Failed to terminate email decoding.");

		return NULL;
	}

	if((eh->eh_flags & EH_BOUNDARY) && !found) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "End boundary not found in mail.");

		return NULL;
	}

	fflush(tfp);
	rewind(tfp);

	return tfp;
}


int
parse_header(FILE *fp, email_hdr_t *eh, char *errstr)
{
	int len;
	int cont;
	int blank;
	int header;
	int gotsubj;
	int gotenc;
	int gotset;
	int waitbound;
	char *p;
	char *s;
	char buf[CDDBBUFSIZ];
	char buf2[CDDBBUFSIZ];
	char set[CDDBBUFSIZ];
	char enc[CDDBBUFSIZ];

	cont = 0;
	header = 0;
	gotenc = 0;
	gotset = 0;
	gotsubj = 0;
	waitbound = 0;

	eh->eh_flags = 0;
	eh->eh_class = EC_NONE;
	eh->eh_charset = CC_US_ASCII;
	eh->eh_encoding = CE_7BIT;

	while(fgets(buf2, sizeof(buf2), fp) != NULL) {
		if(cont) {
			len = sizeof(buf) - strlen(buf);
			if(len > 1) {
				strncat(buf, buf2, len);
				buf[sizeof(buf) - 1] = '\0';
			}

			blank = 0;
		}
		else {
			strcpy(buf, buf2);
			blank = is_blank(buf, 0);
		}

		cont = 0;

		p = &buf[strlen(buf) - 1];
		while(p >= buf && (is_crlf(*p) || is_wspace(*p)))
			p--;

		if(p >= buf && *p == ';') {
			*(p + 1) = '\0';
			strip_crlf(p);
			cont++;
			continue;
		}

		if(!strncmp(buf, from, strlen(from)) ||
		    !strncmp(buf, x_cddbd_from, strlen(rpath)) ||
		    (!header && !strncmp(buf, rpath, strlen(rpath)))) {
			p = buf;
			while(*p != '\0' && !isspace(*p))
				p++;
			while(*p != '\0' && isspace(*p))
				p++;

			if(*p != '\0') {
				cpy_sender(p, eh->eh_rcpt, eh->eh_to,
				    eh->eh_host);

				eh->eh_flags |= EH_RCPT;
				header++;
			}

			continue;
		}

		if(!header)
			continue;

		if(!strncmp(buf, subj, strlen(subj))) {
			p = buf;
			while(*p != '\0' && !isspace(*p))
				p++;
			while(*p != '\0' && isspace(*p))
				p++;

			if(sscanf(p, cmd_send_subj, eh->eh_serial))
				eh->eh_class = EC_COMMAND;
			else if(sscanf(p, sub_send_subj, eh->eh_category,
			    &eh->eh_discid) == 2)
				eh->eh_class = EC_SUBMIT;
			else {
				strcpy(errstr, "Malformed subject in header");
				return EE_ERROR;
			}

			strip_crlf(p);
			strncpy(eh->eh_subj, p, sizeof(eh->eh_subj));
			eh->eh_subj[sizeof(eh->eh_subj) - 1] = '\0';

			gotsubj++;
			eh->eh_flags |= EH_SUBJECT;
		}
		else if(!cddbd_strncasecmp(buf, x_cddbd_crc,
		    strlen(x_cddbd_crc))) {
			/* Skip junk. */
			p = buf + strlen(x_cddbd_crc) + 1;
			while(*p != '\0' && !isxdigit(*p))
				p++;

			/* Find the crc. */
			if(sscanf(p, "%255[a-zA-Z0-9]", buf2) != 1 ||
			    strlen(buf2) != CDDBXCRCLEN) {
				cddbd_snprintf(errstr, CDDBBUFSIZ,
				    "Malformed %s in header", x_cddbd_crc);

				return EE_ERROR;
			}

			strcpy(eh->eh_crc, buf2);
			eh->eh_flags |= EH_CRC;
		}
		else if(!cddbd_strncasecmp(buf, x_cddbd_echo,
		    strlen(x_cddbd_echo))) {
			/* Skip junk. */
			p = buf + strlen(x_cddbd_echo) + 1;
			while(*p != '\0' && isspace(*p))
				p++;

			/* Find the echo string. */
			if(sscanf(p, "%255[^\n\r]", buf2) != 1 ||
			    strlen(buf2) > CDDBXECHOLEN) {
				cddbd_snprintf(errstr, CDDBBUFSIZ,
				    "Invalid %s in header", x_cddbd_echo);

				return EE_ERROR;
			}

			strcpy(eh->eh_echo, buf2);
			eh->eh_flags |= EH_ECHO;
		}
		else if(!cddbd_strncasecmp(buf, x_cddbd_note,
		    strlen(x_cddbd_note))) {
			/* Skip junk. */
			p = buf + strlen(x_cddbd_note) + 1;
			while(*p != '\0' && isspace(*p))
				p++;

			/* Find the note string. */
			if(sscanf(p, "%255[^\n\r]", buf2) != 1 ||
			    strlen(buf2) > CDDBXNOTELEN) {
				cddbd_snprintf(errstr, CDDBBUFSIZ,
				    "Invalid %s in header", x_cddbd_note);

				return EE_ERROR;
			}

			strcpy(eh->eh_note, buf2);
			eh->eh_flags |= EH_NOTE;
		}
		else if(!cddbd_strncasecmp(buf, content_type,
		    strlen(content_type))) {
			if((p = cddbd_strcasestr(buf, charset)) != 0) {
				/* Skip junk. */
				p += strlen(charset);
				while(*p != '\0' && !isalpha(*p) &&
				    !isdigit(*p))
					p++;

				/* Find the charset. */
				if(sscanf(p, "%255[a-zA-Z0-9_-]", set) != 1) {
					cddbd_snprintf(errstr, CDDBBUFSIZ,
					    "Malformed %s in header",
					    content_type);
					return EE_ERROR;
				}

				gotset++;
			}

			if((p = cddbd_strcasestr(buf, boundary)) != 0) {
				/* Skip junk. */
				p += strlen(boundary);
				while(*p != '\0') {
					if(*p == '=') {
						p++;
						break;
					}
					p++;
				}

				if(*p ==  '"') {
					p++;
					s = "%255[^\"]";
				}
				else
					s = "%255s";

				/* Find the boundary string. */
				if(sscanf(p, s, eh->eh_boundary) != 1) {
					cddbd_snprintf(errstr, CDDBBUFSIZ,
					    "Malformed %s in header",
					    content_type);
					return EE_ERROR;
				}

				len = strlen(eh->eh_boundary);
				if(eh->eh_boundary[len - 1] == '"' ||
				    eh->eh_boundary[len - 1] == ';')
					eh->eh_boundary[len - 1] = '\0';

				eh->eh_flags |= EH_BOUNDARY;

				waitbound++;
			}
		}
		else if(!cddbd_strncasecmp(buf, content_encoding,
		    strlen(content_encoding))) {
			/* Skip junk. */
			p = buf + strlen(content_encoding);
			while(*p != '\0' && !isalpha(*p) && !isdigit(*p))
				p++;

			/* Find the encoding. */
			if(sscanf(p, "%[a-zA-Z0-9_-]", enc) != 1) {
				cddbd_snprintf(errstr, CDDBBUFSIZ,
				    "Malformed %s in header", content_encoding);

				return EE_ERROR;
			}

			gotenc++;
		}
		else if(waitbound && strstr(buf, eh->eh_boundary) != NULL) {
			waitbound = 0;
		}
		else if(blank && !waitbound) {
			get_rmt_hostname(-1, eh->eh_host, rhost);

			if(!gotsubj) {
				strcpy(errstr, "Missing subject in header");
				return EE_ERROR;
			}

			if(gotenc && !get_encoding(enc, eh, errstr))
				return EE_ERROR;

			if(gotset && !get_charset(set, eh, errstr))
				return EE_ERROR;

			return EE_OK;
		}
	}

	if(waitbound)
		strcpy(errstr, "Missing boundary string");
	else
		strcpy(errstr, "Malformed email header");

	return EE_ERROR;
}


int
get_encoding(char *encoding, email_hdr_t *eh, char *errstr)
{
	int i;

	/* Look for the encoding in the supported list. */
	for(i = 0; encoding_types[i].en_type != 0; i++)
		if(!cddbd_strcasecmp(encoding, encoding_types[i].en_type))
			break;

	/* Unknown encoding. */
	if(encoding_types[i].en_type == 0) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "Unsupported %s in header: %s", content_encoding,
		    encoding);

		return 0;
	}

	eh->eh_encoding = i;
	return 1;
}


int
get_charset(char *set, email_hdr_t *eh, char *errstr)
{
	int i;

	/* Look for the charset in the supported list. */
	for(i = 0; char_types[i].ct_name != 0; i++)
		if(!cddbd_charcasecmp(set, char_types[i].ct_name))
			break;

	/* Unknown charset. */
	if(char_types[i].ct_name == 0) {
		cddbd_snprintf(errstr, CDDBBUFSIZ,
		    "Unsupported %s in header: %s", charset, set);

		return 0;
	}

	eh->eh_charset = i;
	return 1;
}


void
cpy_sender(char *str, char *rcpt, char *to, char *fhost)
{
	int gotaddr;
	int gotname;
	int gotmisc;
	char *p;
	char *pa;
	char *pn;
	char addr[CDDBBUFSIZ];
	char name[CDDBBUFSIZ];
	char misc[CDDBBUFSIZ];
	char host[CDDBBUFSIZ];

	/* Scan the string for the name and address. */
	if(scan_from(str, addr, SCAN_ADDR))
		gotaddr = 1;
	else
		gotaddr = 0;

	if(scan_from(str, name, SCAN_NAME))
		gotname = 1;
	else
		gotname = 0;

	if((!gotaddr || !gotname) && scan_from(str, misc, SCAN_MISC))
		gotmisc = 1;
	else
		gotmisc = 0;


	if(gotaddr) {
		pa = addr;
	}
	else if(gotmisc) {
		pa = misc;
		gotaddr = 1;
		gotmisc = 0;
	}

	if(gotname) {
		pn = name;
	}
	else if(gotmisc) {
		pn = misc;
		gotname = 1;
	}

	/* We couldn't parse the string, use the raw "from". */
	if(!gotaddr) {
		strcpy(rcpt, str);

		pn = &rcpt[strlen(rcpt) - 1];
		if(*pn == '\n')
			*pn = '\0';

		strcpy(to, rcpt);
		strcpy(host, "unknown");

		return;
	}

	/* Copy the relevant parts. */
	cddbd_snprintf(rcpt, CDDBBUFSIZ, "%s", pa);

	if(gotname)
		cddbd_snprintf(to, CDDBBUFSIZ, "%s (%s)", pa, pn);
	else
		cddbd_snprintf(to, CDDBBUFSIZ, "%s", pa);

	/*
	 * Get the hostname from the address. If it's a complicated
	 * address, this may goof up. If there's no discernible host we 
	 * must assume it's because there isn't one, so call it "localhost".
	 */
	if((p = strrchr(pa, '@')) != NULL)
		cddbd_snprintf(fhost, CDDBBUFSIZ, "%s", (p + 1));
	else if((p = strchr(pa, '!')) != NULL) {
		*p = '\0';
		cddbd_snprintf(fhost, CDDBBUFSIZ, "%s", pa);
	}
	else if((p = strrchr(pa, '%')) != NULL)
		cddbd_snprintf(fhost, CDDBBUFSIZ, "%s", (p + 1));
	else
		strcpy(fhost, LHOST);
}


int
scan_from(char *str, char *buf, int type)
{
	char *p;
	char *p1;

	/* Skip leading white space. */
	p = str;
	while(*p != '\0' && isspace(*p))
		p++;

	/* Scan for a beginning marker. */
	while(*p != '\0' && !is_instr(*p, start_type[type]))
		p++;

	if(*p == '\0')
		return 0;

	/* Strip off marker, if there is one. */
	if(*start_type[type] != '\0')
		p++;

	p1 = buf;

	/* Copy in relevant portion of the string. */
	while(*p != '\0' && !is_instr(*p, end_type[type])) {
		*p1 = *p;
		p++;
		p1++;
	}

	/* Strip off trailing white space. */
	p1--;
	while(isspace(*p1))
		p1--;
	p1++;
	*p1 = '\0';
	
	return 1;
}


int
return_mail(FILE *fp, email_hdr_t *eh, int flags, char *errstr)
{
	int i;
	int len;
	FILE *nfp;
	char *p;
	char *incl;
	char *bincl;
	char *subj;
	char **sig;
	char **body;
	char *copy_addr;
	char *rtn_addr;
	char *tret;
	char *echo;
	char buf[CDDBBUFSIZ];
	char subj_buf[CDDBBUFSIZ];
	struct stat sbuf;

	/* Create a temporary file for the mail. */
	tret = cddbd_mktemp();

	if(flags & MF_FAIL) {
		strncpy(buf, errstr, sizeof(buf));
		buf[sizeof(buf) - 1] = '\0';

		if((p = (char *)strchr(buf, '\n')) != NULL)
			*p = '\0';
	}

	/* Return address is currently always the admin. */
	rtn_addr = admin_email;

	switch(eh->eh_class) {
	case EC_COMMAND:
		/* Command response. */
		if(flags & MF_FAIL) {
			cddbd_log(LOG_ERR | LOG_MAIL,
			    "Email command failed: %s", buf);

			cddbd_snprintf(subj_buf, sizeof(subj_buf),
			    cmd_rej_subj, eh->eh_serial);

			sig = gen_rej_sig;
			body = gen_rej_body;
			incl = gen_incl;
			bincl = gen_incl_blank;
			copy_addr = bounce_email;
		}
		else {
			cddbd_snprintf(subj_buf, sizeof(subj_buf),
			    cmd_ok_subj, eh->eh_serial);

			sig = 0;
			body = 0;
			incl = null_incl;
			bincl = null_incl_blank;
			copy_addr = 0;
		}

		subj = subj_buf;

		break;

	case EC_SUBMIT:
		/* We only send mail for rejections and test submissions. */
		if(flags & MF_FAIL) {
			if(flags & MF_TEST) {
				cddbd_log(LOG_ERR | LOG_MAIL,
				    "Test email submission failed: %s", buf);

				subj = test_rej_subj;
			}
			else {
				cddbd_log(LOG_ERR | LOG_MAIL,
				    "Email submission failed: %s", buf);

				subj = sub_rej_subj;
			}

			sig = gen_rej_sig;
			body = sub_rej_body;
			copy_addr = bounce_email;
		}
		else {
			sig = test_ok_sig;
			body = test_ok_body;
			subj = test_ok_subj;
			copy_addr = test_email;
		}

		incl = gen_incl;
		bincl = gen_incl_blank;

		break;

	default:
		subj = gen_rej_subj;
		body = gen_rej_body;
		sig = gen_rej_sig;
		incl = gen_incl;
		bincl = gen_incl_blank;
		copy_addr = bounce_email;

		break;
	}

	if(eh->eh_flags & EH_ECHO)
		echo = eh->eh_echo;
	else
		echo = 0;

	if((nfp = fopen(tret, "w+")) == NULL) {
		cddbd_log(LOG_ERR | LOG_MAIL,
		    "Can't create mail tmp file %s (%d)", tret, errno);

		return EE_ERROR;
	}

	/* Write out the note string. */
	if(eh->eh_flags & EH_NOTE) {
		if(fprintf(nfp, note, eh->eh_note) == EOF) {
			cddbd_log(LOG_ERR | LOG_MAIL,
			    "Can't write mail tmp file %s (%d)",
			    tret, errno);

			fclose(nfp);
			cddbd_freetemp(tret);

			return EE_ERROR;
		}
	}

	/* Write out the err string. */
	for(i = 0; body && body[i]; i++) {
		if(fprintf(nfp, body[i], errstr) == EOF) {
			cddbd_log(LOG_ERR | LOG_MAIL,
			    "Can't write mail tmp file %s (%d)",
			    tret, errno);

			fclose(nfp);
			cddbd_freetemp(tret);

			return EE_ERROR;
		}
	}

	rewind(fp);

	/* Write out the mail body. */
	while(fgets(buf, sizeof(buf), fp) != NULL) {
		strip_crlf(buf);

		if(is_blank(buf, 0))
			p = bincl;
		else
			p = incl;

		if(fprintf(nfp, p, buf) == EOF) {
			cddbd_log(LOG_ERR | LOG_MAIL,
			    "Can't write mail tmp file %s (%d)", tret, errno);

			fclose(nfp);
			cddbd_freetemp(tret);

			return EE_ERROR;
		}
	}

	/* Write out the err string. */
	for(i = 0; sig && sig[i]; i++) {
		if(fprintf(nfp, sig[i], admin_email) == EOF) {
			cddbd_log(LOG_ERR | LOG_MAIL,
			    "Can't write mail tmp file %s (%d)", tret, errno);

			fclose(nfp);
			cddbd_freetemp(tret);

			return EE_ERROR;
		}
	}

	fflush(nfp);

	if(fstat(fileno(nfp), &sbuf))
		len = 0;
	else
		len = sbuf.st_size;

	rewind(nfp);

	/* Figure out if we need to encode the mail. */
	if(!(flags & (MF_ENC | MF_MULTI))) {
		while(fgets(buf, sizeof(buf), nfp) != NULL) {
			if(is_rfc_1521_mappable((unsigned char *)buf, 1, 0)) {
				flags |= MF_ENC;
				break;
			}
		}
	}

	rewind(nfp);

	if(!smtp_open()) {
		fclose(nfp);
		cddbd_freetemp(tret);
		return EE_ERROR;
	}

	if(!smtp_transmit(nfp, subj, eh->eh_rcpt, eh->eh_to, rtn_addr, echo,
	    flags, len, 0)) {
		fclose(nfp);
		cddbd_freetemp(tret);
		return EE_ERROR;
	}

	if(copy_addr != 0 && copy_addr[0] != '\0') {
		rewind(nfp);
		cddbd_snprintf(buf, sizeof(buf), "%s (fwd)", subj);

		if(!smtp_transmit(nfp, buf, copy_addr, copy_addr, rtn_addr, 
		    echo, flags, len, 0)) {
			fclose(nfp);
			cddbd_freetemp(tret);
			return EE_ERROR;
		}
	}

	smtp_close();
	fclose(nfp);
	cddbd_freetemp(tret);

	return EE_OK;
}
