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

#include	"config.h"
#include	"maildirfilter.h"
#include	"maildirfiltertypelist.h"
#include	"maildirgetquota.h"
#include	"mailbot.h"

#include	"autoresponse.h"
#include	"numlib/numlib.h"
#include	<stdlib.h>
#include	<string.h>
#include	<stdio.h>
#include	<ctype.h>
#include	<errno.h>
#include	<sys/types.h>
#if	HAVE_SYS_STAT_H
#include	<sys/stat.h>
#endif
#if	HAVE_UNISTD_H
#include	<unistd.h>
#endif

static const char rcsid[]="$Id: maildirfilter.c,v 1.18 2003/01/05 04:01:16 mrsam Exp $";

struct maildirfilterrule *maildir_filter_appendrule(struct maildirfilter *r,
					const char *name,
					enum maildirfiltertype type,
					int flags,
					const char *header,
					const char *value,
					const char *folder,
					const char *fromhdr,
					int *errcode)
{
struct maildirfilterrule *p=malloc(sizeof(struct maildirfilterrule));

	*errcode=MF_ERR_INTERNAL;

	if (!p)	return (0);
	memset(p, 0, sizeof(*p));

	if ((p->prev=r->last) != 0)
		p->prev->next=p;
	else
		r->first=p;
	r->last=p;

	if (maildir_filter_ruleupdate(r, p, name, type, flags,
			  header, value, folder, fromhdr, errcode))
	{
		maildir_filter_ruledel(r, p);
		return (0);
	}
	return (p);
}

int maildir_filter_ruleupdate(struct maildirfilter *r,
		  struct maildirfilterrule *p,
		  const char *name,
		  enum maildirfiltertype type,
		  int flags,
		  const char *header,
		  const char *value,
		  const char *folder,
		  const char *fromhdr,
		  int *errcode)
{
const char *c;

/*
** Before creating a new rule, validate all input.
*/

	*errcode=0;

	/* rule name: may not contain quotes or control characters. */
	*errcode=MF_ERR_BADRULENAME;
	if (!name || !*name || strlen(name) > 200)
		return (-1);

	for (c=name; *c; c++)
		if ((unsigned char)*c < ' ' || *c == '\'' || *c == '"' ||
			*c == '`')
			return (-1);

	/* rule type: we must know what it is */

	switch (type)	{
	case startswith:
	case endswith:
	case contains:
	case hasrecipient:
	case mimemultipart:
	case textplain:
	case islargerthan:
		break;
	default:
		*errcode=MF_ERR_BADRULETYPE;
		break;
	} ;

	/* header: */

	*errcode=MF_ERR_BADRULEHEADER;

	c=header;
	if (c && strlen(c) > 200)	return (-1);
	if (c == 0 || *c == 0)
	{
		switch (type)	{
		case hasrecipient:
		case islargerthan:
		case mimemultipart:
		case textplain:
			break;
		case contains:
		case startswith:
		case endswith:
			if (flags & MFR_BODY)
				break;
			/* FALLTHRU */
		default:
			/* required */

			return (-1);
		}
	}
	else for ( ; *c; c++)
	{
		/* no control characters */
		if (*c <= ' ' || *c == MDIRSEP[0] || *c >= 127 || *c == '\'' ||
			*c == '\\' || *c == '"' || *c == '`' || *c == '/')
			return (-1);
	}

	/* rule pattern */

	*errcode=MF_ERR_BADRULEVALUE;

	c=value;
	if (c && strlen(c) > 200)	return (-1);
	if (c == 0 || *c == 0)
	{
		switch (type)	{
		case mimemultipart:
		case textplain:
			break;
		default:
			/* required */

			return (-1);
		}
	}
	else if (!(flags & MFR_PLAINSTRING))
	{
/*
** This is ugly.  We: make sure parenthesis match up, and look out for
** special characters.
*/
	int lparencount=0;

		while (*c)
		{
			if (*c == '/' || *c == '$' || *c == '!'
				|| *c == '`' || (int)(unsigned char)*c < ' '
				|| *c == '\'' || *c == '"') return (-1);
						/* must be escaped */

			if (type == islargerthan)
			{
				if (!isdigit((int)(unsigned char)*c))
					return (-1);
			}

			if (*c == '(')
			{
				if (type == hasrecipient)	return (-1);
				++lparencount;
				++c;
				if (*c == ')')	return (-1);
				continue;
			}
			if (*c == ')')
			{
				if (type == hasrecipient)	return (-1);
				if (lparencount == 0)	return (-1);
				++c;
				--lparencount;
				continue;
			}
			if (*c == '[')	/* This is a set */
			{
				if (type == hasrecipient)	return (-1);
				++c;
				for (;;)
				{
					if (*c == '\'' || *c == '"' ||
						*c == '`')
						return (-1); /* must be quoted*/
					if (*c == '\\')
						++c;
					if (!*c)	return (-1);
					if ((int)(unsigned char)*c < ' ')
						return (-1);
					++c;
					if (*c == ']')	break;
					if (*c != '-')	continue;
					++c;

					if (*c == '\'' || *c == '"' ||
						*c == '`')
						return (-1); /* must be quoted*/
					if (*c == '\\')
						++c;
					if ((int)(unsigned char)*c < ' ')
						return (-1);
					if (!*c)	return (-1);
					++c;
				}
				++c;
				continue;
			}

			if (*c == '\\')
			{
				if (type == hasrecipient)	return (-1);
				++c;
			}
			if (!*c)	return (-1);
			++c;
		}
		if (lparencount)	return (-1);	/* not balanced */
	}

	/* validate FROM header */

	*errcode=MF_ERR_BADFROMHDR;

	while (fromhdr && *fromhdr && isspace((int)(unsigned char)*fromhdr))
		++fromhdr;

	for (c=fromhdr; *c; c++)
		if (*c == '\'' || *c == '\\' ||
		    (int)(unsigned char)*c < ' ')
			return (-1);

	*errcode=MF_ERR_BADRULEFOLDER;

	/* validate name of destination folder */

	c=folder;
	if (!c)	return (-1);
	if (strlen(c) > 200)	return (-1);

	if (*c == '*' || *c == '!')
	{
		/* Forward, or bounce with an error */

		++c;
		for ( ; *c; c++)
		{
			if (strchr("'\"$\\`;(){}#&<>~", *c) ||
				(unsigned char)*c < ' ')
				return (-1);
		}
	}
	else if (*c == '+')	/* Autorespond */
	{
		struct maildir_filter_autoresp_info ai;

		if (maildir_filter_autoresp_info_init_str(&ai, c+1))
			return (-1);

		maildir_filter_autoresp_info_free(&ai);
	}
	else if (strcmp(c, "."))
	{
		for ( ; *c; c++)
		{
			if (*c == '.' && (c[1] == '.' || c[1] == 0))
				return (-1);
			if ((int)(unsigned char)*c < ' ' || *c == '\\'
				|| *c == '|' || *c == '!')
				return (-1);
		}
	}

	/* OK, we're good */

	*errcode=MF_ERR_INTERNAL;

	if (p->rulename)	free(p->rulename);
	if ((p->rulename=strdup(name)) == 0)	return (-1);
	p->type=type;
	if (p->fieldname)	free(p->fieldname);
	if ((p->fieldname=strdup(header ? header:"")) == 0)	return (-1);
	if (p->fieldvalue)	free(p->fieldvalue);
	if ((p->fieldvalue=strdup(value ? value:"")) == 0)	return (-1);
	if (p->tofolder)	free(p->tofolder);
	if ((p->tofolder=malloc(strlen(folder)+2)) == 0)	return (-1);
	*p->tofolder=0;
	/* Prefix with dot by default */
	if (*folder != '*' && *folder != '!' && *folder != '.'
	    && *folder != '+')
		strcpy(p->tofolder, ".");
	strcat(p->tofolder, folder);

	if (p->fromhdr)		free(p->fromhdr);
	if ((p->fromhdr=strdup(fromhdr ? fromhdr:"")) == NULL)
		return (-1);

	p->flags=flags;
	return (0);
}

void maildir_filter_ruledel(struct maildirfilter *r, struct maildirfilterrule *p)
{
	if (p->prev)	p->prev->next=p->next;
	else		r->first=p->next;

	if (p->next)	p->next->prev=p->prev;
	else		r->last=p->prev;

	if (p->rulename)	free(p->rulename);
	if (p->fieldname)	free(p->fieldname);
	if (p->fieldvalue)	free(p->fieldvalue);
	if (p->tofolder)	free(p->tofolder);
	if (p->fromhdr)		free(p->fromhdr);
	free(p);
}

void maildir_filter_ruleup(struct maildirfilter *r, struct maildirfilterrule *p)
{
struct maildirfilterrule *q;

	q=p->prev;
	if (!q)	return;
	q->next=p->next;
	if (p->next)	p->next->prev=q;
	else		r->last=q;

	if ((p->prev=q->prev) != 0)	p->prev->next=p;
	else	r->first=p;

	p->next=q;
	q->prev=p;
}

void maildir_filter_ruledown(struct maildirfilter *r, struct maildirfilterrule *p)
{
struct maildirfilterrule *q;

	q=p->next;
	if (!q)	return;
	q->prev=p->prev;
	if (q->prev)	q->prev->next=q;
	else		r->first=q;

	if ((p->next=q->next) != 0)	p->next->prev=p;
	else	r->last=p;

	p->prev=q;
	q->next=p;
}

static void print_pattern(FILE *f, int flags, const char *v)
{
	if (!(flags & MFR_PLAINSTRING))
	{
		fprintf(f, "%s%s",
			*v && isspace((int)(unsigned char)*v) ? "\\":"", v);
		return;
	}

	while (*v)
	{
		if (!isalnum((int)(unsigned char)*v))
			putc('\\', f);
		putc((int)(unsigned char)*v, f);
		++v;
	}
}

int maildir_filter_saverules(struct maildirfilter *r, const char *filename,
		 const char *maildir,
		 const char *maildirpath, const char *fromaddr)
{
FILE	*f=fopen(filename, "w");
struct maildirfilterrule *p;
char quotabuf[QUOTABUFSIZE];

	if (!f)	return (-1);

	fprintf(f,	"#MFMAILDROP=2\n"
			"#\n"
			"# DO NOT EDIT THIS FILE.  This is an automatically"
						" generated filter.\n"
			"\n");

        if (maildir_getquota(maildir, quotabuf))
        {
                if (errno != ENOENT)
		{
			fclose(f);
			return (-1);
		}
                quotabuf[0]=0;
        }

	if (quotabuf[0] != 0)
	{
		const char *p=quotabuf;

		for (fprintf(f, "MAILDIRQUOTA=\""); *p; p++)
			if (*p != '"' && *p != '\'' && *p != '\\')
				putc(*p, f);
		fprintf(f, "\"\n");
	}

	for (fprintf(f, "FROM='"); *fromaddr; fromaddr++)
	{
		if (*fromaddr != '"' && *fromaddr != '\'' && *fromaddr != '\\')
			putc(*fromaddr, f);
	}
	fprintf(f, "\'\nimport SENDER\nif ($SENDER eq \"\")\n{\n SENDER=$FROM\n}\n\n");

	for (p=r->first; p; p=p->next)
	{
	const char *fieldname=p->fieldname ? p->fieldname:"";
	const char *fieldvalue=p->fieldvalue ? p->fieldvalue:"";
	const char *tofolder=p->tofolder ? p->tofolder:"";

		fprintf(f, "##Op:%s\n",
			typelist[p->type].name);
		fprintf(f, "##Header:%s\n", fieldname);
		fprintf(f, "##Value:%s\n", fieldvalue);
		fprintf(f, "##Folder:%s\n", tofolder);
		fprintf(f, "##From:%s\n", p->fromhdr ? p->fromhdr:"");

		if (p->flags & MFR_PLAINSTRING)
			fprintf(f, "##PlainString\n");
		if (p->flags & MFR_DOESNOT)
			fprintf(f, "##DoesNot\n");
		if (p->flags & MFR_BODY)
			fprintf(f, "##Body\n");
		if (p->flags & MFR_CONTINUE)
			fprintf(f, "##Continue\n");

		fprintf(f, "##Name:%s\n\n", p->rulename ? p->rulename:"");

		fprintf(f, "\nif (");

		if (p->flags & MFR_DOESNOT)
			fprintf(f, "!");
		fprintf(f, "(");

		switch (p->type)	{
		case startswith:
			if (p->flags & MFR_BODY)
			{
				fprintf(f, "/^");
				print_pattern(f, p->flags, fieldvalue);
				fprintf(f, "/:b");
			}
			else
			{
				fprintf(f, "/^%s: *", fieldname);
				print_pattern(f, p->flags, fieldvalue);
				fprintf(f, "/");
			}
			break;
		case endswith:
			if (p->flags & MFR_BODY)
			{
				fprintf(f, "/");
				print_pattern(f, p->flags, fieldvalue);
				fprintf(f, "$/:b");
			}
			else
			{
				fprintf(f, "/^%s:.*", fieldname);
				print_pattern(f, p->flags, fieldvalue);
				fprintf(f, "$/");
			}
			break;
		case contains:
			if (p->flags & MFR_BODY)
			{
				fprintf(f, "/");
				print_pattern(f, p->flags, fieldvalue);
				fprintf(f, "/:b");
			}
			else
			{
				fprintf(f, "/^%s:.*", fieldname);
				print_pattern(f, p->flags, fieldvalue);
				fprintf(f, "/");
			}
			break;
		case hasrecipient:
			fprintf(f, "hasaddr(\"%s\")", fieldvalue);
			break;
		case mimemultipart:
			fprintf(f, "/^Content-Type: *multipart\\/mixed/");
			break;
		case textplain:
			fprintf(f, " (! /^Content-Type:/) || "
					"/^Content-Type: text\\/plain$/ || "
					"/^Content-Type: text\\/plain;/");
			break;
		case islargerthan:
			fprintf(f, "$SIZE > %s", fieldvalue);
			break;
		}
		fprintf(f, "))\n"
			"{\n");

		if (*tofolder == '!')
		{
			fprintf(f, "    %s \"| $SENDMAIL -f \" '\"$SENDER\"' \" %s\"\n",
				p->flags & MFR_CONTINUE ? "cc":"to",
					tofolder+1);
		}
		else if (*tofolder == '*')
		{
			fprintf(f, "    echo \"%s\"\n"
				"    EXITCODE=77\n"
				"    exit\n", tofolder+1);
		}
		else if (*tofolder == '+')
		{
			struct maildir_filter_autoresp_info ai;

			if (maildir_filter_autoresp_info_init_str(&ai, tofolder+1) == 0)
			{

				if (p->fromhdr && p->fromhdr[0])
					fprintf(f, "    AUTOREPLYFROM='%s'\n",
						p->fromhdr);
				else
					fprintf(f, "    AUTOREPLYFROM=$FROM\n"
						);

				fprintf(f, "   `%s -A \"X-Sender: $FROM\""
					" -A \"From: $AUTOREPLYFROM\"",
					MAILBOT);
				if (ai.dsnflag)
					fprintf(f, " -M \"$FROM\"");
				fprintf(f, " -m \"%s/autoresponses/%s\"",
					maildirpath, ai.name);
				if (ai.days > 0)
					fprintf(f,
						" -d \"%s/autoresponses/"
						"%s.dat\" -D %u",
					maildirpath, ai.name, ai.days);
				fprintf(f, " $SENDMAIL -t -f \"\"`\n");
				maildir_filter_autoresp_info_free(&ai);
			}
		}
		else
		{
			if (*tofolder == '.')	++tofolder;
				/* Strip leading dot */

			fprintf(f,
				"   %s \"%s%s%s/.\"\n",
				p->flags & MFR_CONTINUE ? "cc":"to",
				maildirpath,
				*tofolder ? "/.":"",
				tofolder);
		}
		fprintf(f, "}\n\n");
	}
	fflush(f);
	if (ferror(f))
	{
		fclose(f);
		return (-1);
	}
	fprintf(f, "to \"%s/.\"\n", maildirpath);
	if (fclose(f))
		return (-1);
	if (chmod(filename, 0600))
		return (-1);

	return (0);
}

int maildir_filter_loadrules(struct maildirfilter *r, const char *filename)
{
FILE	*f=fopen(filename, "r");
char	buf[BUFSIZ];
char	*p;

enum	maildirfiltertype new_type;
char	new_header[256];
char	new_value[256];
char	new_folder[256];
char	new_autoreplyfrom[512];

int	flags;

	if (!f)	return (MF_LOADNOTFOUND);

	if (fgets(buf, sizeof(buf), f) == 0 ||
		strncmp(buf, "#MFMAILDROP=", 12))
	{
		fclose(f);
		return (MF_LOADFOREIGN);
	}

	flags=atoi(buf+12);
	if (flags != 1 && flags != 2)
	{
		fclose(f);
		return (MF_LOADFOREIGN);
	}

	new_type=contains;
	new_header[0]=0;
	new_value[0]=0;
	new_folder[0]=0;
	new_autoreplyfrom[0]=0;
	flags=0;

#define	SET(f,b) { f[0]=0; strncat( (f), (b), sizeof(f)-1); }

	while ( fgets(buf, sizeof(buf), f))
	{
	int	i;

		p=strchr(buf, '\n');
		if (p)	*p=0;
		if (strncmp(buf, "##", 2))	continue;
		p=buf+2;
		while ( *p && isspace((int)(unsigned char)*p))
			++p;

		if (strncasecmp(p, "From:", 5) == 0)
		{
			p += 5;
			SET(new_autoreplyfrom, p);
			continue;
		}


		if (strncasecmp(p, "Op:", 3) == 0)
		{
			p += 3;

			for (i=0; typelist[i].name; i++)
				if (strcasecmp(typelist[i].name, p) == 0)
					break;
			if (!typelist[i].name)
			{
				fclose(f);
				return (MF_LOADFOREIGN);
			}
			new_type=typelist[i].type;
			continue;
		}

		if (strncasecmp(p, "Header:", 7) == 0)
		{
			p += 7;
			SET(new_header, p);
			continue;
		}

		if (strncasecmp(p, "Value:", 6) == 0)
		{
			p += 6;
			SET(new_value, p);
			continue;
		}

		if (strncasecmp(p, "Folder:", 7) == 0)
		{
			p += 7;
			SET(new_folder, p);
			continue;
		}

		if (strcasecmp(p, "plainstring") == 0)
		{
			flags |= MFR_PLAINSTRING;
			continue;
		}

		if (strcasecmp(p, "doesnot") == 0)
		{
			flags |= MFR_DOESNOT;
			continue;
		}

		if (strcasecmp(p, "continue") == 0)
		{
			flags |= MFR_CONTINUE;
			continue;
		}

		if (strcasecmp(p, "body") == 0)
		{
			flags |= MFR_BODY;
			continue;
		}

		if (strncasecmp(p, "Name:", 5) == 0)
		{
		int dummy;
		struct maildirfilterrule *rr;

			p += 5;
			if ((rr=maildir_filter_appendrule(r, p, new_type, flags,
					      new_header,
					      new_value, new_folder,
					      new_autoreplyfrom, &dummy)) == 0)
			{
				fclose(f);
				return (MF_LOADERROR);
			}
			new_type=contains;
			new_header[0]=0;
			new_value[0]=0;
			new_folder[0]=0;
			new_autoreplyfrom[0]=0;
			flags=0;
		}
	}
	fclose(f);
	return (MF_LOADOK);
}

int maildir_filter_autoresp_info_init(struct maildir_filter_autoresp_info *i, const char *c)
{
	memset(i, 0, sizeof(*i));

	if (maildir_autoresponse_validate(NULL, c))
		return (-1);
	i->name=strdup(c);
	if (!(i->name))
		return (-1);
	return (0);
}

int maildir_filter_autoresp_info_init_str(struct maildir_filter_autoresp_info *i, const char *c)
{
	char *p;

	memset(i, 0, sizeof(*i));
	i->name=strdup(c);
	if (!(i->name))
		return (-1);

	if (strtok(i->name, " \t\r\n") == NULL)
	{
		errno=EINVAL;
		free(i->name);
		i->name=0;
		return (-1);
	}

	while ((p=strtok(NULL, " \t\r\n")) != NULL)
	{
		if (strncmp(p, "dsn=", 4) == 0)
			i->dsnflag=atoi(p+4) ? 1:0;
		else if (strncmp(p, "days=", 5) == 0)
			i->days=atoi(p+5);
	}
	return (0);
}

void maildir_filter_autoresp_info_free(struct maildir_filter_autoresp_info *i)
{
	if (i->name)
	{
		free(i->name);
		i->name=0;
	}
}

char *maildir_filter_autoresp_info_asstr(struct maildir_filter_autoresp_info *i)
{
	char days_buf[NUMBUFSIZE+10];

	const char *dsn_arg="";
	const char *days_arg="";
	char *p;

	if (i->dsnflag)
		dsn_arg=" dsn=1";
	if (i->days > 0)
	{
		strcpy(days_buf, " days=");
		libmail_str_size_t(i->days, days_buf+6);
		days_arg=days_buf;
	}

	p=malloc(strlen(i->name)+1+strlen(dsn_arg)+strlen(days_arg));
	if (!p)
		return (NULL);

	strcat(strcat(strcpy(p, i->name), dsn_arg), days_arg);
	return (p);
}
