/* ---------------------------------------------------------------------------
 *	"LogAll" feature for sendmail  to log any message through this MTA
 */

{
#define LOGALLHEADER	"X-Logged"
#define LOGALLLEVEL	12

	char logallrcsid[] =
		"$Id: logall.c,v 1.49 2007/09/05 19:52:02 axel Exp $"
		"(c) /ARX cleanware 2007";
/*
 *	$Log: logall.c,v $
 *	Revision 1.49  2007/09/05 19:52:02  axel
 *	clear configuration comment
 *
 *	Revision 1.46  2007/02/02 15:12:12  axel
 *	rel 8.14
 *
 *	Revision 1.45  2007/02/02 09:28:43  axel
 *	sendmail-8.14.0 addheader needs 4th arg
 *
 *	Revision 1.44  2006/09/13 16:48:16  axel
 *	bad comments removed
 *
 *	Revision 1.43  2006/09/13 05:55:49  axel
 *	obsolete badword scan removed
 *
 *	Revision 1.42  2006/09/13 05:12:47  axel
 *	cosmetics
 *
 *	Revision 1.41  2006/09/12 16:42:24  axel
 *	transscript removed
 *
 *	Revision 1.40  2006/09/12 15:25:55  axel
 *	move to cvs
 *
 *	Revision 1.39  2006/09/12 14:51:53  root
 *	open flag
 *
 *	Revision 1.38  2006/09/10 09:15:39  root
 *	io flags
 *
 *	Revision 1.37  2006/09/07 19:54:35  root
 *	support prior 8.12 removed
 *
 *	Revision 1.36  2006/09/07 19:49:28  root
 *	syslog body open failure
 *
 *	Revision 1.35  2006/01/13 10:30:30  root
 *	8.13.5
 *
 *	Revision 1.34  2003/07/09 18:45:45  root
 *	minor changes, fixes
 *
 *	Revision 1.33  2002/08/14 09:05:34  root
 *	flush the buffer before unlock
 *
 *	Revision 1.32  2002/07/16 14:06:51  root
 *	use own strdup for 8.12 with pool
 *
 *	Revision 1.31  2002/04/22 19:52:42  root
 *	conf dir for sm 8.12
 *
 *	Revision 1.30  2002/04/22 19:22:45  root
 *	get queue dir from lib call
 *
 *	Revision 1.29  2002/03/18 10:36:44  root
 *	released 8.12.x version
 *
 *	Revision 1.27  2002/03/06 19:06:54  root
 *	conf macro and sm_io lib
 *
 *	Revision 1.26  2002/03/06 18:48:24  root
 *	8.12.1 compiled
 *
 *	Revision 1.25  2001/10/08 08:19:18  root
 *	sm 8.12.x changed macros and io-semantics
 *	alpha alpha
 *
 *	Revision 1.24  2001/04/27 16:26:08  root
 *	multiple exclude headers
 *
 *	Revision 1.23  2001/04/26 04:37:39  root
 *	8.11.3 sendmail
 *
 *	Revision 1.22  2001/04/26 04:36:18  root
 *	prepend From with 'c'
 *
 *	Revision 1.21  2001/02/28 18:46:27  root
 *	comments
 *
 *	Revision 1.20  2000/08/21 07:01:50  root
 *	released
 *
 *	Revision 1.17  2000/07/22 19:39:12  root
 *	bad word scan and reject
 *
 *	Revision 1.16  2000/03/14 05:41:31  root
 *	copyright in binary
 *
 *	Revision 1.14  1999/11/21 11:45:56  root
 *	feature exclude added
 *
 *	Revision 1.13  1998/10/03 11:19:33  root
 *	released
 *
 *	Revision 1.12  1998/09/20 19:45:28  root
 *	8.9.1a stable
 *
 *	Revision 1.11  1998/09/19 10:12:04  root
 *	published
 *
 *	Revision 1.7  1998/09/17 07:12:49  root
 *	clobbered qtimestr
 *

ABSTRACT
========

 This routine logs every message in a mail folder before it is
actually transmitted.  The log is "envelope-oriented" which means
it logs every message once - not for every recipient.  You must
also see the sendmail syslog for actual delivery.  The log
includes full message bodys.

 The routine uses the checkcompat() interface.  To every logged
mail a new header "X-Logged" is added which includes machine and
time of the queueing.  From LogLevel >=12 the routine logs itself
to the sendmail syslog.  For debugging the routine's actions flag
-D49.1 should be set.

 Mails which contain configurable headers can be excluded from the
log.  For example all messages from mailling-lists can be excluded
from the log.

 *	IMPLEMENTATION
 *	The logging code is compiled into the sendmail binary
 *	with the documented and often overlooked checkcompat()
 *	routine:
 *	see "Bryan Costales & Eric Allman: sendmail 2nd Edition
 *		O'Reilly 1997
 *		§20 `The checkcompat() Cookbook' p.285ff"
 *	To check whether a message was already logged it adds
 *	a new header "X-Logged" to the message with the logging
 *	host, the envelope id and queued date/time as values.
 *	As long as this info doesn't change the message is no
 *	more logged.
 *
 *	CONFIGURATION
 *	The logging is configured by inserting a new macro(s)/class
 *	into the m4 generated, sendmail configuration file.
 *	In many sendmail installations, this is the file
 *	/etc/mail/sendmail.cf
 *
 *	D{LogAll}path
 *	where path is the full pathname of the logfile, which
 *	should be mode 600. REMEMBER THIS IS A FULL BODY LOG.
 *	So be prepared for a huge file on site with much traffic.
 *	The file must exist and every mail is appended to it.
 *
 *	D{LogExc}header
 *	specifies headers. LogAll will not log any messages which
 *	have any of these headers. This is useful to exclude all mails from
 *	or to mailing lists for example by specifying "X-Mailing-List".
 *	Separate the headers with spaces.
 *
 *	The following example would log everything to /var/log/mail.log
 *	except mails with headers X-Mailing-List:
 *	These macro(s) should be inserted into the .cf file this way:
 *
 *		D{LogAll}/var/log/mail.log
 *		D{LogExc}X-Mailing-List
 *
 *	If an m4-config-file is used to configure sendmail the macro(s)
 *	should be inserted into a m4-config-file like this:
 *
 *		LOCAL_CONFIG
 *		D{LogAll}/var/log/mail.log
 *		D{LogExc}X-Mailing-List
 *
 *	INSTALLATION
 *	This source fragment must be included into the source-file:
 *	.../sendmail-8.x.y/src/conf.c
 *	at the following position (with the full path name of its
 *	current location - here "/root/adm/mail/sendmail/logall.c"):
 *
 *>>old		if (tTd(49, 1))
 *>>old			printf("checkcompat(to=%s, from=%s)\n",             
 *>>old				to->q_paddr, e->e_from.q_paddr);           
 *>>old
 *>>new		#include "/root/adm/mail/sendmail/logall.c"
 *>>old
 *>>old		# ifdef EXAMPLE_CODE
 *
 *	the sendmail binary must be remaked and reinstalled at
 *	its proper position (normally /usr/sbin/sendmail).
 *	The daemon must be restarted.
 *
 *	MANAGEMENT
 *	Since the logfile produced by LogAll is a standard Unix-mailfolder,
 *	the logged mails can be managed with every Unix mail-reader or MUA.
 *	The mails inside the log can be deleted, remailed or read with any
 *	mail user agent. If the logfile is the standard mail folder of a
 *	special "LogAll-user" this is especially easy and even "mail"
 *	or a Windows-based IMAP-client can manage the logfile.
 *
 *	LOGGING
 *	The routine logs its usage with LOG_LEVEL>=12 into the standard
 *	sendmail logging file. To debug the use of the routine you must
 *	set debug flag 49.1.
 *
 *	COMPATIBILITY
 *	LogAll is tested with following combinations:
 *	minimum sendmail is sendmail-8.12.x
 *	sendmail-8.12.1 / Linux 2.2.20
 *	sendmail-8.13.5 / Linux 2.4.32
 *	sendmail-8.13.8 / Linux 2.4.33
 *	sendmail-8.14.0 / Linux 2.4.34
 *
 *	AUTHOR
 *	Axel Reinhold - logall@freakout.de
 *
 *	BUGS/LIMITATIONS
 *	The logfile cannot grow larger that 2GByte. You must rotate the
 *	logfile before reaching this limit - otherwise sendmail will stop.
 *
 *	LICENSE/WARRANTY
 *	The software is provided "AS IS" without warranties of any kind,
 *	either expressed or implied, including, but not limited to the
 *	implied warranties of merchantability and fitness for a particular
 *	purpose. The entire risc of the software is with you. In no event
 *	we will be liable for any damages, including any lost profits,
 *	lost savings or other incidental damages arising out of the use
 *	or inability to use the software, even if we have been advised
 *	of the possibility of such damages, or for any claim by another party.
 *	The license is free in every aspect - there are no costs or
 *	restrictions of any kind.
 *   -------------------------------------------------------------------------
 */

#include <assert.h>

	#define mymacid(id) macid(id)
/* hack for >8.14.0 */
#ifdef RF_RM_ADDR
	#define HEADSPC ,true
#else
	#define HEADSPC
#endif

	int mid;			/* sendmail macro id		    */
	char *logall = NULL;		/* log file path from macro LogAll  */
	char bfpath[MAXLINE];		/* body file path		    */
	char *hlogged = LOGALLHEADER;	/* header field			    */
	char *mailist = NULL;		/* header field for mailing lists   */
	char hlogval[MAXLINE];		/* header value			    */
	char *hlogvalp;			/* header value pointer		    */
	char qtimestr[80];		/* queued time string from asctime  */
	FILE *lf;			/* log file			    */
	SM_FILE_T *bf;			/* body file descriptor for sm 8.12 */
	ADDRESS *a;			/* address structure		    */
	HDR *h;				/* header structure		    */
	int toflag = 0;			/* to header flag		    */
/*	int fromflag = 0;		 * from header flag		    */
	int exclhdr = 0;		/* exclude header scan flag	    */
	long lfpos = 0;			/* remember file pos		    */
	long bfpos = 0;			/* remember file pos		    */
	size_t b_read;			/* bytes read counter		    */
	long bodybytes = 0;		/* body bytes length read	    */
	unsigned char b_buf[8193];	/* buffer for read/write body	    */
	char *token;			/* word token for match to badlist  */
	char *hsep_list = " \t,";	/* separator list for header scan   */
	char subject[MAXLINE];		/* subject header value pointer	    */

	mid = mymacid("{LogAll}");	/* get our config macro		    */
	logall = macvalue(mid, e);	/* to check whether and where to log*/

	/* Use logging if macro is set and we can open the logfile	    */
	if ((logall!=NULL) && ((lf = fopen(logall, "r+"))!=NULL)) {
	  if (tTd(49, 1))		/* debug use of logging		    */
		printf("checkcompat: LogAll=%s e_id=%s\n", logall, e->e_id);

	  /* don't log eXcluded mails				  	    */
	  mid = mymacid("{LogExc}");			/* get exclude macro*/
	  mailist = macvalue(mid, e);
	  if (mailist!=NULL) {
	    token = strtok (mailist, hsep_list);
	    while (token!=NULL) {
		if (hvalue(token, e->e_header)!=NULL) {
			exclhdr = 1;
			break;
		}
		token = strtok(NULL, hsep_list);
	    }
	  }
	  if (mailist==NULL || exclhdr==0) {

	    /* construct the "X-Logged" header field */
	    strcpy(qtimestr, asctime(localtime(&e->e_ctime))); /* get time  */
	    if (qtimestr[strlen(qtimestr)-1]=='\n')	/* remove trailing  */
	    	qtimestr[strlen(qtimestr)-1]='\0';	/* line-feed asctime*/
	    sprintf(hlogval, "Logged by %s as %s at %s",
		MyHostName, e->e_id, qtimestr);		/* our header field */

	    /* add the header if it doesn't already exist		    */
	    hlogvalp = hvalue(hlogged, e->e_header);	/* get actual field */

	    if (hlogvalp==NULL) addheader(hlogged, hlogval, H_DEFAULT, e HEADSPC);

	    /* Log the message if our header didn't exist or was not from us*/
	    if ((hlogvalp==NULL) || (strcmp(hlogvalp, hlogval)!=0)) {

		/* lock the logfile exclusive				    */
		if (lockfile(fileno(lf), logall, NULL, LOCK_EX)) ;
		fseek(lf, 0, SEEK_END);	/* go the eof for appending message */
		lfpos = ftell(lf);		/* remember old position    */

		/* syslog the usage of LogAll				    */
		if (LogLevel >= LOGALLLEVEL) sm_syslog(LOG_INFO, e->e_id,
			"LogAll to %s at %s", logall, qtimestr);

		/* print the Unix From line to separate messages in log file*/
		fprintf(lf, "From %s %s\n", e->e_from.q_user, qtimestr);

		/* scan all headers					    */
		for (h = e->e_header; h != NULL; h = h->h_link) {

			/* check if our header is from us and recent	    */
			/* if not change the field value to our field	    */
			if (strcasecmp(h->h_field, hlogged)==0) {
				if (hlogvalp!=NULL) {
/* --- seems not needed to be free'd	if (h->h_value != NULL) sm_rpool_free(e->e_rpool, h->h_value); */
					h->h_value = sm_rpool_strdup_x(e->e_rpool, hlogval);
					}
				h->h_flags &= ~H_DEFAULT;	/* set flag */
				}

			/* log the header if appropiate			    */
			if ((h->h_value!=NULL) && ((h->h_flags & H_RESENT)==0)) {
				fprintf(lf, "%s: %s\n", h->h_field, h->h_value);
				}

			if (h->h_flags & H_RCPT) toflag = 1;	/* to-header*/

			}

		if (!toflag) {	/* if no to-header construct from envelope  */
			fprintf(lf, "To:");
			for (a = e->e_sendqueue; a != NULL; a = a->q_next)
				fprintf(lf, " %s", a->q_paddr);
			fprintf(lf, "\n");
			}

		fprintf(lf, "\n");	/* separate headers from body	    */

		/* create body path					    */
		/* body file from routine cause of subdirs function >8.10.x */
		sprintf(bfpath, "%s/df%s", qid_printqueue(e->e_qgrp, e->e_qdir), e->e_id);

		/* now add the body from temp file or queued data file	    */
		bf = NULL;
		if (e->e_dfp != NULL) {	/* if body temp file is open use it */
			bf = e->e_dfp;
			assert(bf->sm_magic != NULL && bf->f_file != -1);
			bfpos = sm_io_tell(bf, SM_TIME_DEFAULT); /* remember old position */
			sm_io_rewind(bf, SM_TIME_DEFAULT); /* read from beginning */
			}
		else	{
			bf = sm_io_fopen(bfpath, O_RDONLY);	/* body file */
			}

		if (bf==NULL) {			/* no body could be opened  */
			if (LogLevel > 5) sm_syslog(LOG_ERR, e->e_id,
				"checkcompat(logall): fopen(%s) failed %d=%s",
					bfpath, errno, sm_errstring(errno));
			fprintf(lf, "Unable to log body\n");
			if (tTd(49, 1))
			    printf("checkcompat: fopen(%s) failed\n", bfpath);
			}
		else	{
			while ((
				sm_io_fgets(bf, SM_TIME_DEFAULT, b_buf, sizeof(b_buf)-1)) != NULL) {
				b_read = strlen(b_buf);
				/* if line within mail starts with "From " prepend it with '>' */
				if (strncmp(b_buf, "From ", 5)==0) fputc('>', lf);
				fputs(b_buf, lf);
				bodybytes += b_read;
				}
			if (tTd(49, 1))	printf("checkcompat: LogAll: bodybytes=%ld\n", bodybytes);
			}

		if (e->e_dfp != NULL)	/* if body temp file is open it was used */
			sm_io_seek(bf, SM_TIME_DEFAULT, bfpos, SM_IO_SEEK_SET);
		else	sm_io_close(bf, SM_TIME_DEFAULT);
#ifdef REJECT_CONDITION
		if (REJECT_CONDITION_IS_TRUE) {
			/* found ... so discard the last body log     	    */
			fseek(lf, lfpos, SEEK_SET); 	/* set old pos      */
			ftruncate(fileno(lf), lfpos);
			}
		else	{
#endif
			fprintf(lf, "\n");	/* print trailing empty line*/
#ifdef REJECT_CONDITION
			}
#endif
		fflush(lf);			/* flush the buffer	    */
		if (lockfile(fileno(lf), logall, NULL, LOCK_UN)) ; /* unlock*/
		}
	    fclose(lf);
	    }
#ifdef REJECT_CONDITION
	    if (REJECT_CONDITION_IS_TRUE) {
		if (LogLevel >= LOGALLLEVEL) sm_syslog(LOG_INFO, e->e_id,
			"LogAll rejected ... to=%s from=%s\n",
				to->q_paddr, e->e_from.q_paddr);
		usrerr("553 ... not allowed");
		e->e_flags |= EF_NO_BODY_RETN;
		to->q_status = "5.7.1";
		return (EX_UNAVAILABLE);
	    	}
#endif
	  }

/*   ------------------------------------------------------------
 *	LogAll End
 *	(c) /ARX cleanware
 *	$Revision: 1.49 $
 *   ------------------------------------------------------------
 */
}
