/*
 * Program: Synonym
 * File: synonym.c
 * Author: Ionut Nistor
 * Date: 20 Feb 2003
 *
 * $Id: synonym.c,v 1.8.2.2 2004/01/19 12:20:30 diciu Exp $
 *
 * Licensed under the Modulo Consulting Software License
 * (see file license.txt)
 * 
 */

const char synonym_c_objid[]="$Id: synonym.c,v 1.8.2.2 2004/01/19 12:20:30 diciu Exp $";

#define _GNU_SOURCE

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include <syslog.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <pwd.h>

#include <pthread.h>

#include <libmilter/mfapi.h>

#include "synonym.h"
#include "config.h"
#include "filtering.h"
#include "milter_data.h"

#ifdef DISCLAIMER_SUPPORT
	#include "body_parser.h"
#endif
extern sfsistat	 mlfi_cleanup(SMFICTX *, BOOL);

_type_synonym_config synonym_config;

void free_matched_action_list(matched_action * m_list);

sfsistat
mlfi_envfrom(ctx, envfrom)
	SMFICTX *ctx;
	char **envfrom;
{
	/* We got a connection let's se where it is from and store the originator for further reference */
	synonym_priv *priv;
	
	syslog(LOG_DEBUG, "Envfrom called");
	
	priv = (synonym_priv*)malloc(sizeof(synonym_priv));
	if(priv == NULL)
	{
		syslog(LOG_EMERG, "*** SERIOUS CONDITION - MEMORY ALLOCATION FAILED - SYSTEM UNSTABLE - TAKE IMMEDIATE ACTION ***");
		priv->error_encountered = TRUE;
		return SMFIS_CONTINUE;
	}
	priv->resulted_actions = NULL;
	priv->headers = NULL;
	priv->error_encountered = FALSE;
	strncpy(priv->env_from, *envfrom, MAX_EMAIL);
	priv->env_from[MAX_EMAIL] = '\0';
	priv->env_to=NULL;

#ifdef DISCLAIMER_SUPPORT
	/* initialise disclaimer data */
	syslog(LOG_DEBUG, "Initialising disclaimer data");
	priv->output_stream = NULL;
	strcpy(priv->temp_filename, "");
	priv->bp_state = NULL;
#endif
	smfi_setpriv(ctx, priv);

	return SMFIS_CONTINUE;
}


sfsistat
mlfi_envrcpt(ctx, envrcpt)
	SMFICTX *ctx;
	char **envrcpt;
{
	synonym_priv * priv;
	to_list *new_to;
	
	priv = smfi_getpriv(ctx);
	if(priv == NULL)
	{
		syslog(LOG_ERR, "Cannot get mail context. Accepting message");
		return SMFIS_CONTINUE;
	}
	if(priv->error_encountered)
		return SMFIS_CONTINUE;

	/* Allocate new 'to' element */
	new_to = (to_list*)malloc(sizeof(to_list));
	if(new_to == NULL)
	{
		syslog(LOG_EMERG, "*** SERIOUS CONDITION - MEMORY ALLOCATION FAILED - SYSTEM UNSTABLE - TAKE IMMEDIATE ACTION ***");
		priv->error_encountered=TRUE;
		return SMFIS_CONTINUE;
	}
	strncpy(new_to->email, *envrcpt, MAX_EMAIL);
	new_to->email[MAX_EMAIL] = '\0';
	new_to->next = NULL;

	if(priv->env_to == NULL)
		priv->env_to = new_to;
	else
	{
		while(priv->env_to->next != NULL)
			priv->env_to=priv->env_to->next;
		priv->env_to->next = new_to;
	}
	return SMFIS_CONTINUE;
}



sfsistat
mlfi_header(ctx, headerf, headerv)
	SMFICTX *ctx;
	char *headerf;
	char *headerv;
{
	synonym_header *new_header, *temp_header;
	synonym_priv * priv;
	priv = smfi_getpriv(ctx);
	if(priv == NULL)
	{
		syslog(LOG_ERR, "Cannot get mail context. Accepting message");
		return SMFIS_CONTINUE;
	}
	if(priv->error_encountered)
		return SMFIS_CONTINUE;

	/* Store the headers */

	/* Alloc the new header */
	new_header = (synonym_header*) malloc(sizeof(synonym_header));
	if(new_header == NULL)
	{
		syslog(LOG_CRIT, "CRITICAL: Memory allocation problem - cannot process the email");
		priv->error_encountered = TRUE;
		return SMFIS_CONTINUE;
	}
	new_header->next = NULL;

	/* Set the values */
	strncpy(new_header->header_name, headerf, MAX_HEADER_NAME_SIZE);
	new_header->header_name[MAX_HEADER_NAME_SIZE]='\0';
	strncpy(new_header->header_value, headerv, MAX_HEADER_VALUE_SIZE);
	new_header->header_value[MAX_HEADER_VALUE_SIZE]='\0';

	/* Link it to the end of the list */
	if(priv->headers == NULL)
		priv->headers = new_header;
	else
	{
		temp_header=priv->headers;
		while(temp_header->next!=NULL)
			temp_header = temp_header->next;
		temp_header->next = new_header;
	}

	return SMFIS_CONTINUE;
}


sfsistat
mlfi_eoh(ctx)
	SMFICTX *ctx;
{
	synonym_priv * priv;
	
	sresult result;


	priv = smfi_getpriv(ctx);
	if(priv == NULL)
	{
		syslog(LOG_ERR, "Cannot get mail context. Accepting message");
		return SMFIS_CONTINUE;
	}

	if(priv->error_encountered)
		return SMFIS_CONTINUE;

	if(synonym_config.rules == NULL)
	{
		syslog(LOG_ERR, "No rules appear to be configured. Accepting message");
		return SMFIS_CONTINUE;
	}

	if(priv->headers == NULL)
		syslog(LOG_WARNING, "Message contains no headers. Weird");

	/* Start processing rules - we should have all the required informtion at this point */
	if((result = Synonym_Process_Rules(synonym_config.rules, priv->headers, priv->env_from, priv->env_to, &priv->resulted_actions)) != SYNONYM_OK)
	{
		syslog(LOG_ERR, "Failed to process the synonym rules - result code %d. Accepting message", result);
		return SMFIS_CONTINUE;
	}

	/* We got all the actions that matched header conditions */
	return SMFIS_CONTINUE;
}

sfsistat
mlfi_body(ctx, bodyp, bodylen)
	SMFICTX *ctx;
	u_char *bodyp;
	size_t bodylen;
{
	synonym_priv *priv;
	
#ifdef DISCLAIMER_SUPPORT
	int temp_fd;
#endif

	priv = smfi_getpriv(ctx);
	if(priv == NULL)
	{
		syslog(LOG_ERR, "Cannot get mail context. Accepting message");
		return SMFIS_CONTINUE;
	}

	if(priv->error_encountered)
		return SMFIS_CONTINUE; /* At some point there was an error so we proceed no longer */

	syslog(LOG_DEBUG, "mlfi_body called");
	
	
	if(priv!=NULL)
	{
#ifdef DISCLAIMER_SUPPORT
		/* dump the body content into a temporary file */
		if(priv->output_stream == NULL)
		{
			syslog(LOG_DEBUG, "Output stream was not yet initialised");
			strcpy(priv->temp_filename, "/tmp/synonymXXXXXX");
			if((temp_fd=mkstemp(priv->temp_filename))<0)
			{
				syslog(LOG_ERR, "Failed to open the temporary file");
				priv->error_encountered = TRUE;
				return SMFIS_CONTINUE;
			}
					
			priv->output_stream = fdopen(temp_fd, "w");
			if(priv->output_stream == NULL)
			{
				syslog(LOG_ERR, "Could not open file %s for write. ", priv->temp_filename);
				close(temp_fd); // Close the file descriptor cause is was open - if fdopen fails, the fd remains open
				priv->error_encountered = TRUE;
				return SMFIS_CONTINUE;
			}
			if(fwrite((char *)bodyp, 1, bodylen, priv->output_stream)!= bodylen)
			{
				syslog(LOG_ERR, "Failed to write to the temp file. Check disk space");
				fclose(priv->output_stream);
				unlink(priv->temp_filename);
				priv->error_encountered = TRUE;
				return SMFIS_CONTINUE;
			}
		}
		else
			if(fwrite((char *)bodyp, 1, bodylen, priv->output_stream) != bodylen)
			{
				syslog(LOG_ERR, "Failed to add body data to the temp file");
				fclose(priv->output_stream);
				unlink(priv->temp_filename);
				priv->error_encountered = TRUE;
				return SMFIS_CONTINUE;
			}
#endif
	}
	return SMFIS_CONTINUE;
}

sfsistat
mlfi_eom(ctx)
	SMFICTX *ctx;
{
	synonym_priv *priv;
	matched_action *temp_action;
	char smtp_reply[MAX_SMTP_REPLY_SIZE+1];
	BOOL reject_email=FALSE, delete_email=FALSE, add_copy=FALSE;
	char copyto_list[MAX_EMAIL*5 + 1 + sizeof(SYNONYM_COPIEDBY)]; /* We shall list max 5 addresses */
#ifdef DISCLAIMER_SUPPORT
	struct stat statstruct;
	long file_length = 0;
	int count;
	char fread_buffer[65000];
	char filename[1000];
	char newfilename[1000];
	sfsistat rstat = SMFIS_CONTINUE;
	int retval = 0;
	int add_disclaimer = SUBTYPE_NONE;
	char disclaimer_text[MAX_DISCLAIMER_SIZE+1], disclaimer_html[MAX_DISCLAIMER_SIZE+1];
	char *all_headers = NULL;
	synonym_header *temp_header;
	FILE * fd_processed;
#endif

	priv = smfi_getpriv(ctx);
	if(priv == NULL)
	{
		syslog(LOG_ERR, "Cannot get mail context. Accepting message");
		return SMFIS_CONTINUE;
	}

	if(priv->error_encountered)
		return SMFIS_CONTINUE;
	strcpy(copyto_list, SYNONYM_COPIEDBY);


	/* Perform the matched actions */
	if(priv->resulted_actions != NULL)
		for(temp_action=priv->resulted_actions; temp_action != NULL; temp_action=temp_action->next)
			switch(temp_action->action.action_type)
			{
				case ACTION_COPY:
					add_copy = TRUE;
					smfi_addrcpt(ctx, temp_action->action.action_custom_field);
					if(strlen(copyto_list) + strlen(temp_action->action.action_custom_field) <= MAX_EMAIL*5 + 3)
					{
						strcat(copyto_list, temp_action->action.action_custom_field);
						strcat(copyto_list, ", ");
					}
					break;
				case ACTION_DELETE:
					delete_email=TRUE;
					break;
				case ACTION_REJECT:
					strncpy(smtp_reply, temp_action->action.action_custom_field, MAX_SMTP_REPLY_SIZE);
					smtp_reply[MAX_SMTP_REPLY_SIZE] = '\0';
					reject_email=TRUE;
					break;
				case ACTION_DISCLAIMER:
#ifdef DISCLAIMER_SUPPORT
					if(temp_action->action.action_text_disclaimer[0] !='\0')
						add_disclaimer = SUBTYPE_PLAIN;
					if(temp_action->action.action_html_disclaimer[0] != '\0')
						add_disclaimer = SUBTYPE_HTML;
					if(temp_action->action.action_text_disclaimer[0] !='\0' && temp_action->action.action_html_disclaimer[0] != '\0')
						add_disclaimer = SUBTYPE_BOTH;
					strcpy(disclaimer_text, temp_action->action.action_text_disclaimer);
					strcpy(disclaimer_html, temp_action->action.action_html_disclaimer);
#else
					syslog(LOG_ERR, "Disclaimer matched though disclaimer support not available");
#endif
					break;
				default:
					syslog(LOG_ERR, "Unknown action %d. Ignoring", temp_action->action.action_type);
					break;
					
			}
	else
		syslog(LOG_DEBUG, "No matched actions for email");

#ifdef DISCLAIMER_SUPPORT
	if(add_disclaimer != SUBTYPE_NONE) 
	{
		/* Cristi, call the c-client parser */
		syslog(LOG_DEBUG, "Closing stream for body file and reopening for read access");
		if(priv->output_stream != NULL)
			fclose(priv->output_stream);
		if(priv->temp_filename != NULL)
		{
			strcpy(filename, priv->temp_filename);
			strcpy(newfilename, priv->temp_filename);
			strcat(newfilename, ".new");
		}
		else
		{
			syslog(LOG_ERR, "Error reading temp filename from priv.");
			return -1;
		}
		priv->output_stream = fopen(filename, "r");
		if(priv->output_stream == NULL)
		{
			syslog(LOG_ERR, "Error opening old file for read");
			return rstat;
		}
		
		file_length = stat(filename, &statstruct);
		if(file_length == -1)
		{
			unlink(filename);
			syslog(LOG_ERR, "Could not open body file for read");
			return rstat;
		}
	
		syslog(LOG_DEBUG, "Mallocing body parser structure");
		priv->bp_state = malloc(sizeof(body_parser_state));
		if(priv->bp_state == NULL)
		{
			syslog(LOG_ERR, "Error on malloc of bp_state.");
			return mlfi_cleanup(ctx, TRUE);
		}
		
		syslog(LOG_DEBUG, "Mallocing disclaimer structure");
		priv->bp_state->d_state = malloc(sizeof(disclaimer_state));
		if(priv->bp_state->d_state == NULL)
		{
			syslog(LOG_ERR, "Error on malloc of bp_state.");
			return mlfi_cleanup(ctx, TRUE);
		}
		priv->bp_state->d_state->disclaimer_text = disclaimer_text;
		priv->bp_state->d_state->disclaimer_html = disclaimer_html;
		priv->bp_state->d_state->operation_mode = add_disclaimer;
		priv->bp_state->d_state->disclaimer_text_processed = 0;
		priv->bp_state->d_state->disclaimer_html_processed = 0;
		
		/* Initialise the output stream for the body parser to write in */
		priv->bp_state->out_stream = fopen(newfilename, "w");
		if(priv->bp_state->out_stream == NULL)
		{
			syslog(LOG_DEBUG, "body_parser_finalize: Failed to open new body file for write");
			return BODY_PARSER_FAILURE;
		}
		
		syslog(LOG_DEBUG, "Parsing stream to locate text/plain");

		/* All the headers are needed in a string for c-client to interpret the message body */
		all_headers = malloc(10000);
		if(all_headers == NULL)
		{
			syslog(LOG_CRIT, "Failed to allocate memory for the headers");
			priv->error_encountered = TRUE;
			return SMFIS_CONTINUE;
		}
		all_headers[0] = '\0';
		syslog(LOG_DEBUG, "Writing headers in string.");
		for(temp_header = priv->headers; temp_header != NULL; temp_header = temp_header->next)
		{
			if(strlen(temp_header->header_name) + strlen(temp_header->header_value) + strlen(all_headers) + 1 < 10000)
			{
				strcat(all_headers, temp_header->header_name);
				strcat(all_headers, ": ");
				strcat(all_headers, temp_header->header_value);
				strcat(all_headers, "\n");
			}
			else
			{
				syslog(LOG_ERR, "headers bigger than 10k. dropping.");
				break;
			}
		}
		syslog(LOG_DEBUG, "Finished writing headers in string.");
		
		retval = body_parser_init(all_headers, priv->output_stream, 
			statstruct.st_size, priv->bp_state);
		if(retval != BODY_PARSER_SUCCESS)
		{
			syslog(LOG_ERR, "Error in body_parser_init");
		}

		/*** NOT IMPLEMENTED YET ***/
		//process_attachment_names(priv->bp_state->attachment_rule);

		retval = body_parser_finalize(priv->bp_state);
		if(retval == BODY_PARSER_SUCCESS)
		{
			fd_processed = fopen(newfilename, "r");
			if(fd_processed == NULL)
			{
				syslog(LOG_ERR, "Failed to open new body for read");
			}
			syslog(LOG_DEBUG, "Opened new body for read");
			while((count = fread(fread_buffer, 
				1, 65000, fd_processed)) != 0)
			{
				retval = smfi_replacebody(ctx, fread_buffer, count);
				if(retval == MI_FAILURE)
					syslog(LOG_ERR, "Error in replace body.");
			}
			syslog(LOG_DEBUG, "Body was replaced, last count is %d", count);
			fclose(fd_processed);
		}
		free(all_headers);

		smfi_addheader(ctx, "X-Synonym", SYNONYM_DISCLAIMERBY);
		
		syslog(LOG_DEBUG, "Freeing disclaimer structure");
		if(priv->bp_state->d_state != NULL)
			free(priv->bp_state->d_state);
		syslog(LOG_DEBUG, "Unlinking new body file");
		unlink(newfilename);
	}
	if(priv->output_stream != NULL)
		fclose(priv->output_stream);
	if(priv->temp_filename != NULL)
		unlink(priv->temp_filename);
#endif

	mlfi_cleanup(ctx, TRUE);
	if(reject_email)
	{
		if(smtp_reply[0]!='\0')
			if(smfi_setreply(ctx, "550", "5.7.1", smtp_reply)!= MI_SUCCESS)
				syslog(LOG_ERR, "Failed to set the smtp reply for message rejection");
		return SMFIS_REJECT;
	}
	if(delete_email)
		return SMFIS_DISCARD;
	return SMFIS_CONTINUE;

}

sfsistat
mlfi_close(ctx)
	SMFICTX *ctx;
{
	return SMFIS_ACCEPT;
}

sfsistat
mlfi_abort(ctx)
	SMFICTX *ctx;
{
	return mlfi_cleanup(ctx, FALSE);
}

sfsistat
mlfi_cleanup(ctx, ok)
	SMFICTX *ctx;
	BOOL ok;
{
	sfsistat rstat = SMFIS_CONTINUE;
	synonym_priv *priv;
	to_list * clist, * plist;

	synonym_header *previous_header, *next_header;

	priv = smfi_getpriv(ctx);
	
	if (priv == NULL) 
	{
		syslog(LOG_ERR, "This connection dropped before MAIL FROM:");
		return rstat;
	}

	if (ok)
		;
	
	/* release private memory */

	next_header = previous_header = priv->headers;
	while(next_header != NULL)
	{
		previous_header = next_header;
		next_header = next_header->next;
		free(previous_header);
	}
#ifdef DISCLAIMER_SUPPORT
	/* free the disclaimer state structure */
	if(priv->bp_state != NULL)
		free(priv->bp_state);
#endif
	
	/* Cristi, fix leak caused by matched action being abandoned */
	syslog(LOG_DEBUG, "Freeing matched actions list");
	if(priv->resulted_actions != NULL)
		free_matched_action_list(priv->resulted_actions);
	/* Free env to list */
	clist = priv->env_to;
        plist = clist;
        while(clist != NULL)
        {
                /* all the members are statically allocated so nothing to do here */
                plist = clist;
                clist = clist->next;
                free(plist);
        }

	free(priv);

	smfi_setpriv(ctx, NULL);

	/* return status */
	return rstat;
}

struct smfiDesc smfilter =
{
	"Synonym",	/* filter name */
	SMFI_VERSION,	/* version code -- do not change */
#ifdef DISCLAIMER_SUPPORT
	SMFIF_ADDHDRS | SMFIF_ADDRCPT | SMFIF_DELRCPT | SMFIF_CHGBODY,	/* flags */
#else
	SMFIF_ADDHDRS | SMFIF_ADDRCPT | SMFIF_DELRCPT,	/* flags */
#endif
	NULL,		/* connection info filter */
	NULL,		/* SMTP HELO command filter */
	mlfi_envfrom,	/* envelope sender filter */
	mlfi_envrcpt,		/* envelope recipient filter */
	mlfi_header,	/* header filter */
	mlfi_eoh,	/* end of header */
	mlfi_body,	/* body block filter */
	mlfi_eom,	/* end of message */
	mlfi_abort,	/* message aborted */
	mlfi_close	/* connection cleanup */
};


int
main(argc, argv)
	int argc;
	char *argv[];
{
	int c;
	const char *args = "s:c:u:l:f:dh";
	char socketname[256];
	char configfile[256];
	char runas_user[256];
	BOOL daemon=FALSE;
	struct stat statbuf;
	char pid_str[1024];
	int nread;
	FILE *f_pidfile;
	int pidno;
	pid_t fork_result_id;
	struct passwd *runas_pwent;
	int loglevel=DEFAULT_LOGLEVEL, logfacility=DEFAULT_LOGFACILITY;
	char loglevel_string[64], logfacility_string[64];
	
	strcpy(loglevel_string, DEFAULT_LOGLEVEL_STRING);
	strcpy(logfacility_string, DEFAULT_LOGFACILITY_STRING);

	openlog("Synonym", LOG_NDELAY|LOG_PID, logfacility);
	
	setlogmask(LOG_UPTO(loglevel));

	/* load some default values */
	strcpy(socketname, DEFAULT_SOCKET);
	strcpy(configfile, DEFAULT_CONFIG);
	strcpy(runas_user, DEFAULT_USER);

	/* Process command line options */
	while ((c = getopt(argc, argv, args)) != -1)
	{
		switch (c)
		{
			case 'f': /* Logging facility for syslog */
				if (optarg == NULL || *optarg == '\0')
				{
					fprintf(stderr, "Invalid logging facility name\n");
					exit(-1);
				}
				logfacility = -1;
				if(!strcasecmp(optarg, "LOG_LOCAL0"))
					logfacility=LOG_LOCAL0;
				if(!strcasecmp(optarg, "LOG_LOCAL1"))
					logfacility=LOG_LOCAL1;
				if(!strcasecmp(optarg, "LOG_LOCAL2"))
					logfacility=LOG_LOCAL2;
				if(!strcasecmp(optarg, "LOG_LOCAL3"))
					logfacility=LOG_LOCAL3;
				if(!strcasecmp(optarg, "LOG_LOCAL4"))
					logfacility=LOG_LOCAL4;
				if(!strcasecmp(optarg, "LOG_LOCAL5"))
					logfacility=LOG_LOCAL5;
				if(!strcasecmp(optarg, "LOG_LOCAL6"))
					logfacility=LOG_LOCAL6;
				if(!strcasecmp(optarg, "LOG_LOCAL7"))
					logfacility=LOG_LOCAL7;
				if(!strcasecmp(optarg, "LOG_DAEMON"))
					logfacility=LOG_DAEMON;
				if(!strcasecmp(optarg, "LOG_MAIL"))
					logfacility=LOG_MAIL;
				if(!strcasecmp(optarg, "LOG_USER"))
					logfacility=LOG_USER;

				if(logfacility == -1)
				{
					fprintf(stderr, "Invalid logging facility name - %s\n Choose one of: LOG_LOCAL0, LOG_LOCAL1, ... LOG_LOCAL7, LOG_DAEMON, LOG_MAIL, LOG_USER\n", optarg);
					exit(-1);
				}
				strcpy(logfacility_string, optarg);
				break;
			case 'l': /* Logging level for syslog */
				if (optarg == NULL || *optarg == '\0')
				{
					fprintf(stderr, "Invalid logging level name\n");
					exit(-1);
				}
				loglevel = -1;
				if(!strcasecmp(optarg, "LOG_DEBUG"))
					loglevel=LOG_DEBUG;
				if(!strcasecmp(optarg, "LOG_INFO"))
					loglevel=LOG_INFO;
				if(!strcasecmp(optarg, "LOG_NOTICE"))
					loglevel=LOG_NOTICE;
				if(!strcasecmp(optarg, "LOG_WARNING"))
					loglevel=LOG_WARNING;
				if(!strcasecmp(optarg, "LOG_ERR"))
					loglevel=LOG_ERR;
				if(!strcasecmp(optarg, "LOG_CRIT"))
					loglevel=LOG_CRIT;
				if(!strcasecmp(optarg, "LOG_ALERT"))
					loglevel=LOG_ALERT;
				if(!strcasecmp(optarg, "LOG_EMERG"))
					loglevel=LOG_EMERG;

				if(loglevel == -1)
				{
					fprintf(stderr, "Invalid logging level name - %s\n Choose one of: LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING, LOG_ERR, LOG_CRIT, LOG_ALERT, LOG_EMERG\n", optarg);
					exit(-1);
				}
				strcpy(loglevel_string, optarg);
				break;
			case 's': /* Socket name (for comms with sendmail) */
				if (optarg == NULL || *optarg == '\0')
				{
					fprintf(stderr, "Invalid socket name\n");
					exit(-1);
				}
				strcpy(socketname, optarg);
				break;
			case 'c': /* synonym config file */
				if (optarg == NULL || *optarg == '\0')
				{
					fprintf(stderr, "Invalid config file name\n");
					exit(-1);
				}
				strcpy(configfile, optarg);
				break;
			case 'u': /* run-as username */
				if (optarg == NULL || *optarg == '\0')
				{
					fprintf(stderr, "Invalid user name\n");
					exit(-1);
				}
				strcpy(runas_user, optarg);
				break;
  			case 'd':
				daemon = TRUE;
				break;
			case 'h':
				fprintf(stdout, "Synonym %s\nA flexible Milter-based extension for MTA level copy, drop and reject.\n\nUsage: synonym [-dh] [-c <configfile>] [-u <runas username>] [-s <milter socket>]\n\
-d\tRun as daemon\n-h\tDisplay this help\n-c\tConfiguration file\n-r\tUsername that Synonym will run as\
\n-s\tMilter communication socket (should be the same as in the sendmail configuration file)\n\nLicensed under the Modulo Consulting Software License\n(see /usr/share/doc/synonym/license.txt)\n\n", SYNONYM_VERSION);
				exit(-1);
				break;
		}
	}
	closelog();
	openlog("Synonym", LOG_NDELAY|LOG_PID, logfacility);
	setlogmask(LOG_UPTO(loglevel));

	syslog(LOG_INFO, "Log facility is %s", logfacility_string);
	syslog(LOG_INFO, "Log level is %s", loglevel_string);
	syslog(LOG_INFO, "Socket is %s", socketname);
	syslog(LOG_INFO, "Config file is %s", configfile);
	syslog(LOG_INFO, "Synonym will attempt to run as user %s", runas_user);


	if((runas_pwent = getpwnam(runas_user)) == NULL)
	{
		fprintf(stderr, "Invalid user (cannot run as %s)\n", runas_user);
		exit(-1);
	}
	if((runas_pwent->pw_uid == 0) || (runas_pwent->pw_gid == 0))
	{
		fprintf(stderr, "Synonym should not be run as root. Please specify a valid user.\n");
		exit(-1);
	}

	if(setgid(runas_pwent->pw_gid)!=0)
	{
		fprintf(stderr, "Failed to drop privileges to gid %d\n", (int)runas_pwent->pw_gid);
		exit(-1);
	}
	if(setuid(runas_pwent->pw_uid)!=0)
	{
		fprintf(stderr, "Failed to drop privileges to uid %d\n", (int)runas_pwent->pw_uid);
		exit(-1);
	}

	if(Synonym_Get_Config(configfile, &synonym_config) != SYNONYM_OK)
	{
		fprintf(stderr, "Failed to load configuration (%s)\nPlease check the log file for more details\n", configfile);
		exit(-1);
	}

	if((f_pidfile = fopen(PIDFILE, "r")) != NULL)
	{
		nread = fread(pid_str, 1, 1023, f_pidfile);
		pid_str[nread] = '\0';
		fclose(f_pidfile);
		if((pidno = atoi(pid_str)) != 0)
		{
			if((kill(pidno, 0) == -1) && (errno == ESRCH)) /* pid exists but process is not running anymore */
			{
				syslog(LOG_WARNING, "Synonym was previously stopped uncleanly");
				fprintf(stderr, "Synonym was previously stopped uncleanly\n");
				unlink(PIDFILE);
			}
			else 
			{
				syslog(LOG_ERR, "Synonym already running (%d)", pidno);
				fprintf(stderr, "Synonym already running (%d)\n", pidno);
				exit(-1);
			}
		}
		else
		{
			fprintf(stderr, "Failed to read the PID from %s", PIDFILE);
			exit(-1);
		}
				
	}

	if(daemon)
	{

		fork_result_id = fork();
		if(fork_result_id == -1) /* Error; we should exit */
		{
			syslog(LOG_ERR, "Failed to fork to background");
			exit(-1);
		}
		if(fork_result_id != 0) 
			exit(0); /* we're parent - close ourselves */

		/* we're child, detached */
		syslog(LOG_INFO, "Synonym started as daemon");
	}

	f_pidfile = fopen(PIDFILE, "w");
	if(f_pidfile == NULL)
	{
		syslog(LOG_ERR, "Failed to create pidfile %s", PIDFILE);
		fprintf(stderr, "Failed to create pidfile %s\n", PIDFILE);
		exit(-1);
	}
	sprintf(pid_str, "%d", (int)getpid());
	if(fwrite(pid_str, 1, strlen(pid_str), f_pidfile) != strlen(pid_str))
	{
		syslog(LOG_ERR, "Failed to write the pid in %s", PIDFILE);
		fprintf(stderr, "Failed to write the pid in %s\n", PIDFILE);
		fclose(f_pidfile);
		exit(-1);
	}
	fclose(f_pidfile);

	if(strncmp(socketname, "local:", 6) == 0)
	{/* Local socket already exists - we should delete it */
		if(stat(&socketname[6], &statbuf) == 0)
		{/* The file exists so we have to remove it */
			syslog(LOG_INFO, "The local socket already exists");
			/* Delete the socket file */
			if(unlink(&socketname[6]) != 0)
			{
				fprintf(stderr, "Can't remove the socket file. Please try removing it manually\n");
				exit(-1);
			}
		}
	}

	if(smfi_setconn(socketname) == MI_FAILURE)
	{
		fprintf(stderr, "Can't create comms socket. (check permissions)\n");
		exit(-1);
	}
		
	if (smfi_register(smfilter) == MI_FAILURE)
	{
		fprintf(stderr, "Unable to register as sendmail milter\n");
		exit(EX_UNAVAILABLE);
	}

	fclose(stdin);
	fclose(stdout);
	fclose(stderr);

	smfi_main();

	Synonym_Free_Config(&synonym_config);

	if(unlink(PIDFILE) == -1)
		syslog(LOG_ERR, "Failed to delete the pid file %s", PIDFILE);
	syslog(LOG_WARNING, "Synonym exiting");
	closelog();
	return 0;
}

void free_matched_action_list(matched_action * m_list)
{
        matched_action *current_action, *previous_action;

        if(m_list == NULL)
                return;

        current_action = m_list;
        previous_action = current_action;
        while(current_action != NULL)
        {
		/* all the members are statically allocated so nothing to do here */
                previous_action = current_action;
                current_action = current_action->next;
                free(previous_action);
        }
        m_list = NULL;
}



syntax highlighted by Code2HTML, v. 0.9.1