aboutsummaryrefslogblamecommitdiffstats
path: root/plugin.c
blob: f75193d390a22b463634b3207349aeed267f501a (plain) (tree)
1
  























                                                                                      
   
 
                   
 

                             
                

                    
                        
 

                            
                       
 






                                                  
                              



                                                                  
 
                          
 
                                                   
                       
                      
 
                 

 













                                                                              
 
                                                                  
 
                            
 








                                                                              
         

 





                                                               
                                                                         
                                                                
                                                              

                                                     
                                        

                                               
                                                      


                                         
                                    



                                                   
                

                                                                        
                              




                                       
 




                                                       
                                                                  










                                                                             

                                                                
 
                                                             
                              
                      
                                                                       





                                                                             








                                                         
                      
                                                        
                                                                        
                                                                  

                                                               
                                                      
                                                    










                                                                              

                         
                      
                                             
      



                                                   
                                                       
                         
 
                                       
                              
                                                                       

                                                         



                                 
                             
 



                                          


                   
 
                                                               
                                                                 




                                              
                                           







                                                      
                

                                       







                                                                    











                                                                    
                                                                
                                          


                                                                             
                                                              
                                                              







                                                                 



















                                                                             
                                              



                                                                            


                                                 
                      
              
                                                       

                                 




















                                                                              


                                                     
                                                  


                                        
                                            
                    
                                                                                   


                                                            
                                                                              






                                                                   
                
















                                                                   
                     
                             
 



                                                                         
                                            
                                                  
                                                                            
                 

                                    

         
                               

 
 
                        
 































                                                                           
                              







                                                                            


                                       

                                                                             
                                                                            

                                                       





                                                                            

 
                          
 
                       
                                    
 
/*
 * 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>

/* dovecot headers we need */
#include "lib.h"
#include "str.h"
#include "client.h"
#include "ostream.h"
#include "imap-search.h"

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

static pool_t global_pool;
static char **trash_folders = NULL;
static char *default_spam_folders[] = {
	"SPAM",
	NULL
};
static char **spam_folders = default_spam_folders;
#ifdef BACKEND_WANTS_SIGNATURE
static char *signature_hdr = "X-DSPAM-Signature";
#endif

static struct strlist *list_add(pool_t pool, struct strlist *list)
{
	struct strlist *n;

	n = p_malloc(pool, sizeof(struct strlist));
	n->next = list;
	n->str = NULL;

	return n;
}

static bool mailbox_in_list(struct mail_storage *storage, struct mailbox *box,
			    char **list)
{
	if (!list)
		return FALSE;

	while (*list) {
		if (mailbox_equals(box, storage, *list))
			return TRUE;
		list++;
	}

	return FALSE;
}

static void client_send_sendalive_if_needed(struct client *client)
{
	time_t now, last_io;

	if (o_stream_get_buffer_used_size(client->output) != 0)
		return;

	now = time(NULL);
	last_io = I_MAX(client->last_input, client->last_output);
	if (now - last_io > MAIL_STORAGE_STAYALIVE_SECS) {
		o_stream_send_str(client->output, "* OK Hang in there..\r\n");
		o_stream_flush(client->output);
		client->last_output = now;
	}
}

#define GENERIC_ERROR		-1
#define SIGNATURE_MISSING	-2
#define BACKEND_FAILURE		-3

/* mostly copied from cmd-copy.c (notes added where changed) */
/* MODIFIED: prototype to include "src_spam" */
static int fetch_and_copy(struct client *client, struct mailbox *destbox,
			  struct mailbox_transaction_context *t,
			  struct mail_search_arg *search_args,
			  const char **src_uidset_r,
			  unsigned int *copy_count_r,
			  bool src_spam)
{
	struct mail_search_context *search_ctx;
	struct mailbox_transaction_context *src_trans;
	struct mail_keywords *keywords;
	const char *const *keywords_list;
	struct mail *mail;
	unsigned int copy_count = 0;
#if DOVECOT_VER==10100
	struct msgset_generator_context srcset_ctx;
	string_t *src_uidset;
#endif
	int ret;
	/* MODIFIED: new variables */
	pool_t pool = pool_alloconly_create("antispam-copy-pool", 1024);
#ifdef BACKEND_WANTS_SIGNATURE
	const char *signature;
	struct strlist *siglist = NULL;
#else
#error Not implemented
#endif

#if DOVECOT_VER==10100
	src_uidset = t_str_new(256);
	msgset_generator_init(&srcset_ctx, src_uidset);
#endif

	src_trans = mailbox_transaction_begin(client->mailbox, 0);
	search_ctx = mailbox_search_init(src_trans, NULL, search_args, NULL);

	mail = mail_alloc(src_trans, MAIL_FETCH_STREAM_HEADER |
			  MAIL_FETCH_STREAM_BODY, NULL);
	ret = 1;
	while (mailbox_search_next(search_ctx, mail) > 0 && ret > 0) {
		if (mail->expunged) {
			ret = 0;
			break;
		}

		if ((++copy_count % COPY_CHECK_INTERVAL) == 0)
			client_send_sendalive_if_needed(client);

		/* MODIFIED: keep track of mail as we copy */
#ifdef BACKEND_WANTS_SIGNATURE
#if DOVECOT_VER==10000
		signature = mail_get_first_header(mail, signature_hdr);
#elif DOVECOT_VER==10100
		signature = NULL;
		ret = mail_get_first_header(mail, signature_hdr, &signature);
#endif
		/* ret can only be != 1 with new dovecot 1.1 API */
		if (ret != 1 || !signature || !signature[0]) {
			ret = SIGNATURE_MISSING;
			break;
		}
		siglist = list_add(pool, siglist);
		siglist->str = p_strdup(pool, signature);
#else
#error Not implemented
#endif

#if DOVECOT_VER==10000
		keywords_list = mail_get_keywords(mail);
		keywords = str_array_length(keywords_list) == 0 ? NULL :
			mailbox_keywords_create(t, keywords_list);
		if (mailbox_copy(t, mail, mail_get_flags(mail),
				 keywords, NULL) < 0)
			ret = mail->expunged ? 0 : -1;
		mailbox_keywords_free(t, &keywords);
#elif DOVECOT_VER==10100
		keywords_list = mail_get_keywords(mail);
		keywords = str_array_length(keywords_list) == 0 ? NULL :
			mailbox_keywords_create_valid(destbox, keywords_list);
		if (mailbox_copy(t, mail, mail_get_flags(mail),
				 keywords, NULL) < 0)
			ret = mail->expunged ? 0 : -1;
		mailbox_keywords_free(destbox, &keywords);

		msgset_generator_next(&srcset_ctx, mail->uid);
#endif
	}
	mail_free(&mail);
#if DOVECOT_VER==10100
	msgset_generator_finish(&srcset_ctx);
#endif

	if (mailbox_search_deinit(&search_ctx) < 0)
		ret = -1;

	if (mailbox_transaction_commit(&src_trans) < 0)
		ret = -1;

	/* MODIFIED: pass to backend */
#ifdef BACKEND_WANTS_SIGNATURE
	/* got all signatures now, pass them to backend if no errors */
	if (ret > 0 && !backend(pool, src_spam, siglist))
		ret = BACKEND_FAILURE;
#else
#error Not implemented
#endif
	/* MODIFIED: kill pool */
	mempool_unref(&pool);

#if DOVECOT_VER==10100
	*src_uidset_r = str_c(src_uidset);
	*copy_count_r = copy_count;
#endif
	return ret;
}


/* mostly copied from cmd-copy.c (notes added where changed) */
static bool cmd_copy_antispam(struct client_command_context *cmd)
{
	struct client *client = cmd->client;
	struct mail_storage *storage;
	struct mailbox *destbox;
	struct mailbox_transaction_context *t;
	struct mail_search_arg *search_arg;
	const char *messageset, *mailbox, *src_uidset;
	enum mailbox_sync_flags sync_flags = 0;
	unsigned int copy_count;
	enum imap_sync_flags imap_flags = 0;
#if DOVECOT_VER==10100
	uint32_t uid_validity, uid1, uid2;
	const char *msg = NULL;
#endif
	int ret;
	/* MODIFIED: added variables */
	bool dst_spam, src_spam;

	/* <message set> <mailbox> */
	if (!client_read_string_args(cmd, 2, &messageset, &mailbox))
		return FALSE;

	if (!client_verify_open_mailbox(cmd))
		return TRUE;

	/* open the destination mailbox */
	if (!client_verify_mailbox_name(cmd, mailbox, TRUE, FALSE))
		return TRUE;

	search_arg = imap_search_get_arg(cmd, messageset, cmd->uid);
	if (search_arg == NULL)
		return TRUE;

	storage = client_find_storage(cmd, &mailbox);
	if (storage == NULL)
		return TRUE;

	if (mailbox_equals(client->mailbox, storage, mailbox)) {
		destbox = client->mailbox;
		/* MODIFIED: don't try to reclassify on copy within folder */
		return cmd_copy(cmd);
	} else {
		destbox = mailbox_open(storage, mailbox, NULL,
				       MAILBOX_OPEN_SAVEONLY |
				       MAILBOX_OPEN_FAST |
				       MAILBOX_OPEN_KEEP_RECENT);
		if (destbox == NULL) {
			client_send_storage_error(cmd, storage);
			return TRUE;
		}
	}

	/* MODIFIED: Trash detection */
	if (mailbox_in_list(storage, client->mailbox, trash_folders) ||
	    mailbox_in_list(storage, destbox, trash_folders)) {
		mailbox_close(&destbox);
		return cmd_copy(cmd);
	}

	/* MODIFIED: from/to-SPAM detection */
	src_spam = mailbox_in_list(storage, client->mailbox, spam_folders);
	dst_spam = mailbox_in_list(storage, destbox, spam_folders);
	/*
	 * "both spam" can happen with multiple spam folders,
	 * "none spam" is the common case where spam folders are not involved
	 */
	if ((src_spam && dst_spam) ||
	    (!src_spam && !dst_spam)) {
		mailbox_close(&destbox);
		return cmd_copy(cmd);
	}

	t = mailbox_transaction_begin(destbox,
				      MAILBOX_TRANSACTION_FLAG_EXTERNAL |
				      MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS);
	ret = fetch_and_copy(client, destbox, t, search_arg,
			     &src_uidset, &copy_count, src_spam);

	if (ret <= 0)
		mailbox_transaction_rollback(&t);
#if DOVECOT_VER==10000
	else {
		if (mailbox_transaction_commit(&t) < 0)
			ret = -1;
	}
#elif DOVECOT_VER==10100
	else if (mailbox_transaction_commit_get_uids(&t, &uid_validity,
						     &uid1, &uid2) < 0)
		ret = -1;
	else if (copy_count == 0)
		msg = "OK No messages copied.";
	else {
		i_assert(copy_count == uid2 - uid1 + 1);

		if (uid1 == uid2) {
			msg = t_strdup_printf("OK [COPYUID %u %s %u] "
					      "Copy completed.",
					      uid_validity, src_uidset, uid1);
		} else {
			msg = t_strdup_printf("OK [COPYUID %u %s %u:%u] "
					      "Copy completed.",
					      uid_validity, src_uidset,
					      uid1, uid2);
		}
	}
#endif

	if (destbox != client->mailbox) {
		sync_flags |= MAILBOX_SYNC_FLAG_FAST;
		imap_flags |= IMAP_SYNC_FLAG_SAFE;
		mailbox_close(&destbox);
	}

	/* MODIFIED: extended error codes */
	if (ret > 0)
		return cmd_sync(cmd, sync_flags, imap_flags, "OK Copy completed.");
	else if (ret == 0) {
		/* some messages were expunged, sync them */
		return cmd_sync(cmd, 0, 0,
			"NO Some of the requested messages no longer exist.");
	} else if (ret == SIGNATURE_MISSING) {
		return cmd_sync(cmd, 0, 0,
			"NO Some of the requested messages have no"
			" antispam signature.");
	} else if (ret == BACKEND_FAILURE) {
		return cmd_sync(cmd, 0, 0,
			"NO Antispam backend failed to retrain.");
	} else {
		client_send_storage_error(cmd, storage);
		return TRUE;
	}
}


static bool cmd_append_antispam(struct client_command_context *cmd)
{
	const char *mailbox;
	struct mail_storage *storage;
	struct mailbox *box;

	/* <mailbox> */
	if (!client_read_string_args(cmd, 1, &mailbox))
		return FALSE;

	storage = client_find_storage(cmd, &mailbox);
	if (!storage)
		return FALSE;

	box = mailbox_open(storage, mailbox, NULL,
			   MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT);
	if (box) {
		if (mailbox_in_list(storage, box, spam_folders)) {
			mailbox_close(&box);
			return cmd_sync(cmd, 0, 0,
					"NO Cannot APPEND to spam folder.");
		}

		mailbox_close(&box);
	}

	return cmd_append(cmd);
}


void antispam_init(void)
{
	char *tmp, **iter;

	debug("antispam plugin intialising\n");

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

	tmp = getenv("ANTISPAM_TRASH");
	if (tmp)
		trash_folders = p_strsplit(global_pool, tmp, ";");

	if (trash_folders) {
		iter = trash_folders;
		while (*iter) {
			debug("antispam: \"%s\" is trash folder\n", *iter);
			iter++;
		}
	} else
		debug("antispam: no trash folders\n");

	tmp = getenv("ANTISPAM_SPAM");
	if (tmp)
		spam_folders = p_strsplit(global_pool, tmp, ";");

	if (spam_folders) {
		iter = spam_folders;
		while (*iter) {
			debug("antispam: \"%s\" is spam folder\n", *iter);
			iter++;
		}
	} else
		debug("antispam: no spam folders\n");

#ifdef BACKEND_WANTS_SIGNATURE
	tmp = getenv("ANTISPAM_SIGNATURE");
	if (tmp)
		signature_hdr = tmp;
	debug("antispam: signature header line is \"%s\"\n", signature_hdr);
#endif

	backend_init(global_pool);

	command_unregister("COPY");
	command_unregister("APPEND");
	command_unregister("UID COPY");
	/*
	 * i_strdup() here is a kludge to avoid crashing in commands_deinit()
	 * since modules are unloaded before it's called, this "COPY" string
	 * would otherwise point to nonexisting memory.
	 */
	command_register(i_strdup("COPY"), cmd_copy_antispam,
			 COMMAND_FLAG_USES_SEQS | COMMAND_FLAG_BREAKS_SEQS);
	command_register(i_strdup("UID COPY"), cmd_copy_antispam,
			 COMMAND_FLAG_BREAKS_SEQS);
	command_register(i_strdup("APPEND"), cmd_append_antispam,
			 COMMAND_FLAG_BREAKS_SEQS);
}

void antispam_deinit(void)
{
	backend_exit();
	mempool_unref(&global_pool);
}