aboutsummaryrefslogblamecommitdiffstats
path: root/antispam-plugin.c
blob: bbcc0e6caa0d927c4f734d4d1deb0615e5af947e (plain) (tree)



























                                                                                      
                  




                             
                                 
                             






                                                                       

                                             
 
                          
 



                                       






















                                                                      
                                                                              
                                         




                                   

                               
                                                         
                                                       













                                                                         
 

                                                                    

                                                  
 





                                                           

                             
























                                                                 

                                                                         




                                                               

                                                                          




                                                              
                                                                               

                                         













                                                                                















                                                                  
                                                                       


                                            




                     
                                         
 




                                                                               

 
                                          
 




                                                                                

 

                                           




                                                                                 

 















                                             














                                                             

                                                                     
 
                        

                          
 
                 
 
                                      
                                                                            


                                                                       
                                                      







                                                                       
 




                                                                        
                                                                           

                                       
                 
         
 
                
 

                                                       
 

                   
 





                                












                                                    

                                                              











                                                               




                                                                               
 

















                                                                  

                                     











                                                             

                                
                



                                                
                                                              
                                                 

                                            
                                   




                                                                            
                                  

                                                                            
                        



                                                                   
                                                       
/*
 * antispam plugin for dovecot
 *
 * Copyright (C) 2004-2007  Johannes Berg <johannes@sipsolutions.net>
 *                    2006  Frank Cusack
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * based on the original framework http://www.dovecot.org/patches/1.0/copy_plugin.c
 *
 * Please see http://johannes.sipsolutions.net/wiki/Projects/dovecot-dspam-integration
 * for more information on this code.
 *
 * Install the plugin in the usual dovecot module location.
 */

#include <stdlib.h>
#include <ctype.h>

/* dovecot headers we need */
#include "lib.h"
#include "str.h"
#include "client.h"
#include "mail-storage-private.h"
#include "antispam-version.h"

/* defined by imap, pop3, lda */
extern void (*hook_mail_storage_created)(struct mail_storage *storage);

/* internal stuff we need */
#include "antispam-plugin.h"

/* macro since only needed for dovecot 1.1 */
PLUGIN_ID;

static pool_t global_pool;

static char *default_spam_folders[] = {
	"SPAM",
	NULL
};

enum match_type {
	MT_REG,
	MT_PATTERN,
	MT_PATTERN_IGNCASE,

	/* keep last */
	NUM_MT
};

/*
 * There are three different matches for each folder type
 *
 * type			usage
 * ----------------------------
 * MT_REG		plain strcmp()
 * MT_PATTERN		case sensitive match with possible wildcards
 * MT_PATTERN_IGNCASE	case insensitive match with possible wildcards
 */
static char **trash_folders[]  = { NULL,		NULL, NULL };
static char **spam_folders[]   = { default_spam_folders,NULL, NULL };
static char **unsure_folders[] = { NULL,		NULL, NULL };

void (*antispam_next_hook_mail_storage_created)(struct mail_storage *storage);
bool antispam_can_append_to_spam = FALSE;
static char **spam_keywords = NULL;

bool need_keyword_hook;
bool need_folder_hook;

struct backend *backend = NULL;

/* lower-case string, but keep modified UTF7 unchanged */
static void lowercase_string(const char *in, char *out)
{
	char ch;

	while ((ch = *out++ = i_tolower(*in++))) {
		/* lower case */
		if (ch == '&') {
			/* modified UTF7 -- find end of sequence ('-') */
			while ((ch = *out++ = *in++)) {
				if (ch == '-')
					break;
			}
		}
	}
}

static bool mailbox_patternmatch(const struct mailbox *box,
				 const struct mail_storage *storage,
				 const char *name,
				 bool lowercase)
{
	const char *boxname;
	char *lowerboxname;
	int len;
	int rc;

	if (storage && mailbox_get_storage(box) != storage)
		return FALSE;

	t_push();

	boxname = mailbox_get_name(box);
	if (lowercase) {
		lowerboxname = t_buffer_get(strlen(boxname) + 1);
		lowercase_string(boxname, lowerboxname);
		boxname = lowerboxname;
	}

	len = strlen(name);
	if (len && name[len - 1] == '*') {
		/* any wildcard */
		--len;
	} else {
		/* compare EOS too */
		++len;
	}

	rc = memcmp(name, boxname, len) == 0;

	t_pop();

	return rc;
}

static bool mailbox_patternmatch_case(const struct mailbox *box,
				      const struct mail_storage *storage,
				      const char *name)
{
	return mailbox_patternmatch(box, storage, name, FALSE);
}

static bool mailbox_patternmatch_icase(const struct mailbox *box,
				       const struct mail_storage *storage,
				       const char *name)
{
	return mailbox_patternmatch(box, storage, name, TRUE);
}

typedef bool (*match_fn_t)(const struct mailbox *, const struct mail_storage *,
			   const char *);

/* match information */
static const struct {
	const char *human, *suffix;
	match_fn_t fn;
} match_info[NUM_MT] = {
	[MT_REG]		= { .human  = "exact match",
				    .suffix = "",
				    .fn     = mailbox_equals, },
	[MT_PATTERN]		= { .human  = "wildcard match",
				    .suffix = "_PATTERN",
				    .fn     = mailbox_patternmatch_case, },
	[MT_PATTERN_IGNCASE]	= { .human  = "case-insensitive wildcard match",
				    .suffix = "_PATTERN_IGNORECASE",
				    .fn     = mailbox_patternmatch_icase, },
};

static bool mailbox_in_list(struct mailbox *box, char ***patterns)
{
	enum match_type i;
	char **list;

	if (!patterns)
		return FALSE;

	for (i = 0; i < NUM_MT; i++) {
		list = patterns[i];
		if (!list)
			continue;

		while (*list) {
			if (match_info[i].fn(box, box->storage, *list))
				return TRUE;
			list++;
		}
	}

	return FALSE;
}

bool mailbox_is_spam(struct mailbox *box)
{
	bool ret;

	ret = mailbox_in_list(box, spam_folders);
	debug_verbose("mailbox_is_spam(%s): %d\n", mailbox_get_name(box), ret);
	return ret;
}

bool mailbox_is_trash(struct mailbox *box)
{
	bool ret;

	ret = mailbox_in_list(box, trash_folders);
	debug_verbose("mailbox_is_trash(%s): %d\n", mailbox_get_name(box), ret);
	return ret;
}

bool mailbox_is_unsure(struct mailbox *box)
{
	bool ret;

	ret = mailbox_in_list(box, unsure_folders);
	debug_verbose("mailbox_is_unsure(%s): %d\n", mailbox_get_name(box), ret);
	return ret;
}

bool keyword_is_spam(const char *keyword)
{
	char **k = spam_keywords;

	if (!spam_keywords)
		return FALSE;

	while (*k) {
		if (strcmp(*k, keyword) == 0)
			return TRUE;
		k++;
	}

	return FALSE;
}

const char *get_setting(const char *name)
{
	const char *env;

	t_push();
	env = t_strconcat(t_str_ucase(stringify(PLUGINNAME)),
			  "_",
			  name,
			  NULL);
	env = getenv(env);
	t_pop();

	return env;
}

static int parse_folder_setting(const char *setting, char ***strings,
				const char *display_name)
{
	const char *tmp;
	int cnt = 0;
	enum match_type i;

	t_push();

	for (i = 0; i < NUM_MT; ++i) {
		tmp = get_setting(t_strconcat(setting, match_info[i].suffix,
				  NULL));
		if (tmp) {
			strings[i] = p_strsplit(global_pool, tmp, ";");
			if (i == MT_PATTERN_IGNCASE) {
				/* lower case the string */
				char **list = strings[i];
				while (*list) {
					lowercase_string(*list, *list);
					++list;
				}
			}
		}

		if (strings[i]) {
			char **iter = strings[i];
			while (*iter) {
				++cnt;
				debug("\"%s\" is %s %s folder\n", *iter,
					match_info[i].human, display_name);
				iter++;
			}
		}
	}

	t_pop();

	if (!cnt)
		debug("no %s folders\n", display_name);

	return cnt;
}

void PLUGIN_FUNCTION(init)(void)
{
	const char *tmp;
	char * const *iter;
	int spam_folder_count;

	debug_target = ADT_NONE;
	verbose_debug = 0;

	tmp = get_setting("DEBUG_TARGET");
	if (tmp) {
		if (strcmp(tmp, "syslog") == 0)
			debug_target = ADT_SYSLOG;
		else if (strcmp(tmp, "stderr") == 0)
			debug_target = ADT_STDERR;
		else
			exit(4);
	}

	debug("plugin initialising (%s)\n", ANTISPAM_VERSION);

	tmp = get_setting("VERBOSE_DEBUG");
	if (tmp) {
		char *endp;
		unsigned long val = strtoul(tmp, &endp, 10);
		if (*endp || val >= 2) {
			debug("Invalid verbose_debug setting");
			exit(5);
		}
		verbose_debug = val;
		debug_verbose("verbose debug enabled");
	}

	global_pool = pool_alloconly_create("antispam-pool", 1024);

	parse_folder_setting("TRASH", trash_folders, "trash");
	spam_folder_count = parse_folder_setting("SPAM", spam_folders, "spam");
	parse_folder_setting("UNSURE", unsure_folders, "unsure");

	tmp = get_setting("ALLOW_APPEND_TO_SPAM");
	if (tmp && strcasecmp(tmp, "yes") == 0) {
		antispam_can_append_to_spam = TRUE;
		debug("allowing APPEND to spam folders");
	}

	tmp = get_setting("SPAM_KEYWORDS");
	if (tmp)
		spam_keywords = p_strsplit(global_pool, tmp, ";");

	if (spam_keywords) {
		iter = spam_keywords;
		while (*iter) {
			debug("\"%s\" is spam keyword\n", *iter);
			iter++;
		}
	}

	tmp = get_setting("BACKEND");
	if (tmp) {
		if (strcmp(tmp, "crm114") == 0)
			backend = &crm114_backend;
		else if (strcmp(tmp, "dspam") == 0)
			backend = &dspam_backend;
		else if (strcmp(tmp, "pipe") == 0)
			backend = &pipe_backend;
		else if (strcmp(tmp, "signature") == 0)
			backend = &signature_backend;
		else if (strcmp(tmp, "spool2dir") == 0)
			backend = &spool2dir_backend;
		else {
			debug("selected invalid backend!\n");
			exit(3);
		}
	} else {
		debug("no backend selected!\n");
		exit(2);
	}

	/* set spam_folders to empty to only allow keywords */
	need_folder_hook = spam_folder_count > 0;
	need_keyword_hook = !!spam_keywords;

	backend->init(global_pool);

	antispam_next_hook_mail_storage_created = hook_mail_storage_created;
	hook_mail_storage_created = antispam_mail_storage_created;
}

void PLUGIN_FUNCTION(deinit)(void)
{
	hook_mail_storage_created = antispam_next_hook_mail_storage_created;
	backend->exit();
	mempool_unref(&global_pool);
}

/* put dovecot version we built against into plugin for checking */
const char *PLUGIN_FUNCTION(version) = PACKAGE_VERSION;