diff options
| author | Sam Varshavchik | 2013-08-19 16:39:41 -0400 | 
|---|---|---|
| committer | Sam Varshavchik | 2013-08-25 14:43:51 -0400 | 
| commit | 9c45d9ad13fdf439d44d7443ae75da15ea0223ed (patch) | |
| tree | 7a81a04cb51efb078ee350859a64be2ebc6b8813 /maildir/maildirfilter.c | |
| parent | a9520698b770168d1f33d6301463bb70a19655ec (diff) | |
| download | courier-libs-9c45d9ad13fdf439d44d7443ae75da15ea0223ed.tar.bz2 | |
Initial checkin
Imported from subversion report, converted to git. Updated all paths in
scripts and makefiles, reflecting the new directory hierarchy.
Diffstat (limited to 'maildir/maildirfilter.c')
| -rw-r--r-- | maildir/maildirfilter.c | 882 | 
1 files changed, 882 insertions, 0 deletions
| diff --git a/maildir/maildirfilter.c b/maildir/maildirfilter.c new file mode 100644 index 0000000..af8b353 --- /dev/null +++ b/maildir/maildirfilter.c @@ -0,0 +1,882 @@ +/* +** Copyright 2000-2010 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> +#include	"maildirmisc.h" + +#if HAVE_SYSEXITS_H +#include	<sysexits.h> +#else +#define	EX_SOFTWARE	70 +#endif + +#if	HAVE_PCRE_H +#include	<pcre.h> +#else +#if	HAVE_PCRE_PCRE_H +#include	<pcre/pcre.h> +#define HAVE_PCRE_H	1 +#endif +#endif + +#if	HAVE_SYS_STAT_H +#include	<sys/stat.h> +#endif +#if	HAVE_UNISTD_H +#include	<unistd.h> +#endif + + +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; +	struct maildirfilterrule *pom; + +/* +** 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 name: may not already exist */ +	*errcode=MF_ERR_EXISTS; +	 +	for (pom=r->first; pom->next; pom=pom->next) { +	    if (p!=pom && !strcmp(name, pom->rulename)) +		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: +	case anymessage: +		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: +		case anymessage: +			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: +		case anymessage: +			break; +		default: +			/* required */ + +			return (-1); +		} +	} +	else if (!(flags & MFR_PLAINSTRING)) +	{ +		/* +		** Let PCRE decide if this is a valid pattern. +		** +		** One exception: the forward slash character, and some other +		** special characters, must always be escaped. +		*/ + +		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); +				++c; +				if (*c == ')')	return (-1); +				continue; +			} +			if (*c == ')') +			{ +				if (type == hasrecipient)	return (-1); +				++c; +				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; +					if (*c == ']')	break; +				} +				++c; +				continue; +			} + +			if (*c == '\\') +			{ +				if (type == hasrecipient)	return (-1); +				++c; +			} +			if (!*c)	return (-1); +			++c; +		} + +#if HAVE_PCRE_H +		switch (type) { +		case contains: +		case startswith: +		case endswith: +			{ +				const char *errptr; +				int errindex; + +				pcre *p=pcre_compile(value, 0, +						     &errptr, +						     &errindex, +						     0); + + +				if (p == NULL) +					return -1; +				pcre_free(p); +			} +			break; +		default: +			break; +		} +#endif +	} + +	/* validate FROM header */ + +	*errcode=MF_ERR_BADFROMHDR; + +	while (fromhdr && *fromhdr && isspace((int)(unsigned char)*fromhdr)) +		++fromhdr; + +	for (c=fromhdr; *c; c++) +		if ((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, "exit") == 0)	/* Purge */ +	{ +	} +	else +	{ +		char *s; + +		if (strcmp(c, INBOX) && +		    strncmp(c, INBOX ".", sizeof(INBOX))) +			return -1; + +		s=maildir_name2dir(".", c); + +		if (!s) +			return -1; +		free(s); +	} + +	/* 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)+1)) == 0)	return (-1); +	strcpy(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; + +	if (!f)	return (-1); + +	fprintf(f,	"#MFMAILDROP=2\n" +			"#\n" +			"# DO NOT EDIT THIS FILE.  This is an automatically" +						" generated filter.\n" +			"\n"); + +	for (fprintf(f, "FROM='"); *fromaddr; fromaddr++) +	{ +		if (*fromaddr == '\'' || *fromaddr == '\\') +			putc('\\', f); +		putc(*fromaddr, f); +	} +	fprintf(f, "\'\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", +			strcmp(tofolder, INBOX) == 0 ? ".": +			strncmp(tofolder, INBOX ".", sizeof(INBOX)) == 0 +			? strchr(tofolder, '.'):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; +		case anymessage: +			fprintf(f, "1"); +			break; +		} +		fprintf(f, "))\n" +			"{\n"); + +		if (*tofolder == '!') +		{ +			fprintf(f, "    %s \"| $SENDMAIL -f \" '\"\"' \" %s\"\n", +				p->flags & MFR_CONTINUE ? "cc":"to", +					tofolder+1); +		} +		else if (*tofolder == '*') +		{ +			fprintf(f, "    echo \"%s\"\n" +				"    EXITCODE=%d\n" +				"    exit\n", tofolder+1, EX_SOFTWARE); +		} +		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]) +				{ +					const char *cp; + +					fprintf(f, "    AUTOREPLYFROM='"); + + +					for (cp=p->fromhdr; *cp; ++cp) +					{ +						if (*cp == '\'' || *cp == '\\') +							putc('\\', f); +						putc(*cp, f); +					} +					fprintf(f, "'\n"); +				} +				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.noquote) +					fprintf(f, " -N"); +				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 (strcmp(tofolder, "exit") == 0) +		{ +			fprintf(f, "    exit\n"); +		} +		else +		{ +			char *s; + +			s=maildir_name2dir(maildirpath, tofolder); + +			if (!s) +				fprintf(f, "  # INTERNAL ERROR in maildir_name2dir\n"); +			else +			{ +				fprintf(f, +					"   %s \"%s/.\"\n", +					p->flags & MFR_CONTINUE ? "cc":"to", +					s); +				free(s); +			} +		} +		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; + +			if (*p == '.') +			{ +				strcpy(new_folder, INBOX); +			} +			else +				new_folder[0]=0; + +			if (strcmp(p, ".")) +				strncat(new_folder, p, +					sizeof(new_folder)-1-strlen(new_folder)); +			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; + +			p += 5; +			maildir_filter_appendrule(r, p, new_type, flags, +						  new_header, +						  new_value, new_folder, +						  new_autoreplyfrom, &dummy); +			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); +		else if (strcmp(p, "noquote") == 0) +			i->noquote=1; +	} +	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=""; +	const char *noquote_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; +	} + +	if (i->noquote) +		noquote_arg=" noquote"; + +	p=malloc(strlen(i->name)+1+strlen(dsn_arg)+strlen(days_arg)+ +		 strlen(noquote_arg)); +	if (!p) +		return (NULL); + +	strcat(strcat(strcat(strcpy(p, i->name), dsn_arg), days_arg), +	       noquote_arg); +	return (p); +} | 
