/*
** Copyright 1998 - 2000 Double Precision, Inc.  See COPYING for
** distribution information.
*/

#if	HAVE_CONFIG_H
#include	"config.h"
#endif
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<time.h>
#include	<stdio.h>
#include	<errno.h>
#include	<string.h>
#if	HAVE_STRINGS_H
#include	<strings.h>
#endif
#include	<stdlib.h>
#include	<ctype.h>
#include	<pwd.h>
#include	<fcntl.h>
#include	<signal.h>
#include	"rfc2045.h"
#include	"rfc822/rfc2047.h"
#include	"rfc2045charset.h"
#if HAVE_UNISTD_H
#include	<unistd.h>
#endif
#if HAVE_SYS_WAIT_H
#include	<sys/wait.h>
#endif
//#include	"numlib/numlib.h"

#if     HAS_GETHOSTNAME
#else
int gethostname(const char *, size_t);
#endif


static const char rcsid[]="$Id: reformime.c,v 1.1 2005/05/22 20:07:44 jan Exp $";

void rfc2045_error(const char *errmsg)
{
	fprintf(stderr, "reformime: %s\n", errmsg);
	exit(1);
}

static void do_print_structure(struct rfc2045 *p, struct rfc2045id *id, void *ptr)
{
	p=p;
	ptr=p;

	while (id)
	{
		printf("%d%c", id->idnum, id->next ? '.':'\n');
		id=id->next;
	}
}

static int decode_to_file(const char *p, size_t n, void *ptr)
{
FILE	*fp=(FILE *)ptr;

	while (n)
	{
		--n;
		if (putc(*p++, fp) == -1)
		{
			perror("write");
			exit(1);
		}
	}
	return (0);
}

void usage()
{
	fprintf(stderr, "Usage: reformime [options]\n");
	fprintf(stderr, "    -d - parse a delivery status notification.\n");
	fprintf(stderr, "    -e - extract contents of MIME section.\n");
	fprintf(stderr, "    -x - extract MIME section to a file.\n");
	fprintf(stderr, "    -X - pipe MIME section to a program.\n");
	fprintf(stderr, "    -i - show MIME info.\n");
	fprintf(stderr, "    -s n.n.n.n - specify MIME section.\n");
	fprintf(stderr, "    -r - rewrite message, filling in missing MIME header.\n");
	fprintf(stderr, "    -r7 - also convert 8bit/raw encoding to quoted-printable, if possible.\n");
	fprintf(stderr, "    -r8 - also convert quoted-printable encoding to 8bit, if possible.\n");
	fprintf(stderr, "    -c charset - when rewriting, specify default charset.\n");
	fprintf(stderr, "    -m [file] [file]... - create a MIME message digest.\n");
	exit(1);
}

static char *tempname(const char *tempdir)
{
char	pidbuf[NUMBUFSIZE], timebuf[NUMBUFSIZE], hostnamebuf[256];
static unsigned counter=0;
time_t	t;
char	*p;

	str_pid_t(getpid(), pidbuf);
	time(&t);
	str_time_t(t, timebuf);
	hostnamebuf[sizeof(hostnamebuf)-1]=0;
	if (gethostname(hostnamebuf, sizeof(hostnamebuf)))
		hostnamebuf[0]=0;
	p=malloc(strlen(tempdir)+strlen(pidbuf)+strlen(timebuf)+
		strlen(hostnamebuf)+100);
	if (!p)	return (0);
	sprintf(p, "%s/%s.%s-%u.%s", tempdir, timebuf, pidbuf, counter++,
		hostnamebuf);
	return (p);
}

struct rfc2045 *read_message()
{
char	buf[BUFSIZ];
struct	rfc2045 *p=rfc2045_alloc_ac();
FILE	*tempfp=0;

	if (fseek(stdin, 0L, SEEK_END) < 0 ||
		fseek(stdin, 0L, SEEK_SET) < 0)	/* Pipe, save to temp file */
	{
		tempfp=tmpfile();
	}

	while (fgets(buf, sizeof(buf), stdin))
	{
	int l=strlen(buf);

		rfc2045_parse(p, buf, l);
		if (tempfp && fwrite(buf, l, 1, tempfp) != 1)
		{
			perror("fwrite");
			exit(1);
		}
	}

	if (tempfp)	/* Replace stdin */
	{
		close(0);
		dup(fileno(tempfp));
		fclose(tempfp);
	}
	return (p);
}

void print_structure(struct rfc2045 *p)
{
	rfc2045_decode(p, &do_print_structure, 0);
}

static void notfound(const char *p)
{
	fprintf(stderr, "reformime: MIME section %s not found.\n", p);
	exit(1);
}

static void do_print_info(struct rfc2045 *s)
{
const char *content_type, *transfer_encoding, *charset;
off_t start, end, body;
const char *disposition;
const char *disposition_name;
const char *disposition_filename;
const char *content_name;
off_t nlines, nbodylines;
const char *p;

	rfc2045_mimeinfo(s, &content_type, &transfer_encoding, &charset);
	rfc2045_mimepos(s, &start, &end, &body, &nlines, &nbodylines);
	rfc2045_dispositioninfo(s, &disposition, &disposition_name,
		&disposition_filename);
	content_name=rfc2045_contentname(s);
	printf("content-type: %s\n", content_type);
	if (content_name)
	{
	char *s=rfc2047_decode_simple(content_name);

		if (!s)
		{
			perror("rfc2047_decode_simple");
			exit(1);
		}
		printf("content-name: %s\n", s);
		free(s);
	}

	printf("content-transfer-encoding: %s\n", transfer_encoding);
	printf("charset: %s\n", charset);
	if (disposition && *disposition)
		printf("content-disposition: %s\n", disposition);
	if (disposition_name && *disposition_name)
	{
	char *s=rfc2047_decode_simple(disposition_name);

		if (!s)
		{
			perror("rfc2047_decode_simple");
			exit(1);
		}
		printf("content-disposition-name: %s\n", s);
		free(s);
	}

	if (disposition_filename && *disposition_filename)
	{
	char *s=rfc2047_decode_simple(disposition_filename);

		if (!s)
		{
			perror("rfc2047_decode_simple");
			exit(1);
		}
		printf("content-disposition-filename: %s\n", s);
		free(s);
	}

	if (*(p=rfc2045_content_id(s)))
		printf("content-id: <%s>\n", p);
	if (*(p=rfc2045_content_description(s)))
	{
	char *s=rfc2047_decode_simple(p);

		if (!s)
		{
			perror("rfc2047_decode_simple");
			exit(1);
		}
		printf("content-description: %s\n", s);
		free(s);
	}
	if (*(p=rfc2045_content_language(s)))
		printf("content-language: %s\n", p);
	if (*(p=rfc2045_content_md5(s)))
		printf("content-md5: %s\n", p);

	printf("starting-pos: %lu\n", (unsigned long)start);
	printf("starting-pos-body: %lu\n", (unsigned long)body);
	printf("ending-pos: %lu\n", (unsigned long)end);
	printf("line-count: %lu\n", (unsigned long)nlines);
	printf("body-line-count: %lu\n", (unsigned long)nbodylines);
}

static void do_print_info_multiple(struct rfc2045 *p, struct rfc2045id *id,
		void *ptr)
{
	printf("section: ");
	do_print_structure(p, id, ptr);
	do_print_info(p);
	printf("\n");
}

void print_info(struct rfc2045 *p, const char *mimesection)
{
struct	rfc2045 *s;

	if (mimesection)
	{
		s=rfc2045_find(p, mimesection);
		if (!s)
			notfound(mimesection);
		printf("section: %s\n", mimesection);
		do_print_info(s);
		return;
	}
	rfc2045_decode(p, &do_print_info_multiple, 0);
}

static void do_print_section(struct rfc2045 *s, FILE *fp)
{
off_t start, end, body;
off_t nlines;
off_t nbodylines;

	rfc2045_mimepos(s, &start, &end, &body, &nlines, &nbodylines);

	if (fseek(stdin, body, SEEK_SET) == -1)
	{
		perror("fseek");
		exit(1);
	}

	rfc2045_cdecode_start(s, &decode_to_file, fp);
	while (body < end)
	{
	char	buf[BUFSIZ];
	size_t	n=sizeof(buf);

		if ((off_t)n > end-body)	n=end-body;
		n=fread(buf, 1, n, stdin);
		if (n == 0)
		{
			perror("fread");
			exit(1);
		}
		rfc2045_cdecode(s, buf, n);
		body += n;
	}
	rfc2045_cdecode_end(s);
}

void print_decode(struct rfc2045 *p, const char *mimesection)
{
struct	rfc2045 *s;

	if (!mimesection)
		usage();

	s=rfc2045_find(p, mimesection);
	if (!s)
		notfound(mimesection);

	do_print_section(s, stdout);
}

void rewrite(struct rfc2045 *p, int rwmode)
{
	rfc2045_ac_check(p, rwmode);
	if (rfc2045_rewrite(p, fileno(stdin), fileno(stdout),
		"reformime (" RFC2045PKG " " RFC2045VER ")"))
	{
		perror("reformime");
		exit(1);
	}
}

static char *get_suitable_filename(struct rfc2045 *r, const char *pfix,
	int ignore_filename)
{
const char *disposition_s;
const char *disposition_name_s;
const char *disposition_filename_s;
char	*filename_buf;
const char *content_name_s;
char	*p, *q;
char	*dyn_disp_name=0;

	rfc2045_dispositioninfo(r, &disposition_s, &disposition_name_s,
		&disposition_filename_s);

	content_name_s=rfc2045_contentname(r);

	if (!disposition_filename_s || !*disposition_filename_s)
		disposition_filename_s=disposition_name_s;
	if (!disposition_filename_s || !*disposition_filename_s)
		disposition_filename_s=content_name_s;

	filename_buf=rfc2047_decode_simple(disposition_filename_s);

	if (!filename_buf)
	{
		perror("rfc2047_decode_simple");
		exit(1);
	}

	if (strlen(filename_buf) > 32)
	{
		p=filename_buf;
		q=filename_buf + strlen(filename_buf)-32;
		while ( (*p++ = *q++) != 0)
			;
	}

	disposition_filename_s=filename_buf;

	if (ignore_filename)
	{
	char	numbuf[NUMBUFSIZE];
	static size_t counter=0;
	const char *p=str_size_t(++counter, numbuf);

		dyn_disp_name=malloc(strlen(disposition_filename_s)
			+ strlen(p)+2);
		if (!dyn_disp_name)
		{
			perror("malloc");
			exit(1);
		}
		disposition_filename_s=strcat(strcat(strcpy(
			dyn_disp_name, p), "-"),
			disposition_filename_s);
	}
	else if (!disposition_filename_s || !*disposition_filename_s)
	{
		dyn_disp_name=tempname(".");
		disposition_filename_s=dyn_disp_name+2;	/* Skip over ./ */
	}

	p=malloc((pfix ? strlen(pfix):0)+strlen(disposition_filename_s)+1);
	if (!p)
	{
		perror("malloc");
		exit(1);
	}
	*p=0;
	if (pfix)	strcpy(p, pfix);
	q=p+strlen(p);
	for (strcpy(q, disposition_filename_s); *q; q++)
		if (!isalnum(*q) && *q != '.' && *q != '-')
			*q='_';

	if (dyn_disp_name)	free(dyn_disp_name);

	if (!pfix)
	{
        const char *content_type_s;
        const char *content_transfer_encoding_s;
        const char *charset_s;
	int c;
	static char filenamebuf[256];
	char	*t;
	FILE	*tty;

		if ((tty=fopen("/dev/tty", "r+")) == 0)
		{
			perror("/dev/tty");
			exit(1);
		}

		rfc2045_mimeinfo(r, &content_type_s,
			&content_transfer_encoding_s, &charset_s);

		fprintf (tty, "Extract %s? ", content_type_s);
		fflush(tty);
		c=getc(tty);
		if (c != '\n' && c != EOF)
		{
		int	cc;

			while ((cc=getc(tty)) != '\n' && cc != EOF)
				;
		}
		if (c != 'y' && c != 'Y')
		{
			free(p);
			fclose(tty);
			free(filename_buf);
			return (0);
		}
		fprintf (tty, "Filename [%s]: ", p);
		fgets(filenamebuf, sizeof(filenamebuf)-1, tty);
		fclose(tty);
		t=strchr(filenamebuf, '\n');
		if (t)	*t=0;
		else
		{
			fprintf(stderr, "Filename too long.\n");
			exit(1);
		}
		if (filenamebuf[0])
		{
			free(p);
			p=strdup(filenamebuf);
			if (!p)
			{
				perror("malloc");
				exit(1);
			}
		}
	}
	free(filename_buf);
	return (p);
}

static void extract_file(struct rfc2045 *p,
	const char *filename, int argc, char **argv)
{
char	*f;
FILE	*fp;
int	ignore=0;

	for (;;)
	{
	int	fd;

		f=get_suitable_filename(p, filename, ignore);
		if (!f)	return;

		fd=open(f, O_WRONLY|O_CREAT|O_EXCL, 0666);
		if (fd < 0)
		{
			if (errno == EEXIST)
			{
				printf("%s exists.\n", f);
				free(f);
				ignore=1;
				continue;
			}

			perror(f);
			exit(1);
		}
		fp=fdopen(fd, "w");
		if (!fp)
		{
			perror("fdopen");
			exit(1);
		}
		break;
	}

	do_print_section(p, fp);
	if (fflush(fp) || ferror(fp))
	{
		perror("write");
		exit(1);
	}
	fclose(fp);
	free(f);
}

static void extract_pipe(struct rfc2045 *p,
	const char *filename,
	int argc, char **argv)
{
char	*f=get_suitable_filename(p, "FILENAME=", 0);
int	pipefd[2];
pid_t	pid, p2;
FILE	*fp;
int	waitstat;

	if (argc == 0)
	{
		fprintf(stderr, "reformime: Invalid -X option.\n");
		exit(1);
	}

	if (pipe(pipefd))
	{
		perror("pipe");
		exit(1);
	}

	if ((fp=fdopen(pipefd[1], "w")) == 0)
	{
		perror("fdopen");
		exit(1);
	}

	while ((pid=fork()) == -1)
	{
		sleep(2);
	}

	if (pid == 0)
	{
        const char *content_type_s;
        const char *content_transfer_encoding_s;
        const char *charset_s;

		if (!f)	f="FILENAME=attachment.dat";
		putenv(f);
		rfc2045_mimeinfo(p, &content_type_s,
			&content_transfer_encoding_s, &charset_s);
		f=malloc(strlen(content_type_s)
			+sizeof("CONTENT_TYPE="));
		if (!f)
		{
			perror("malloc");
			exit(1);
		}
		strcat(strcpy(f, "CONTENT_TYPE="), content_type_s);
		putenv(f);
		close(0);
		dup(pipefd[0]);
		close(pipefd[0]);
		close(pipefd[1]);
		execv(argv[0], argv);
		perror("exec");
		_exit(1);
	}
	close(pipefd[0]);
	signal(SIGPIPE, SIG_IGN);
	do_print_section(p, fp);
	signal(SIGPIPE, SIG_DFL);
	fclose(fp);
	close(pipefd[1]);

	while ((p2=wait(&waitstat)) != pid && p2 != -1)
		;
	free(f);
}

static void extract_section(struct rfc2045 *top_rfcp, const char *mimesection,
	const char *extract_filename, int argc, char **argv,
	void	(*extract_func)(struct rfc2045 *, const char *,
		int, char **))
{
	if (mimesection)
	{
		top_rfcp=rfc2045_find(top_rfcp, mimesection);
		if (!mimesection)
			notfound(mimesection);
		if (top_rfcp->firstpart)
		{
			fprintf(stderr, "reformime: MIME section %s is a compound section.\n", mimesection);
			exit(1);
		}
		(*extract_func)(top_rfcp, extract_filename, argc, argv);
		return;
	}

	/* Recursive */

	if (top_rfcp->firstpart)
	{
		for (top_rfcp=top_rfcp->firstpart; top_rfcp;
			top_rfcp=top_rfcp->next)
			extract_section(top_rfcp, mimesection,
				extract_filename, argc, argv, extract_func);
		return;
	}

	if (!top_rfcp->isdummy)
		(*extract_func)(top_rfcp, extract_filename, argc, argv);
}

static void print_dsn_recip(char *addr, char *action)
{
char *p, *q;

	if (!action || !addr)
	{
		if (action)	free(action);
		if (addr)	free(addr);
		return;
	}

	for (p=action; *p; ++p)
		*p=tolower((int)(unsigned char)*p);

	for (p=addr; *p && isspace((int)(unsigned char)*p); ++p)
		;

	if (strncasecmp(p, "rfc822;", 7))
	{
		free(action);
		free(addr);
		return;
	}
	for (q=action; *q && isspace((int)(unsigned char)*q); ++q)
		;
	p += 7;
	while (*p && isspace((int)(unsigned char)*p))
		++p;
	printf("%s %s\n", q, p);
	free(action);
	free(addr);
}

static void dsn(struct rfc2045 *p, int do_orig)
{
const char *content_type_s;
const char *content_transfer_encoding_s;
const char *charset_s;
off_t start_pos, end_pos, start_body;
off_t dummy;
const char *q;
char	buf[BUFSIZ];
unsigned i;
int	ch;
char *recip;
char *action;
char *orecip;

	rfc2045_mimeinfo(p, &content_type_s, &content_transfer_encoding_s,
		&charset_s);
	if (strcasecmp(content_type_s, "multipart/report") ||
		(q=rfc2045_getattr(p->content_type_attr, "report-type")) == 0 ||
		strcasecmp(q, "delivery-status") ||
		!p->firstpart || !p->firstpart->next ||
		!p->firstpart->next->next)
		_exit(1);
	p=p->firstpart->next->next;
	rfc2045_mimeinfo(p, &content_type_s, &content_transfer_encoding_s,
		&charset_s);
	rfc2045_mimepos(p, &start_pos, &end_pos, &start_body, &dummy, &dummy);
	if (strcasecmp(content_type_s, "message/delivery-status") ||
		fseek(stdin, start_body, SEEK_SET) == -1)
		_exit(1);

	i=0;
	recip=0;
	orecip=0;
	action=0;
	while (start_body < end_pos)
	{
		if ((ch=getchar()) == EOF)	break;
		++start_body;
		if (i < sizeof(buf)-1)
			buf[i++]= ch;
		if (ch != '\n')	continue;
		ch=getchar();
		if (ch != EOF)	ungetc(ch, stdin);
		if (ch != '\n' && isspace((int)(unsigned char)ch))
			continue;
		buf[i-1]=0;
		if (buf[0] == 0)
		{
			if (orecip)
			{
				if (recip)	free(recip);
				recip=orecip;
				orecip=0;
			}
			print_dsn_recip(recip, action);
			recip=0;
			action=0;
		}
		if (strncasecmp(buf, "Final-Recipient:", 16) == 0 &&
			recip == 0)
		{
			recip=strdup(buf+16);
			if (!recip)
			{
				perror("strdup");
				exit(2);
			}
		}
		if (strncasecmp(buf, "Original-Recipient:", 19) == 0 &&
			orecip == 0 && do_orig)
		{
			orecip=strdup(buf+19);
			if (!orecip)
			{
				perror("strdup");
				exit(2);
			}
		}
		if (strncasecmp(buf, "Action:", 7) == 0 && action == 0)
		{
			action=strdup(buf+7);
			if (!action)
			{
				perror("strdup");
				exit(2);
			}
		}
		i=0;
	}
	if (orecip)
	{
		if (recip)	free(recip);
		recip=orecip;
		orecip=0;
	}
	print_dsn_recip(recip, action);
}

static void mimedigest1(int, char **);
static char mimebuf[BUFSIZ];

static void mimedigest(int argc, char **argv)
{
char	*p;
struct filelist { struct filelist *next; char *fn; } *first=0, *last=0;
unsigned pcnt=0;
char	**l;

	if (argc > 0)
	{
		mimedigest1(argc, argv);
		return;
	}

	while (fgets(mimebuf, sizeof(mimebuf), stdin))
	{
	struct	filelist *q;

		if ((p=strchr(mimebuf, '\n')) != 0)	*p=0;
		q=malloc(sizeof(struct filelist));
		if (!q || !(q->fn=strdup(mimebuf)))
		{
			perror("malloc");
			exit(1);
		}

		if (last)	last->next=q;
		else	first=q;
		last=q;
		q->next=0;
		++pcnt;
	}
	if (pcnt == 0)	return;

	if ( (l=malloc(sizeof (char *) * pcnt)) == 0)
	{
		perror("malloc");
	}
	pcnt=0;

	for (last=first; last; last=last->next)
		l[pcnt++]=last->fn;

	mimedigest1(pcnt, l);
}

static void mimedigest1(int argc, char **argv)
{
time_t	t;
char	boundarybuf[200];
unsigned boundarycnt=0;
int	i;
FILE	*fp;

	time (&t);

	/* Search for a suitable boundary */

	do
	{
	int	l;

		sprintf(boundarybuf, "reformime_%lu_%u",
			(unsigned long)t, ++boundarycnt);

		l=strlen(boundarybuf);

		for (i=0; i<argc; i++)
		{
		int	err=0;

			if ((fp=fopen(argv[i], "r")) == 0)
			{
				perror(argv[i]);
				exit(1);
			}

			while (fgets(mimebuf, sizeof(mimebuf), fp))
			{
				if (mimebuf[0] != '-' || mimebuf[1] != '-')
					continue;

				if (strncasecmp(mimebuf+2, boundarybuf, l) == 0)
				{
					err=1;
					break;
				}
			}
			fclose(fp);
			if (err)	break;
		}
	} while (i < argc);
		
	printf("Mime-Version:1.0\n"
		"Content-Type: multipart/digest; boundary=\"%s\"\n\n%s",
			boundarybuf, RFC2045MIMEMSG);

	for (i=0; i<argc; i++)
	{
		if ((fp=fopen(argv[i], "r")) == 0)
		{
			perror(argv[i]);
			exit(1);
		}

		printf("\n--%s\nContent-Type: message/rfc822\n\n",
			boundarybuf);

		while (fgets(mimebuf, sizeof(mimebuf), fp))
			printf("%s", mimebuf);
		fclose(fp);
	}

	printf("\n--%s--\n", boundarybuf);
}

int main(int argc, char **argv)
{
int	argn;
char	optc;
char	*optarg;
char	*mimesection=0;
int	doinfo=0, dodecode=0, dorewrite=0, dodsn=0, domimedigest=0;
struct	rfc2045 *p;
int	rwmode=0;
void	(*do_extract)(struct rfc2045 *, const char *, int, char **)=0;
const char *extract_filename=0;

	for (argn=1; argn<argc; )
	{
		if (argv[argn][0] != '-')	break;
		optarg=0;
		optc=argv[argn][1];
		if (optc && argv[argn][2])	optarg=argv[argn]+2;
		++argn;
		switch	(optc)	{
		case 'c':
			if (!optarg && argn < argc)
				optarg=argv[argn++];
			if (optarg && *optarg)
				rfc2045_setdefaultcharset(optarg);
			break;

		case 's':
			if (!optarg && argn < argc)
				optarg=argv[argn++];
			if (optarg && *optarg)	mimesection=optarg;
			break;
		case 'i':
			doinfo=1;
			break;
		case 'e':
			dodecode=1;
			break;
		case 'r':
			dorewrite=1;
			if (optarg && *optarg == '7')
				rwmode=RFC2045_RW_7BIT;
			if (optarg && *optarg == '8')
				rwmode=RFC2045_RW_8BIT;
			break;
		case 'm':
			domimedigest=1;
			break;
		case 'd':
			dodsn=1;
			break;
		case 'D':
			dodsn=2;
			break;
		case 'x':
			do_extract=extract_file;
			if (optarg)
				extract_filename=optarg;
			break;
		case 'X':
			do_extract=extract_pipe;
			break;
		case 'v':
			printf("%s\n", rcsid);
			exit(0);
		default:
			usage();
		}
	}

	if (domimedigest)
	{
		mimedigest(argc-argn, argv+argn);
		return (0);
	}

	p=read_message();

	if (doinfo)
		print_info(p, mimesection);
	else if (dodecode)
		print_decode(p, mimesection);
	else if (dorewrite)
		rewrite(p, rwmode);
	else if (dodsn)
		dsn(p, dodsn == 2);
	else if (do_extract)
		extract_section(p, mimesection, extract_filename,
			argc-argn, argv+argn, do_extract);
	else
		print_structure(p);
	rfc2045_free(p);
	exit(0);
	return (0);
}
