/*
** Mailto v1.3.2
**
** A WWW FORMs to Mail gateway
**
** Copyright (c) 10.6.94 by Andreas Ley <ley@rz.uni-karlsruhe.de>
**
** Permission to use, copy, modify, and distribute this software for any
** purpose and without fee is hereby granted, provided that the above
** copyright notice appears in all copies. This software is provided "as is"
** and without any express or implied warranties.
**
** This program has been tested on a HP9000/715 with HP-UX A.09.05
** In this environment, neither lint -u nor gcc -Wall produce any messages.
** If you encounter any errors or need to make any changes to port it
** to another platform, please contact me.
**
** Usage:
**    - Since WWW is anonymous, mail sent through the WWW Mail Gateway
**	is anonymous, too. Some people don't like to get anonymous mail,
**	that's why mailto can be used with an access configuration file,
**	which specifies who is enabled to get mail through the WWW Mail
**	Gateway. If you don't need access control, just undefine ADDRESSES,
**	otherwise point it to a configuration file with entries:
**		<address> (<realname>)
**	<realname> is optional.
**    - Calling /cgi-bin/mailto/<address>/<subject>/<url> gives you a template
**	mail FORM. <address> is the recipients email address, <subject> is
**	a predefined subject and <url> is the URL of an acknowledgement page.
**	Any of the parameters may be skipped; since address and subject
**	are required, mailto will generate input fields if they're missing.
**    - Create your own customized/beautified FORM with METHOD=POST and
**	ACTION=/cgi-bin/mailto/<address>/<subject>/<url>. As above, <address>
**	is the recipients email address, <subject> is a predefined subject and
***	<url> is the URL of an acknowledgement page. You can replace any of
**	these parameters by INPUTs with NAME= "To", "Subject" or "Acknowledge"
**	respectivly, which may also be of TYPE=hidden (recommended for the
**	acknowledgement URL). These INPUTs will be treated special, as will
**	be NAME="From" (NAME="Reply-To" provides the same functionality but
**	is obsolete and it's use is discouraged), NAME="Cc" and
**	NAME="Copy-Self". They all have precedence over the URL parameters. Use
**	of the INPUTs is recommended.
**	When the FORM is submitted, the <address> (or the VALUE of the
**	NAME="To" field) will be checked against the configuration file, the
**	contents will be converted into a mail message and the message will be
**	sent to <address> (<realname>) with <subject> (or the VALUE of the
**	NAME="Subject" field) as the subject. Input fields will be listed as
**	"<name>: <content><LF>" for one-line-contents and as
**	"<name>:<LF><content><LF><content><LF>.<LF>" for multi-line-contents
**	with leading periods preceeded by another period.
**	If there is a field with NAME="Cc", a carbon copy of the mail message
**	will be sent to the addresses listed in its VALUE.
**	If an INPUT with TYPE="checkbox" and NAME="Copy-Self" has been checked,
**	a copy of the message will be sent to the originator of the message
**	as specified in the NAME="From" (or NAME="Reply-To") field.
**	If <url> (or a field with NAME="Acknowledge") was specified, it will
**	be sent back as a confirmation to the sender.
**
** Version history
**
** Version 1.3.2 - 2.3.1999
**	Conditional error-to.
**
** Version 1.3.1 - 2.9.1998
**	Fixed carbon copy option.
**
** Version 1.3 - 13.8.96
**	Option to send a carbon copy. (Not yet working)
**
** Version 1.2.7 - 12.4.96
**	Added random number (pid) code to subject parsing
**
** Version 1.2.6 - 30.1.96
**	Fixed address checking bug with addresses without realnames
**
** Version 1.2.5 - 29.1.96
**	Added debugging output
**	Fixed some possible references to null-pointers
**
** Version 1.2.4 - 25.1.96
**	Added support for machines lacking strerror(3C)
**
** Version 1.2.3 - 27.9.95
**	Gateway identifies with Sender: instead of From:
**	User address now specified in From: field, Reply-To alias for From
**
** Version 1.2.2 - 5.9.95
**	Added <TAB> to the address file separators
**
** Version 1.2.1 - 2.8.95
**	X- postfix for Addr, Host, Ident and User headers.
**
** Version 1.2 - 1.6.95
**	Option to send a copy of the generated mail to oneself.
**	Fixed bug when client sends <CR><LF> instead of <LF> only.
**
** Version 1.1.1 - 23.1.95
**	Option to show generated mail in automatic acknowledgement page.
**
** Version 1.1 - 19.1.95
**	Added evaluation of INPUT fields for To, Subject and Acknowledge.
**	Default FORM now generates INPUT fields instead of URL parameters.
**
** Version 1.0.2 - 23.8.94
**	Fixed location bug.
**	Supplies default FORM on method GET now.
**
** Version 1.0.1 - 10.6.94
**	Fixed documentation bug.
**
** Version 1.0 - 10.6.94
**	Initial version
**
** Thanx for bug reports, ideas and fixes to
**	anr@ime.usp.br (Adriano R.)
**	pall@rz.uni-karlsruhe.de (Michael Pall)
**	otisg@cobalt.middlebury.edu (Otis Gospodnetic)
**
** A current version of mailto can be retrieved from
**	ftp://ftp.rz.uni-karlsruhe.de/pub/net/www/tools/cgi-src/mailto.tar.gz
*/

char header[]="mailto v1.3.2\n(c) 1994-1998 by Andreas Ley (Andreas.Ley@rz.uni-karlsruhe.de)\n";

/* General definitions */

#define	MASTER	"webmaster@rz.uni-karlsruhe.de"
#define	SENDER	"www@rz.uni-karlsruhe.de (" TITLE ")"
#define	ERRORS_TO	"null@rz.uni-karlsruhe.de"
#define	SENDMAIL_SMTP	/* There's no other method yet */
#define	FULL_ACKNOWLEDGE	/* Define this if you want an automatic full acknowledgement page */
/* #define	NEED_STRERROR	/* Define this if you don't have strerr */

/* File locations */

/*
#define	ADDRESSES	"/usr/local/etc/httpd/config/mailto.conf"
*/

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
/*
#include "mailto_german.h"
*/
#include "mailto_english.h"

#ifdef SENDMAIL_SMTP
#define	MAILCMD	"/usr/lib/sendmail -bs >/dev/null"
#endif /* SENDMAIL_SMTP */

#ifdef NEED_STRERROR
extern int	sys_nerr;
extern char	*sys_errlist[];
#define	strerror(errno)	((errno)>=sys_nerr?"Undefined error":sys_errlist[errno])
#endif /* NEED_STRERROR */

#define	FALSE	0		/* This is the naked Truth */
#define	TRUE	1		/* and this is the Light */

/* Functions from util.c */

char *makeword(char *line, char stop);
char *fmakeword(FILE *f, char stop, int *len);
void unescape_url(char *url);
void plustospace(char *str);

/* Global variables */

char	master[]=MASTER;
char	*htag[1024],*hval[1024];
int	max,debug=0;


/* Prints header of a HTML-Request */
void show_header(char *subtitle, char *description)
{
	char	*ptr;

	if (debug)
		(void)fprintf(stderr,"show_header(\"%s\",\"%s\")\n",subtitle,description);
	while (ptr=strchr(header,'\n'))
		*ptr=' ';
	(void)printf("Content-type: text/html\n\n");
	(void)printf("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n");
	(void)printf("<!-- %s-->\n",header);
	(void)printf("<HTML>\n");
	(void)printf("<HEAD>\n");
	(void)printf("<TITLE>%s%s%s</TITLE>\n",TITLE,subtitle?" - ":"",subtitle?subtitle:"");
	(void)printf("<LINK REV=\"made\" HREF=\"mailto:%s\">\n",master);
	(void)printf("</HEAD>\n");
	(void)printf("<BODY>\n");
	(void)printf("<H1>%s</H1>\n",subtitle?subtitle:TITLE);
	if (description) {
		(void)printf(description,master);
		(void)printf("<P>\n");
	}
}



/* Prints trailer of a HTML-Request */
void show_trailer()
{
	if (debug)
		(void)fprintf(stderr,"show_trailer()\n");
	(void)printf("</BODY>\n");
	(void)printf("</HTML>\n");
}



/* Shows an error page, based on wrong user input */
void show_error(char *error)
{
	if (debug)
		(void)fprintf(stderr,"show_error(\"%s\")\n",error);
	show_header(ERROR,error);
	show_trailer();
	exit(0);
}



/* Shows an error page, based on internal misfunction */
void show_fatal(char *error)
{
	if (debug)
		(void)fprintf(stderr,"show_fatal(\"%s\")\n",error);
	show_header(ERROR,error);
	(void)printf(ERROR_FATAL,master);
	(void)printf("<P>\n");
	show_trailer();
	exit(0);
}



/* Returns a Location: header */
void show_location(char *location)
{
	if (debug)
		(void)fprintf(stderr,"show_location(\"%s\")\n",location);
	(void)printf("Location: %s\n",location);
	(void)printf("URI: %s\n\n",location);
}



int tagindex(char **htag, int max, char *tagname)
{
	int	cnt;

	if (debug)
		(void)fprintf(stderr,"tagindex(0x%08lx,08lx,\"%s\")\n",htag,max,tagname);
	for (cnt=0;cnt<max&&strcmp(htag[cnt],tagname);cnt++);
	return(cnt);
}



char *secure(char *text)
{
	char	*src,*dest;

	if (debug)
		(void)fprintf(stderr,"secure(\"%s\")\n",text);
	if (text) {
		for (src=text,dest=text;*src;src++)
			if ((*src&0x7f)>=0x20)
				*dest++=*src;
		*dest='\0';
	}
	if (debug)
		(void)fprintf(stderr,"secure()=0x%08lx\n",text);
	return(text);
}



/* Replaces <CR><LF> by <LF> only */
char *stripcr(char *text)
{
	char	*src,*dest;

	if (debug)
		(void)fprintf(stderr,"stripcr(\"%s\")\n",text);
	if (text) {
		for (src=text,dest=text;*src;src++)
			if (src[0]!='\r'||src[1]!='\n')
				*dest++=*src;
		*dest='\0';
	}
	return(text);
}



char *checkaccess(char *address,int check)
{
	char	buffer[1024],*ptr;
	int	len;
	FILE	*src;

	if (debug)
		(void)fprintf(stderr,"checkaccess(\"%s\",%d)\n",address,check);
#ifdef ADDRESSES
	/* Check for valid address, extract full address (with personal name) */
	if ((src=fopen(ADDRESSES,"r"))) {
		len=strlen(address);
		while(fgets(buffer,sizeof(buffer),src))
			if (*buffer!='#') {
				if ((ptr=strchr(buffer,'\n')))
					*ptr='\0';
				if (debug)
					(void)fprintf(stderr,"checkaccess: \"%s\"\n",buffer);
				if ((!strncmp(buffer,address,(size_t)len))&&(buffer[len]==' '||buffer[len]=='\t'||buffer[len]=='\0')) {
					(void)fclose(src);
					if (debug)
						(void)fprintf(stderr,"checkaccess: valid \"%s\"\n",buffer);
					return(strdup(buffer));
				}
			}
		(void)fclose(src);
		if (check) {
			(void)sprintf(buffer,BAD_ADDRESS,address,master);
			show_error(buffer);
		}
		else
			return(address);
	}
	else
		show_fatal(ERROR_READ);
	/*NOTREACHED*/
#else /* ADDRESSES */
	return(address);
#endif /* ADDRESSES */
}



/* Actually send mail to recipient */
void mailto(char *address, char *fulladdress, char *cc, char *bcc, char *subject, char *from, int copyself)
{
	char	buffer[1024],error[2048],*ptr,*nptr;
	int	cnt,retval;
	FILE	*dest;

	if (debug)
		(void)fprintf(stderr,"mailto(\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",%d)\n",address,fulladdress,cc,bcc,subject,from,copyself);
#ifdef SENDMAIL_SMTP
	(void)sprintf(buffer,MAILCMD,address);
	if ((dest=popen(buffer,"w"))) {
		(void)fprintf(dest,"HELO\n");
		(void)fprintf(dest,"MAIL FROM:<%s>\n",SENDER);
		(void)fprintf(dest,"RCPT TO:<%s>\n",address);
		if (cc)
			(void)fprintf(dest,"RCPT TO:<%s>\n",cc);
		if (bcc)
			(void)fprintf(dest,"RCPT TO:<%s>\n",bcc);
		if (copyself)
			(void)fprintf(dest,"RCPT TO:<%s>\n",from);
		(void)fprintf(dest,"DATA\n");
		(void)fprintf(dest,"From: %s\n",from);
		(void)fprintf(dest,"Subject: %s\n",subject);
		(void)fprintf(dest,"Sender: %s\n",SENDER);
#ifdef ERRORS_TO
		(void)fprintf(dest,"Errors-To: %s\n",ERRORS_TO);
#endif
		(void)fprintf(dest,"To: %s\n",fulladdress);
		if (cc)
			(void)fprintf(dest,"Cc: %s\n",cc);
		if ((ptr=getenv("REMOTE_ADDR"))&&*ptr)
			(void)fprintf(dest,"X-Addr: %s\n",ptr);
		if ((ptr=getenv("REMOTE_HOST"))&&*ptr)
			(void)fprintf(dest,"X-Host: %s\n",ptr);
		if ((ptr=getenv("REMOTE_IDENT"))&&*ptr)
			(void)fprintf(dest,"X-Ident: %s\n",ptr);
		if ((ptr=getenv("REMOTE_USER"))&&*ptr)
			(void)fprintf(dest,"X-User: %s\n",ptr);
		(void)fprintf(dest,"\n");
		/* Print all the fields preceeded by their name */
		for (cnt=0;cnt<max;cnt++) {
			/* Names starting with a period must be escaped with another period */
			if (*htag[cnt]=='.')
				(void)fprintf(dest,".");
			/* Multiline values have a different format... */
			if (strchr(hval[cnt],'\n')) {
				(void)fprintf(dest,"%s:\n",htag[cnt]);
				ptr=hval[cnt];
				while ((nptr=strchr(ptr,'\n'))) {
					*nptr='\0';
					if (*ptr=='.')
						(void)fprintf(dest,"..");
					(void)fprintf(dest,"%s\n",secure(ptr));
					*nptr++='\n';
					ptr=nptr;
				}
				(void)fprintf(dest,"%s\n",secure(ptr));
				(void)fprintf(dest,"..\n");
			}
			/* ...while singlelines are just Name: Value */
			else
				(void)fprintf(dest,"%s: %s\n",htag[cnt],secure(hval[cnt]));
		}
		/* Ok, end of transmission */
		(void)fprintf(dest,".\n");
		(void)fprintf(dest,"QUIT\n");
		retval=pclose(dest);
		if (retval) {
			(void)sprintf(error,ERROR_PCLOSE,MAILCMD,retval);
			show_fatal(error);
		}
	}
	else {
		(void)sprintf(error,ERROR_POPEN,buffer,strerror(errno));
		show_fatal(error);
	}
#endif
}



void usage(char *image)
{
	(void)fprintf(stderr,"Usage: %s [-h] [-v]\n",image);
	exit(1);
}


int main(int argc, char *argv[])
{
	int	c;
	char	*ptr,*nptr;
	char	*address,*fulladdress,*from,*cc,*bcc,*subject,*location;
	char	*tag,*val;
	int	cl,cnt,copyself;

	while ((c=getopt(argc,argv,"Dvh?")) != EOF)
		switch ((char)c) {
		case 'D':
			debug++;
			break;
		case 'v':
			(void)fprintf(stderr,header);
			exit(0);
		case 'h':
			(void)fprintf(stderr,header);
		case '?':
			usage(argv[0]);
		}

	address=NULL;
	subject=NULL;
	location=NULL;
	from="not-for-mail";
	cc=NULL;
	bcc=NULL;
	copyself=FALSE;

	ptr=getenv("PATH_INFO");
	if (debug)
		(void)fprintf(stderr,"PATH_INFO=\"%s\"\n",ptr);
	if (ptr) {
		if (*ptr=='/')
			ptr++;
		if (*ptr) {
			address=ptr;
			if ((subject=strchr(address,'/'))) {
				*subject++='\0';
				if ((location=strchr(subject,'/'))) {
					*location++='\0';
					if ((ptr=strchr(location,':'))&&ptr[1]=='/')
						if (ptr[2]!='/') {
							nptr=malloc(strlen(location)+2);
							(void)strncpy(nptr,location,(size_t)(ptr-location)+2);
							(void)strcpy(nptr+(ptr-location)+2,ptr+1);
							location=nptr;
						} else if (ptr[3]=='/')
							(void)strcpy(ptr+2,ptr+3);
				}
			}
		}
		if (address&&!*secure(address))
			address=NULL;
		if (subject&&!*secure(subject))
			subject=NULL;
		if (location&&!*secure(location))
			location=NULL;
	}

	/* In case we get a GET request, spit out a minimal mail form */
	ptr=getenv("REQUEST_METHOD");
	if (debug)
		(void)fprintf(stderr,"REQUEST_METHOD=\"%s\"\n",ptr);
	if (!ptr||strcmp(ptr,"POST")) {
		show_header(NULL,NULL);
		(void)printf(DEFAULT_FORM_HEADER,getenv("SCRIPT_NAME"));
		(void)printf(address?DEFAULT_FORM_HIDDEN:DEFAULT_FORM_ADDRESS,"To",address);
		(void)printf(DEFAULT_FORM_CC,"Cc");
		(void)printf(subject?DEFAULT_FORM_HIDDEN:DEFAULT_FORM_SUBJECT,"Subject",subject);
		if (location)
			(void)printf(DEFAULT_FORM_HIDDEN,"Acknowledge",location);
		(void)printf(DEFAULT_FORM_TRAILER);
		show_trailer();
	}

	else {
		/* Sometimes this doesn't work - due to broken clients?
		char	buffer[1024];
		if (strcmp(getenv("CONTENT_TYPE"),"application/x-www-form-urlencoded")) {
			(void)sprintf(buffer,ERROR_CONTENT_TYPE,getenv("CONTENT_TYPE"),"application/x-www-form-urlencoded");
			show_fatal(buffer);
		}
		*/

		/* Parse form, extract special keywords */
		cl=atoi(getenv("CONTENT_LENGTH"));
		if (cl==0)
			show_fatal(ERROR_CONTENT_LENGTH);
		max=0;
		while (cl&&(!feof(stdin))) {
			val=fmakeword(stdin,'&',&cl);
			plustospace(val);
			unescape_url(val);
			tag=secure(makeword(val,'='));
#ifdef FLEMMING
			if (*val) {
#endif
				if (!strcmp(tag,"To")) {
					if (*secure(val))
						address=val;
				} else if (!strcmp(tag,"Subject")) {
					if (*secure(val)) {
						subject=val;
						if (debug)
							(void)fprintf(stderr,"subject=\"%s\"\n",subject);
					}
				} else if (!strcmp(tag,"From")||!strcmp(tag,"Reply-To")) {
					if (*secure(val))
						from=val;
				} else if (!strcmp(tag,"Cc")) {
					if (*secure(val))
						cc=val;
				} else if (!strcmp(tag,"Bcc")) {
					if (*secure(val))
						bcc=val;
				} else if (!strcmp(tag,"Copy-Self")) {
					copyself=TRUE;
				} else if (!strcmp(tag,"Acknowledge")) {
					if (*secure(val))
						location=val;
				} else {
					if (*stripcr(val)) {
						htag[max]=tag;
						hval[max]=val;
						max++;
					}
				}
#ifdef FLEMMING
			}
#endif
		}

		if (address&&(ptr=strchr(address,' ')))
			*ptr='\0';

		/* Test for required values */
		if (!address||!subject)
			show_error(ERROR_ADDRESS);

		/* Self copy needs email address */
		if (!strcmp(from,"not-for-mail"))
			copyself=FALSE;

		/* Add pid to subject */
		if (strstr(subject,"%d")) {
			ptr=malloc(strlen(subject)+16);
			(void)sprintf(ptr,subject,getpid());
			if (debug)
				(void)fprintf(stderr,"Subject: \"%s\" -> \"%s\"\n",subject,ptr);
			subject=ptr;
		}

		/* Send mail */
		fulladdress=checkaccess(address,TRUE);
		mailto(address,fulladdress,cc,bcc,subject,from,copyself);

		/* Drop the user a message */
		if (location)
			show_location(location);
		else {
			show_header(SUCCESS_HEADER,NULL);
			if (copyself||cc||bcc) {
				(void)printf(SUCCESS_SENT,fulladdress);
				if (copyself) {
					if (cc)
						(void)printf(SUCCESS_ALSO,checkaccess(cc,FALSE));
					if (bcc)
						(void)printf(SUCCESS_ALSO,checkaccess(bcc,FALSE));
					(void)printf(SUCCESS_COPY,checkaccess(from,FALSE));
				}
				else
					if (bcc) {
						if (cc)
							(void)printf(SUCCESS_ALSO,checkaccess(cc,FALSE));
						(void)printf(SUCCESS_COPY,checkaccess(bcc,FALSE));
					}
					else
						(void)printf(SUCCESS_COPY,checkaccess(from,FALSE));
			}
			else
				(void)printf(SUCCESS_DESC,fulladdress);
#ifdef FULL_ACKNOWLEDGE
			(void)printf(SUCCESS_FROM,from);
			(void)printf(SUCCESS_SUBJECT,subject);
			(void)printf(SUCCESS_SENDER,SENDER);
			(void)printf(SUCCESS_TO,fulladdress);
			if (cc)
				(void)printf(SUCCESS_CC,cc);
			if (bcc)
				(void)printf(SUCCESS_BCC,bcc);
			if ((ptr=getenv("REMOTE_ADDR"))&&*ptr)
				(void)printf(SUCCESS_ADDR,ptr);
			if ((ptr=getenv("REMOTE_HOST"))&&*ptr)
				(void)printf(SUCCESS_HOST,ptr);
			if ((ptr=getenv("REMOTE_IDENT"))&&*ptr)
				(void)printf(SUCCESS_IDENT,ptr);
			if ((ptr=getenv("REMOTE_USER"))&&*ptr)
				(void)printf(SUCCESS_USER,ptr);
			for (cnt=0;cnt<max;cnt++) {
				if (strchr(hval[cnt],'\n')) {
					(void)printf(SUCCESS_MULTI,htag[cnt]);
					ptr=hval[cnt];
					while ((nptr=strchr(ptr,'\n'))) {
						*nptr++='\0';
						(void)printf(SUCCESS_LINE,secure(ptr));
						ptr=nptr;
					}
					(void)printf(SUCCESS_LINE,secure(ptr));
				}
				else
					(void)printf(SUCCESS_SINGLE,htag[cnt],secure(hval[cnt]));
			}
#endif /* FULL_ACKNOWLEDGE */
			show_trailer();
		}
	}

	return(0);
}
