diff -urN spamass-milt/Makefile.am spamass-milt-1027-bbra5/Makefile.am
--- spamass-milt/Makefile.am	2006-01-23 15:47:11.000000000 -0500
+++ spamass-milt-1027-bbra5/Makefile.am	2006-10-27 10:31:33.000000000 -0400
@@ -63,7 +63,7 @@
 		contrib/spamass-milter.spec.in
 FBSD_CONTRIB =	contrib/spamass-milter.sh
 MISC_CONTRIB =	contrib/README.gnus
-spamass_milter_SOURCES = spamass-milter.cpp spamass-milter.h 
+spamass_milter_SOURCES = spamass-milter.cpp spamass-milter.h llist.c llist.h 
 spamass_milter_LDADD = @LIBOBJS@
 EXTRA_DIST =	$(DEB_CONTRIB) \
 		$(RH_CONTRIB) \
@@ -74,3 +74,4 @@
 		subst_poll.h
 
 spamass-milter.cpp: spamass-milter.h
+llist.c: llist.h
diff -urN spamass-milt/configure.in spamass-milt-1027-bbra5/configure.in
--- spamass-milt/configure.in	2006-04-05 11:59:18.000000000 -0400
+++ spamass-milt-1027-bbra5/configure.in	2006-10-27 10:31:33.000000000 -0400
@@ -100,8 +100,8 @@
 DN_WITH_DMALLOC
 
 # Checks for library functions.
-AC_CHECK_FUNCS([vsyslog vasprintf vsnprintf])
-AC_CHECK_FUNCS([asprintf snprintf])
+AC_CHECK_FUNCS([vsyslog])
+AC_CHECK_FUNCS([snprintf vsnprintf],have_vsnprintf="yes")
 AC_SEARCH_LIBS(gethostbyname, nsl)
 AC_SEARCH_LIBS(connect, socket)
 AC_SEARCH_LIBS(inet_aton, resolv)
@@ -112,6 +112,11 @@
 AC_LANG_PUSH(C)
 AC_REPLACE_FUNCS(strsep daemon)
 AC_CHECK_DECLS([strsep, daemon])
+AC_CHECK_FUNCS([vasprintf],AC_DEFINE(HAVE_VASPRINTF,[1]),AC_DEFINE(HAVE_VASPRINTF,[0])) 
+AC_CHECK_FUNCS([asprintf],AC_DEFINE(HAVE_ASPRINTF,[1]),AC_DEFINE(HAVE_ASPRINTF,[0])) 
+if test x$have_vsnprintf = xyes; then 
+	AC_LIBOBJ([vsnprintf_alloc])
+fi
 AC_LANG_POP(C)
 
 # Check for libmilter and its header files in the usual locations
@@ -129,6 +134,7 @@
 AC_CHECK_HEADERS(libmilter/mfapi.h,,[
 	AC_MSG_ERROR([Please install mfapi.h from the sendmail distribution])
 ])
+AC_CHECK_FUNCS(smfi_setmlreply)
 
 # Files to create from .in templates:
 AC_OUTPUT(Makefile contrib/spamass-milter.spec spamass-milter.1)
diff -urN spamass-milt/llist.c spamass-milt-1027-bbra5/llist.c
--- spamass-milt/llist.c	1969-12-31 19:00:00.000000000 -0500
+++ spamass-milt-1027-bbra5/llist.c	2006-10-27 10:31:33.000000000 -0400
@@ -0,0 +1,406 @@
+/*
+	llist A linked list and manipulation "library"
+
+        Copyright 2002, Joe Maimon, New York USA
+
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+
+#include "llist.h"
+
+#ifdef _LLIST_ERROR_
+#include "llist_error.h"
+#endif /* _LLIST_ERROR_ */
+
+struct linked_list *
+add_linked_list_item(struct linked_list *list,char *data)
+{
+	list = push_linked_list_item(list,(void *)data,strlen(data)+1);	
+	return(list);
+}
+
+struct linked_list *
+push_linked_list_item(struct linked_list * list,void * data,size_t size)
+{
+	struct linked_list * newlist = NULL;
+
+	
+	if (!(size && data))
+	{
+#ifdef _LLIST_ERROR_
+		LLIST_ERROR_THROW(EINVAL,__FILE__,__LINE__,__func__);
+#endif /* _LLIST_ERROR_ */
+		return(list);
+	}
+	
+	newlist = (struct linked_list *) malloc ( sizeof(struct linked_list));
+	if (!newlist)
+	{
+#ifdef _LLIST_ERROR_
+		LLIST_ERROR_THROW(ENOMEM,__FILE__,__LINE__,__func__);
+#endif /* _LLIST_ERROR_ */
+		return(list);
+	}
+
+	newlist->data = NULL;
+	newlist->next = NULL;
+	newlist->prev = NULL;
+	newlist->size = size;
+	
+
+	newlist->data = (void *) malloc ( newlist->size );
+	if (!newlist->data)
+	{
+#ifdef _LLIST_ERROR_
+		LLIST_ERROR_THROW(ENOMEM,__FILE__,__LINE__,__func__);
+#endif /* _LLIST_ERROR_ */
+		free(newlist);
+		return(list);
+	}
+	
+	memcpy(newlist->data,data,newlist->size);
+	
+	if (list)
+	{
+		list->next = newlist;
+		newlist->prev = list;
+	}
+	
+	return(newlist);
+}
+
+struct linked_list *
+del_linked_list_item(struct linked_list *list)
+{
+	struct linked_list *newlist = NULL;
+	struct linked_list *nlistp = NULL;
+	
+	if (!list)
+	{
+#ifdef _LLIST_ERROR_
+		LLIST_ERROR_THROW(EINVAL,__FILE__,__LINE__,__func__);
+#endif /* _LLIST_ERROR_ */
+		return(NULL);
+	}
+
+	/* First the data */
+	/* If you have use of the data, set list->data=NULL before calling this */
+	if (list->data)
+		free(list->data);
+	
+	if (list->next == NULL && list->prev != NULL)
+	{
+		/* We are at the end, more behind us and nothing in front */
+		newlist = list->prev;
+		newlist->next = NULL;
+		free(list);
+		return(newlist);
+	}
+	if (list->next != NULL && list->prev == NULL)
+	{
+		/* We are at the begining, more to come and nothing behind */
+		newlist = list->next;
+		newlist->prev = NULL;
+		free(list);
+		return(newlist);
+	}
+	if (list->next != NULL && list->prev != NULL)
+	{
+		/* We are in the middle of it now */
+		/* We choose to advance the pointer 'forward' */
+		newlist = list->next;
+		nlistp = list->prev;
+		nlistp->next = newlist;
+		newlist->prev = nlistp;
+		free(list);
+		return(newlist);
+	}
+	if (list->next == NULL && list->prev == NULL)
+	{
+		/* Last remaining Item */
+		free(list);
+		return(NULL);
+	}
+	/* 
+	* We should never get here. 
+	* At this point we are looking at a big bug
+	*/
+#ifdef _LLIST_ERROR_
+	LLIST_ERROR_THROW(LLIST_ERROR_EDELE,__FILE__,__LINE__,__func__);
+#endif /* _LLIST_ERROR_ */
+	return(list);
+		
+}
+	
+struct linked_list *
+del_linked_list(struct linked_list *list)
+{
+	while (list)
+		list = del_linked_list_item(list);
+	
+	return(list);
+}
+
+struct linked_list *
+popdel_linked_list_item(struct linked_list * list,void ** data,size_t * len)
+{
+	if(!(data && list && list->data))
+	{
+#ifdef _LLIST_ERROR_
+		LLIST_ERROR_THROW(EINVAL,__FILE__,__LINE__,__func__);
+#endif /* _LLIST_ERROR_ */
+		return(list);
+	}
+	*data = linked_list_data(list, len);
+	list->data = NULL; /* So that the list forgets about this item */
+	list = del_linked_list_item(list);
+	return (list);
+}
+
+struct linked_list *
+pop_linked_list_item(struct linked_list * list,void ** data,size_t * len)
+{
+	if(!(data && list && list->data))
+	{
+#ifdef _LLIST_ERROR_
+		LLIST_ERROR_THROW(EINVAL,__FILE__,__LINE__,__func__);
+#endif /* _LLIST_ERROR_ */
+		return(list);
+	}
+	*data = linked_list_data(list, len);
+	return(list);
+	
+}
+
+struct linked_list *
+getanddel_linked_list_item(struct linked_list * list,void ** data,size_t * len)
+{
+	return(popdel_linked_list_item(list,data,len));
+			
+}
+
+struct linked_list *
+popnext_linked_list_item(struct linked_list * list,void ** data,size_t * len)
+{
+	struct linked_list * nlistp = NULL; 
+	*data = linked_list_data(list, len);
+	if( (nlistp = next_linked_list_item(list)) )
+		return(nlistp);
+	else
+	if( (nlistp = prev_linked_list_item(list)) )
+		return(nlistp);
+	else
+		return(list);
+	
+}	
+
+void *
+linked_list_data(struct linked_list * list, size_t * len)
+{
+	if(!list)
+		return(NULL);
+	
+	if(len)
+		*len = list->size;
+	return(list->data);
+}	
+
+char *
+linked_list_item(struct linked_list * list)
+{
+	return((char *) linked_list_data(list,0));
+}
+
+
+struct linked_list *
+next_linked_list_item(struct linked_list * list)
+{
+	list = step_linked_list(list,1);
+	return(list);
+}
+
+struct linked_list *
+prev_linked_list_item(struct linked_list * list)
+{
+	list = step_linked_list(list,0);
+	return(list);
+}
+
+struct linked_list *
+step_linked_list(struct linked_list * list,const int dir)
+{
+	/* 0 == FIFO, 0< == LIFO */	
+
+	if (!list)
+	{
+#ifdef _LLIST_ERROR_
+		LLIST_ERROR_THROW(EINVAL,__FILE__,__LINE__,__func__);
+#endif /* _LLIST_ERROR_ */
+		return(list);
+	}
+
+	if (!dir)
+	{
+		list = list->prev;
+	}
+	else
+	{
+		list = list->next;
+	}
+	return(list);
+	
+}
+
+
+struct linked_list *
+orient_linked_list(struct linked_list * list,const int dir)
+{
+	if (!list)
+	{
+#ifdef _LLIST_ERROR_
+		LLIST_ERROR_THROW(EINVAL,__FILE__,__LINE__,__func__);
+#endif /* _LLIST_ERROR_ */
+		return(list);
+	}
+
+	if (!dir)
+	{
+		while (list->prev)
+			list = list->prev;	
+	}
+	else
+	{
+		while (list->next)
+			list = list->next;
+	}
+	return(list);
+}
+
+struct linked_list *
+rewind_linked_list(struct linked_list * list)
+{
+	return(orient_linked_list(list,0));
+}
+
+struct linked_list *
+ffrwrd_linked_list(struct linked_list * list)
+{
+	return(orient_linked_list(list,1));
+}
+
+
+
+size_t 
+count_linked_list_data(struct linked_list * list)
+{
+	size_t size = 0;
+	
+	if(!list)
+	{
+		return(size);
+	}
+
+	list = orient_linked_list(list,0);
+
+	if(!list)
+	{
+#ifdef _LLIST_ERROR_
+		LLIST_ERROR_THROW(LLIST_ERROR_EORIENT,__FILE__,__LINE__,__func__);
+#endif /* _LLIST_ERROR_ */
+		return(size);
+	}
+	return(count_linked_list_data_dir(list, 1));
+}	
+
+size_t
+count_linked_list_data_dir(struct linked_list * list, int direction)
+{
+	size_t size = 0;
+	
+	if(!list)
+		return(size);
+
+	while(list)
+	{
+		size += list->size; 
+		list = step_linked_list(list, direction);
+	}
+	return(size);
+}
+
+unsigned int
+count_linked_list_items(struct linked_list * list)
+{
+	int count = 0;
+
+	if(!list)
+	{
+		return(0);
+	}
+
+	list = orient_linked_list(list,0);
+
+	if(!list)
+		return(0);
+
+	while(list)
+	{
+		count++;
+		list = next_linked_list_item(list);
+	}
+	return(count);
+}
+	
+unsigned int
+count_linked_list(struct linked_list * list,const int dir)
+{
+	int count = 0;
+
+	if(!list)
+	{
+		return(0);
+	}
+
+	while(list)
+	{
+		count++;
+		list = step_linked_list(list,dir);
+	}
+	return(count);
+}
+
+struct linked_list *
+search_linked_list(struct linked_list * list, int (*cmp_func)(void *, void *), void * needle, int direction)
+{
+
+	if(!list || !cmp_func)
+	{
+#ifdef _LLIST_ERROR_
+		LLIST_ERROR_THROW(EINVAL,__FILE__,__LINE__,__func__);
+#endif /* _LLIST_ERROR_ */
+		return(NULL);
+	}
+	
+	while(list)
+	{
+		if((*cmp_func)(linked_list_data(list, NULL), needle) == 0)
+			return list;
+			
+		list = step_linked_list(list, direction);
+	}
+	return(NULL);
+}
diff -urN spamass-milt/llist.h spamass-milt-1027-bbra5/llist.h
--- spamass-milt/llist.h	1969-12-31 19:00:00.000000000 -0500
+++ spamass-milt-1027-bbra5/llist.h	2006-10-27 10:31:33.000000000 -0400
@@ -0,0 +1,252 @@
+/*
+	llist.h, llist.c
+	Routines to implement linked lists.
+        
+	Copyright 2002, Joe Maimon, New York USA
+
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+
+#ifndef INCLUDED_LINK_LIST
+	#define INCLUDED_LINK_LIST 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+/* A bi-directional list originaly designed for char strings
+* Can theoreticaly be used for anything.
+* 
+* DO NOT define space for the structure, just a pointer.
+* When handed a NULL for the first time add_linked_list_item || push_linked_list_item
+* will allocate all neccessary and initialize.
+* 
+* If handed garbage, you will get a segfault. SO initialize your pointer to NULL.
+* To use:
+* struct linked_list * list = NULL;
+*/
+
+struct linked_list
+{
+        void  * data;
+        struct linked_list *next;
+        struct linked_list *prev;
+	size_t size;
+};
+
+
+/* takes a list pointer and char string pointer, stores it and returns list address
+* If the list does not currently exist, ensure the pointer *list == NULL and a new chain will be malloced.
+* Returns *list (arg 1) on error (No mem)
+* This is safe to use with primary list handle as it will not lose a valid list pointer.
+* 
+* TO detect whether value was stored correctly, compare return poiinter with input pointer. 
+* If they match, no storage was done.
+* 
+* This is a wrapper arounf push_linked_list_item for char strings.
+* TO Use:
+* list = add_linked_list_item(list, string);
+*/
+
+struct linked_list *
+add_linked_list_item(struct linked_list *list,char *data);
+
+/*See add_linked_list_item()
+ *
+ * This is the general case of the above, it takes a (void *) to an opaque data structure.
+ * You must supply the size.
+ * 
+ * TO Use:
+ * list = push_linked_list_item(list,(void *)data,(int) data_size);
+ */
+struct linked_list *
+push_linked_list_item(struct linked_list * list,void * data, size_t size);
+
+/* takes list pointer. Free char string and list item. Direction is default to LIFO
+* returns a new list pointer or NULL if the list is done for.
+*
+* This is safe to use on primary list handle because when you get NULL back, 
+* there is nothing left to lose.
+*/
+
+struct linked_list *
+del_linked_list_item(struct linked_list *list);
+
+/* Takes list pointer. recursively calls del_linked_list_item until list is no more
+* Returns NULL.
+* 
+* Use this on the primary list handle because it should be NULL after you call this.
+* 
+*/
+
+struct linked_list *
+del_linked_list(struct linked_list *list);
+
+/* These two pop the data pointer and unlink/free the current list entry. 
+ * There is NO mallocing here, the data pointer is copied.
+ *
+ * Refer to pop_linked_list_item and del_linked_list_item
+ */
+
+struct linked_list *
+getanddel_linked_list_item(struct linked_list *list,void ** data,size_t * len);
+
+struct linked_list *
+popdel_linked_list_item(struct linked_list * list,void ** data,size_t * len);
+
+/* Takes a list pointer, a pointer to a pointer of proper type, and a pointer to a size_t variable.
+ * If data pointer is NULL nothing is stored there. If length pointer is NULL, length information is NOT stored there.
+ * You need to make sure you can detect the end of the data refereneced by the pointer in that case.
+ *
+ * Does not delete the list item.
+ * Does not move the list position.
+ * Will not alter list value.
+ *
+ * You must use this in conjunction with {step|next|prev}_linked_list_item which may return a NULL
+ * Test for NULL before assigning it your list handle because otherwise you risk a memory leak.
+ *
+ * Call count_linked_list_items to obtain a loop countdown.
+ *
+ * If there is an error data will remain NULL and so will len.
+ *
+ * Advice: use linked_list_data() instead.
+ */
+
+struct linked_list *
+pop_linked_list_item(struct linked_list * list,void ** data,size_t * len);
+
+/* The only difference in this one is that the list posistion is moved.
+ * If not possible (because the end of the list is here) it will return list verbatim. 
+ * It is a good idea to orient the list while using this.
+ * 
+ * DO NOT test for NULL to terminate while loops while using this. Instead call count_linked_list_items.
+ * No deleting
+ *
+ * Advice: If possible use linked_list_data() followed by step_linked_list() instead.
+ */
+
+struct linked_list *
+popnext_linked_list_item(struct linked_list * list,void ** data,size_t * len);
+
+/*
+ * Returns a pointer to the linked lists data. Does not free.
+ * Sets size value.
+ */
+
+void *
+linked_list_data(struct linked_list * list, size_t * len);
+
+/* Calls above but assumes char *
+ */
+
+char *
+linked_list_item(struct linked_list * list);
+
+
+/* Takes list pointer and moves forward */
+struct linked_list *
+next_linked_list_item(struct linked_list * list);
+
+/* Takes list pointer and moves backwards */
+struct linked_list *
+prev_linked_list_item(struct linked_list * list);
+
+/* Steps one link in any direction of list 
+ * (DIR) ? LIFO : FIFO
+ */
+struct linked_list *
+step_linked_list(struct linked_list * list,int dir);
+
+/* Takes list pointer and flag for which direction to orient list
+* (DIR) ? LIFO : FIFO
+*/
+
+struct linked_list *
+orient_linked_list(struct linked_list *list,const int dir);
+
+/*
+ * Convenience functions for above
+ */
+struct linked_list *
+rewind_linked_list(struct linked_list *list);
+
+struct linked_list *
+ffrwrd_linked_list(struct linked_list *list);
+
+/*
+* Takes pointer to list; returns total byte count of all data pointed to list->data_ptr
+* On error will return 0, same as no data.
+*/
+
+size_t count_linked_list_data(struct linked_list * list);
+
+/*
+ * Same as ABove but only counts data in direction indicated (1 forward 0 reverse) until
+ * end of list
+*/
+
+size_t count_linked_list_data_dir(struct linked_list * list, int direction);
+
+/*
+* Takes pointer to list; return count of ALL linked_list structures pointed to.
+* 
+* Return 0 on error (passing a null or bad list in)
+*/
+
+unsigned int 
+count_linked_list_items(struct linked_list * list);
+
+/*
+* Takes pointer to list; return count of linked_list structures. 
+* This will only count till the end of the list in the direction you specify.
+* Return 0 on error (passing a null or bad list in)
+*/
+
+unsigned int 
+count_linked_list(struct linked_list * list,const int dir);
+
+/*
+ * Takes pointer to list, address of function that return an int and takes two 
+ * arguments of pointer to void, a pointer to void and a direction to search 
+ * the list till end.
+ *
+ * Returns NULL or pointer to list if passed in function returns 0 
+ *
+ * The passed in function will be called with a pointer to the data in the 
+ * list item and the third argument. If it return 0 than, this function will return
+ * the pointer to the list which contained that piece of data.
+ *
+ * If no items in the list produce the return value of 0 from the passed in function
+ * or if the passed in arguments are invalid, NULL will be returned.
+ *
+ * Side effects: None except any caused by passed in function.
+ *
+ * Advice: You should consider rewind_linked_list() or ffrwrd_linked_list before calling this.
+ */
+struct linked_list *
+search_linked_list(struct linked_list * list, int(*cmp_func)(void * data, void * needle), void * needle, const int dir);
+
+#ifdef __cplusplus
+	}
+#endif
+
+#endif /* INCLUDED_LINK_LIST */
diff -urN spamass-milt/llist_error.c spamass-milt-1027-bbra5/llist_error.c
--- spamass-milt/llist_error.c	1969-12-31 19:00:00.000000000 -0500
+++ spamass-milt-1027-bbra5/llist_error.c	2006-10-27 10:31:33.000000000 -0400
@@ -0,0 +1,142 @@
+#include "llist.h"
+#include "llist_error.h"
+
+struct llist_error_FLAGS llist_error_flags = { 0,0,0,0 };
+
+static unsigned int
+llist_error_index(unsigned int);
+
+static void
+llist_error_action(unsigned int,const char *);
+
+static void
+llist_error_perror_arg(unsigned int, const char *);
+
+static unsigned int
+llist_error_get(void);
+
+static struct llist_error llist_error_table [] =
+{ 
+	{ LLIST_ERROR_EGIGO , "garbage in, garbage out" , 0 },
+	{ LLIST_ERROR_EDELE , "deletion routine reaached impossible code branch", 1},
+	{ LLIST_ERROR_EORIENT, "a call to orient_linked_list returned NULL", 0},
+	{ LLIST_ERROR_NONE, "", 0}
+
+};
+
+static const char blank_string[] = "";
+
+static unsigned int llist_errno = 0;
+
+static unsigned int 
+llist_error_index(unsigned int ui)
+{
+	int i = 0;
+	
+	while(i < sizeof(llist_error_table)/sizeof(llist_error_table[0]) )
+	{
+		if(llist_error_table[i].llist_errno == ui)
+			return(i);
+
+		i++;
+	}
+	return(i - 1); /* LLIST_ERROR_NONE */
+}
+
+static void
+llist_error_action(unsigned int ui,const char *s)
+{
+	if(llist_error_flags.print)
+                llist_error_perror_arg(ui,s);
+
+
+        if(ui > LLIST_ERROR_FIRST)
+        {
+
+                if( (llist_error_table[llist_error_index(ui)].severity)
+                  &&(llist_error_flags.llist_abort) )
+                        exit(EX_FAIL);
+        }
+        if(ui < LLIST_ERROR_FIRST)
+        {
+                if(llist_error_flags.sys_abort)
+                        exit( (ui <= sys_nerr) ? ui : EX_FAIL);
+        }
+        return;
+
+}
+
+static void
+llist_error_perror_arg(unsigned int ui,const char *s)
+{
+	char * s2 = NULL;
+	
+        if(ui < LLIST_ERROR_FIRST)
+	{
+                 s2 = strerror((int)ui);
+		 fprintf(stderr,"%s : %s\n",s,s2);
+	}
+
+        if(llist_errno > LLIST_ERROR_FIRST)
+                fprintf(stderr,"%s : %s\n",s,llist_error_table[llist_error_index(ui)].llist_error_string);
+
+        if(ui == LLIST_ERROR_FIRST)
+                fprintf(stderr,"%s : %s\n",s,"Not an error");
+
+        return;
+}
+
+
+void llist_error_throw(unsigned int ui,const char * s)
+{
+	llist_errno = ui;
+
+	llist_error_action(ui,s);
+	
+	return;
+}
+
+unsigned int llist_error_catch(const char * s)
+{
+	unsigned int ui = llist_error_get();
+
+	if(s)
+		llist_error_action(ui,s);
+	if(llist_error_flags.clear)
+		llist_error_clear();
+	return(ui);
+}
+
+void llist_error_clear(void)
+{
+	llist_errno = 0;
+}
+
+static unsigned int llist_error_get(void)
+{
+	return(llist_errno);
+}
+
+unsigned int llist_error_errno(void)
+{
+	return(llist_error_get());
+}
+
+char * 
+llist_error_strerror(unsigned int ui)
+{
+	if(ui < LLIST_ERROR_FIRST)
+		return(strerror((int) ui));
+	
+	if(ui > LLIST_ERROR_FIRST)
+		return(llist_error_table[llist_error_index(ui)].llist_error_string);
+
+	return(blank_string);
+}
+
+void llist_error_perror(const char * s)
+{
+	(void) llist_error_perror_arg(llist_error_errno(),s);
+
+	return;
+}
diff -urN spamass-milt/llist_error.h spamass-milt-1027-bbra5/llist_error.h
--- spamass-milt/llist_error.h	1969-12-31 19:00:00.000000000 -0500
+++ spamass-milt-1027-bbra5/llist_error.h	2006-10-27 10:31:33.000000000 -0400
@@ -0,0 +1,63 @@
+#ifndef HAVE_LLIST_ERROR_INCLUDE
+	#define HAVE_LLIST_ERROR_INCLUDE
+#include <errno.h>
+#include <sys/errno.h>
+#include <sysexits.h>
+
+#ifndef EX_FAIL
+	#define EX_FAIL 63
+#endif /* EX_FAIL */
+
+struct llist_error
+{
+	unsigned int llist_errno;
+	const char * llist_error_string;
+	unsigned int severity;
+};
+
+enum 
+{
+	LLIST_ERROR_FIRST = 1000,
+	LLIST_ERROR_EGIGO,
+	LLIST_ERROR_EDELE,
+	LLIST_ERROR_EORIENT,
+	LLIST_ERROR_NONE,
+
+
+
+	LLIST_ERROR_LAST
+} llist_errors;
+
+struct llist_error_FLAGS
+{
+	char    llist_abort; /* bail on severe errors */
+	char    sys_abort;  /* bail on system errors */
+	char    print; /* library prints errors or not? */
+	char    clear; /* clear error from llist_error_catch */
+
+} llist_error_flags ;
+
+
+
+void llist_error_throw(unsigned int,const char *);
+
+void llist_error_clear(void);
+
+unsigned int llist_error_errno(void);
+
+char * llist_error_strerror(unsigned int);
+
+void llist_error_perror(const char *);
+
+#ifndef MAX_BUF
+	#define MAX_BUF 1024
+#endif /* MAX_BUF */
+
+#define LLIST_ERROR_THROW(w,x,y,z)	\
+{\
+	char buf[MAX_BUF];\
+	snprintf(buf,sizeof(buf),"%s:%d %s()",x,y,z);\
+	llist_error_throw(w,buf);\
+}
+
+#endif /* HAVE_LLIST_ERROR_INCLUDE */
diff -urN spamass-milt/spamass-milter.cpp spamass-milt-1027-bbra5/spamass-milter.cpp
--- spamass-milt/spamass-milter.cpp	2006-07-24 15:59:17.000000000 -0400
+++ spamass-milt-1027-bbra5/spamass-milter.cpp	2006-10-27 10:33:55.000000000 -0400
@@ -102,6 +102,8 @@
 
 #include "libmilter/mfapi.h"
 //#include "libmilter/mfdef.h"
+#include "vsnprintf_alloc.h"
+#include "llist.h"
 
 #if !HAVE_DECL_STRSEP
 char *strsep(char **stringp, const char *delim);
@@ -154,7 +156,7 @@
 
 int flag_debug = (1<<D_ALWAYS);
 bool flag_reject = false;
-int reject_score = -1;
+bool flag_discard = false;
 bool dontmodifyspam = false;    // Don't modify/add body or spam results headers
 bool dontmodify = false;        // Don't add SA headers, ever.
 bool flag_sniffuser = false;
@@ -165,10 +167,10 @@
 int spamc_argc;
 char **spamc_argv;
 bool flag_bucket = false;
-bool flag_bucket_only = false;
-char *spambucket;
 bool flag_full_email = false;		/* pass full email address to spamc */
 bool flag_expand = false;	/* alias/virtusertable expansion */
+struct linked_list * bBra_list = NULL;
+int bBra_search_dir = 0; /* search list in reverse order of entry*/
 bool warnedmacro = false;	/* have we logged that we couldn't fetch a macro? */
 
 #if defined(__FreeBSD__) /* popen bug - see PR bin/50770 */
@@ -181,11 +183,13 @@
 main(int argc, char* argv[])
 {
    int c, err = 0;
-   const char *args = "fd:mMp:P:r:u:D:i:b:B:e:x";
+   const char *args = "fd:mMp:P:r:s:u:D:i:b:B:e:x";
    char *sock = NULL;
    bool dofork = false;
    char *pidfilename = NULL;
    FILE *pidfile = NULL;
+   struct bBrAction bbra;
+   char *p;
 
 #ifdef HAVE_VERBOSE_TERMINATE_HANDLER
 	std::set_terminate (__gnu_cxx::__verbose_terminate_handler);
@@ -228,9 +232,18 @@
 			case 'P':
 				pidfilename = strdup(optarg);
 				break;
+			case 's':
 			case 'r':
-				flag_reject = true;
-				reject_score = atoi(optarg);
+				if (c == 'r')
+					flag_reject = true;
+				else
+					flag_discard = true;
+				p = optarg;
+				do {
+					bbra = parse_bBr_arg(c, p, &p); 
+					bBra_list = push_linked_list_item(bBra_list, (void*) &bbra,
+						sizeof(struct bBrAction)); 
+				} while (p);
 				break;
 			case 'u':
 				flag_sniffuser = true;
@@ -238,24 +251,24 @@
 				break;
 			case 'b':
 			case 'B':
-				if (flag_bucket)
-				{
-					fprintf(stderr, "Can only have one -b or -B flag\n");
-					err = 1;
-					break;
-				}
 				flag_bucket = true;
 				if (c == 'b')
 				{
-					flag_bucket_only = true;
 					smfilter.xxfi_flags |= SMFIF_DELRCPT; // May delete recipients
 				}
-				// we will modify the recipient list; if spamc returns
-				// indicating that this mail is spam, the message will be
-				// sent to <optarg>@localhost
-				smfilter.xxfi_flags |= SMFIF_ADDRCPT; // May add recipients
-				// XXX we should probably verify that optarg is vaguely sane
-				spambucket = strdup( optarg );
+				smfilter.xxfi_flags |= SMFIF_ADDRCPT; 
+				p = optarg;
+				do {
+					bbra = parse_bBr_arg(c, p, &p); 
+					if (!bbra.bucket)
+					{
+						fprintf(stderr, "-%c arg requires a bucket address\n", c);
+						err = 1;
+					}
+					else
+						bBra_list = push_linked_list_item(bBra_list, (void*) &bbra,
+							sizeof(struct bBrAction)); 
+				} while (p);
 				break;
 			case 'x':
 				flag_expand = true;
@@ -279,14 +292,12 @@
    if (!sock || err) {
       cout << PACKAGE_NAME << " - Version " << PACKAGE_VERSION << endl;
       cout << "SpamAssassin Sendmail Milter Plugin" << endl;
-      cout << "Usage: spamass-milter -p socket [-b|-B bucket] [-d xx[,yy...]] [-D host]" << endl;
+      cout << "Usage: spamass-milter -p socket [-d xx[,yy...]] [-D host]" << endl;
       cout << "                      [-e defaultdomain] [-f] [-i networks] [-m] [-M]" << endl;
-      cout << "                      [-P pidfile] [-r nn] [-u defaultuser] [-x]" << endl;
+      cout << "                      [-P pidfile] [-u defaultuser] [-x]" << endl;
+      cout << "                      [ -r arg ] [-b|-B arg] [-s arg]" << endl; 
       cout << "                      [-- spamc args ]" << endl;
       cout << "   -p socket: path to create socket" << endl;
-      cout << "   -b bucket: redirect spam to this mail address.  The orignal" << endl;
-      cout << "          recipient(s) will not receive anything." << endl;
-      cout << "   -B bucket: add this mail address as a BCC recipient of spam." << endl;
       cout << "   -d xx[,yy ...]: set debug flags.  Logs to syslog" << endl;
       cout << "   -D host: connect to spamd at remote host (deprecated)" << endl;
       cout << "   -e defaultdomain: pass full email address to spamc instead of just\n"
@@ -297,12 +308,34 @@
       cout << "   -m: don't modify body, Content-type: or Subject:" << endl;
       cout << "   -M: don't modify the message at all" << endl;
       cout << "   -P pidfile: Put processid in pidfile" << endl;
-      cout << "   -r nn: reject messages with a score >= nn with an SMTP error.\n"
-              "          use -1 to reject any messages tagged by SA." << endl;
       cout << "   -u defaultuser: pass the recipient's username to spamc.\n"
               "          Uses 'defaultuser' if there are multiple recipients." << endl;
       cout << "   -x: pass email address through alias and virtusertable expansion." << endl;
       cout << "   -- spamc args: pass the remaining flags to spamc." << endl;
+      cout << ""<< endl;
+      cout << ""<< endl;
+      cout << "   The following flags support scores, score-ranges, multiple invocations" << endl;
+      cout << "   and multiple argument values concatenated by ';'." << endl;
+      cout << ""<< endl;
+      cout << "   Scores take the following form: nn[,message|,address]." << endl;
+      cout << "   Score-ranges take the following forms: [>nn|>=nn][<nn|<=nn][,message|,address]." << endl;
+      cout << "   Multiple argument values take the following form: [value1][;value2][;value3]." << endl;
+      cout << "   All the flags support multiple invocation in full form. The last applicable one" << endl;
+      cout << "   entered is used by the milter while processing messages." << endl; 
+      cout << "   All flags support escaping the special charachters '<>=,;\\' with the \\." << endl;
+      cout << "   When score is missing the flag applies to all messages spamassassin tags as spam." << endl;
+      cout << "" << endl;
+      cout << "   -r \"[score | score-range][,error-message]\"" << endl;
+      cout << "           Reject messages." << endl;
+      cout << "		  Special cases: if score is -1 reject applies to all tagged spam." << endl;
+      cout << "   -s  \"[score | score-range][,success-message]\"" << endl;
+      cout << "           Discard messages." << endl;
+      cout << "		  Special cases: if score is -1 discard applies to all tagged spam." << endl;
+      cout << "   -b \"[score | score-range],address\"" << endl;
+      cout << "           Replace all recipients of spam and send message to this address." << endl;
+      cout << "   -B \"[score | score-range],address\"" << endl;
+      cout << "           Copy (bcc) spam to address." << endl;
+   
               
       exit(EX_USAGE);
    }
@@ -415,6 +448,15 @@
   string::size_type eoh2 = assassin->d().find("\n\r\n");
   string::size_type eoh = (eoh1 < eoh2) ? eoh1 : eoh2;
   string::size_type bob = assassin->d().find_first_not_of("\r\n", eoh);
+  struct bBrAction bbra; 
+  struct bBrAction * bbra_p = NULL;
+  int score = 0;
+  struct linked_list * rcpt_list = NULL;
+  struct linked_list * bbra_list;
+  bool flag_bucket_only = false;
+
+  bBra_list = orient_linked_list(bBra_list, (bBra_search_dir) ? 0 : 1);
+  memset(&bbra, '\0', sizeof(bbra));
 
   if (bob == string::npos)
   	bob = assassin->d().size();
@@ -422,41 +464,110 @@
   update_or_insert(assassin, ctx, assassin->spam_flag(), &SpamAssassin::set_spam_flag, "X-Spam-Flag");
   update_or_insert(assassin, ctx, assassin->spam_status(), &SpamAssassin::set_spam_status, "X-Spam-Status");
 
-  /* Summarily reject the message if SA tagged it, or if we have a minimum
-     score, reject if it exceeds that score. */
-  if (flag_reject)
+  if (flag_reject || flag_bucket || flag_discard)
+  {
+	  /* we need the score now */
+	int rv;
+	const char *spam_status = assassin->spam_status().c_str();
+	/* SA 3.0 uses the keyword "score" */
+	rv = sscanf(spam_status,"%*s score=%d", &score);
+	if (rv != 1)
+	{
+		/* SA 2.x uses the keyword "hits" */
+		rv = sscanf(spam_status,"%*s hits=%d", &score);
+	}
+	if (rv != 1)
+		debug(D_ALWAYS, "Could not extract score from <%s>", spam_status);
+	else 
+	{
+		bbra.score = score;
+		bbra.score_flag = true;
+		debug(D_MISC, "SA score: %d", score);
+	}
+  }
+
+  /* Summarily reject the message if SA tagged it and we have a reject action 
+   * that matches this scores. Otherwise reject it if there is a matching reject action
+   * for this score.
+   */
+  if (flag_reject || flag_discard)
   {
 	bool do_reject = false;
-	if (reject_score == -1 && !assassin->spam_flag().empty())
-		do_reject = true;
-	if (reject_score != -1)
-	{
-		int score, rv;
-		const char *spam_status = assassin->spam_status().c_str();
-		/* SA 3.0 uses the keyword "score" */
-		rv = sscanf(spam_status,"%*s score=%d", &score);
-		if (rv != 1)
-		{
-			/* SA 2.x uses the keyword "hits" */
-			rv = sscanf(spam_status,"%*s hits=%d", &score);
-		}
-		if (rv != 1)
-			debug(D_ALWAYS, "Could not extract score from <%s>", spam_status);
-		else 
+	bool do_discard = false;
+	bbra_list = bBra_list;
+	bbra.bBr[0] = 'r';
+	bbra.bBr[1] = 's';
+	do
+	{
+		bbra_list = search_linked_list(bbra_list, bBr_cmp,(void *) &bbra, bBra_search_dir);
+		if (!bbra_list)
+			break;
+		bbra_p = (struct bBrAction *) linked_list_data(bbra_list, NULL);
+		bbra_list = step_linked_list(bbra_list, bBra_search_dir);
+		if (!bbra_p)
+			continue;
+		
+ 		if (bbra_p->min_flag || bbra_p->max_flag || 
+		    !assassin->spam_flag().empty())
+ 
 		{
-			debug(D_MISC, "SA score: %d", score);
-			if (score >= reject_score)
+			if (bbra_p->bBr[0] == 'r')
 				do_reject = true;
+			else 
+				do_discard = true;
 		}
-	}
+		
+	} while(!do_reject && !do_discard);
+	
 	if (do_reject)
 	{
+		char * p = (bbra_p && bbra_p->bucket && *bbra_p->bucket) ? 
+			bbra_p->bucket : (char *)"Blocked by SpamAssassin";
 		debug(D_MISC, "Rejecting");
-		smfi_setreply(ctx, "550", "5.7.1", "Blocked by SpamAssassin");
-
-
+		smfi_setreply(ctx, "550", "5.7.1", p);
+	}
+	else if (do_discard)
+	{
+#if defined(HAVE_VSNPRINTF)
+		char * buf;
+#else
+		char buf[4096];
+#endif
+		char * p = (bbra_p && bbra_p->bucket && *bbra_p->bucket) ? 
+			bbra_p->bucket : (char *)"Discarded by SpamAssassin";
+#if defined(HAVE_VSNPRINTF)
+		asprintf(&buf, "%s %s", smfi_getsymval(ctx, "i"), p);
+#else
+		sprintf(&buf, "%s %s", smfi_getsymval(ctx, "i"), p);
+#endif
+		debug(D_MISC, "Discarding");
+		smfi_setreply(ctx, "250", "2.2.1", buf);
+#if defined(HAVE_VSNPRINTF)
+		if (buf)
+			free(buf);
+#endif
+	}
+	if (do_reject || do_discard)
+	{
 		if (flag_bucket)
 		{
+			bbra.bBr[0] = 'B';
+			bbra.bBr[1] = 'b';
+			bbra_list = bBra_list;
+			do 
+			{
+				bbra_list = search_linked_list(bbra_list, bBr_cmp, (void*) &bbra, bBra_search_dir); 
+				if(bbra_list)
+				{	
+					bbra_p = (struct bBrAction *)linked_list_data(bbra_list, NULL);
+					bbra_list = step_linked_list(bbra_list, bBra_search_dir);
+					rcpt_list = add_linked_list_item(rcpt_list, bbra_p->bucket);
+				}
+			} while (bbra_list);
+		}
+		if (rcpt_list)
+		{
+
 			/* If we also want a copy of the spam, shell out to sendmail and
 			   send another copy.  The milter API will not let you send the
 			   message AND return a failure code to the sender, so this is
@@ -464,26 +575,58 @@
 #if defined(__FreeBSD__)
 			int rv;
 #endif
-			
-#if defined(HAVE_ASPRINTF)
+#if defined(HAVE_SNPRINTF)
 			char *buf;
+			char *buf2;
 #else
-			char buf[1024];
+			/* XXX possible buffer overflow here */
+			char buf[4096];
+			char buf2[4096];
+			char bp1 = buf;
+			char bp2 = buf2;
 #endif
 			char *fmt="%s \"%s\"";
 			FILE *p;
-
-#if defined(HAVE_ASPRINTF)
-			asprintf(&buf, fmt, SENDMAIL, spambucket);
-#else
 #if defined(HAVE_SNPRINTF)
-			snprintf(buf, sizeof(buf)-1, fmt, SENDMAIL, spambucket);
+			asprintf(&buf,"%s --", SENDMAIL);
+			while (buf && rcpt_list)
+			{
+				char * rcpt = NULL;
+				rcpt_list = popdel_linked_list_item(rcpt_list,(void **) &rcpt, NULL);
+				asprintf(&buf2, fmt, buf, (rcpt) ? rcpt : "" );
+				if (buf2)
+				{
+					free(buf);
+					buf = buf2;
+				}
+				if (rcpt)
+				{
+					free(rcpt);
+					rcpt = NULL;
+				}
+			}
 #else
 			/* XXX possible buffer overflow here */
-			sprintf(buf, fmt, SENDMAIL, spambucket);
-#endif
+			sprintf(buf, "%s --", SENDMAIL);
+			while (rcpt_list)
+			{
+				char * rcpt = NULL;
+				char * bp3;
+				
+				rcpt_list = popdel_linked_list_item(rcpt_list, &rcpt, NULL);
+				sprintf(bp2, fmt, bp1, (rcpt) ? rcpt : "");
+				bp3 = bp2;
+				bp2 = bp1;
+				bp1 = bp3;
+				if (rcpt)
+				{
+					free(rcpt);
+					rcpt = NULL;
+				}
+			}
 #endif
 
+
 			debug(D_COPY, "calling %s", buf);
 #if defined(__FreeBSD__) /* popen bug - see PR bin/50770 */
 			rv = pthread_mutex_lock(&popen_mutex);
@@ -511,24 +654,66 @@
 				abort();
 			}		
 #endif
-#if defined(HAVE_ASPRINTF)
+#if defined(HAVE_SNPRINTF)
 			free(buf);
 #endif 
 		}
-		return SMFIS_REJECT;
 	}
+	if (do_reject)
+		return SMFIS_REJECT;
+	else if (do_discard)
+		return SMFIS_DISCARD;
+  }
+
+  /* Drop the message into the spam bucket if it's score matches an -bB action or
+   * if the message is tagged as spam and there is a scoreless -bB action */
+  if (flag_bucket)
+  {
+	
+	bbra.bBr[0] = 'B';
+	bbra.bBr[1] = 'b';
+	bbra_list = bBra_list;
+	do
+	{
+		bbra_list = search_linked_list(bbra_list, bBr_cmp,(void *) &bbra, bBra_search_dir);
+		if (bbra_list)
+		{
+			bbra_p = (struct bBrAction *)linked_list_data(bbra_list, NULL);
+			bbra_list = step_linked_list(bbra_list, bBra_search_dir);
+			if (!bbra_p)
+				continue; /* should never happen */
+			if (!bbra_p->min_flag && !bbra_p->max_flag && assassin->spam_flag().empty())
+			{
+				/* caught scoreless && not tagged as spam, throw it back */
+				continue;
+			}
+			
+			if (bbra_p->bBr[0] == 'b')
+				flag_bucket_only = true;
+			rcpt_list = add_linked_list_item(rcpt_list, bbra_p->bucket); 
+		}
+	} while (bbra_list);
   }
 
-  /* Drop the message into the spam bucket if it's spam */
-  if ( flag_bucket ) {
-        if (!assassin->spam_flag().empty()) {
-          // first, add the spambucket address
-          if ( smfi_addrcpt( ctx, spambucket ) != MI_SUCCESS ) {
-                throw string( "Failed to add spambucket to recipients" );
-          }
-          if (flag_bucket_only) {
-                // Move recipients to a non-active header, one at a
-                // time. Note, this may generate multiple X-Spam-Orig-To
+  if (rcpt_list) { 
+	
+	do {
+		char * rcpt;
+
+		rcpt_list = popdel_linked_list_item(rcpt_list,(void **) &rcpt, NULL);
+		if (rcpt && smfi_addrcpt(ctx, rcpt) != MI_SUCCESS ) {
+			throw string( "Failed to add spambucket to recipients");
+		}
+		if (rcpt)
+		{
+			free(rcpt); /* this is a copy */
+			rcpt = NULL;
+		}
+	} while (rcpt_list);
+		
+	if (flag_bucket_only) {
+		// Move recipients to a non-active header, one at a
+		// time. Note, this may generate multiple X-Spam-Orig-To
                 // headers, but that's okay.
                 while( !assassin->recipients.empty()) {
                   if ( smfi_addheader( ctx, "X-Spam-Orig-To", (char *)assassin->recipients.front().c_str()) != MI_SUCCESS ) {
@@ -542,10 +727,9 @@
                   }
                   assassin->recipients.pop_front();
                 }
-          }
         }
   }
-
+  
   update_or_insert(assassin, ctx, assassin->spam_report(), &SpamAssassin::set_spam_report, "X-Spam-Report");
   update_or_insert(assassin, ctx, assassin->spam_prev_content_type(), &SpamAssassin::set_spam_prev_content_type, "X-Spam-Prev-Content-Type");
   update_or_insert(assassin, ctx, assassin->spam_level(), &SpamAssassin::set_spam_level, "X-Spam-Level");
@@ -1972,26 +2156,22 @@
 		vsyslog(LOG_ERR, fmt, vl);
 		va_end(vl);
 #else
-#if defined(HAVE_VASPRINTF)
+#if defined(HAVE_VSNPRINTF)
 		char *buf;
 #else
 		char buf[1024];
 #endif
 		va_list vl;
 		va_start(vl, fmt);
-#if defined(HAVE_VASPRINTF)
-		vasprintf(&buf, fmt, vl);
-#else
 #if defined(HAVE_VSNPRINTF)
-		vsnprintf(buf, sizeof(buf)-1, fmt, vl);
+		vasprintf(&buf, fmt, vl);
 #else
 		/* XXX possible buffer overflow here; be careful what you pass to debug() */
 		vsprintf(buf, fmt, vl);
 #endif
-#endif
 		va_end(vl);
 		syslog(LOG_ERR, "%s", buf);
-#if defined(HAVE_VASPRINTF)
+#if defined(HAVE_VSNPRINTF)
 		free(buf);
 #endif 
 #endif /* vsyslog */
@@ -2048,6 +2228,258 @@
 
 }
 
+/*
+ * This supports syntaxes like these:
+ * -b"<=20>=10,spambucket@myhost.com"
+ * -b">10,spambucket@myhost.com"
+ * -b">=10,spambucket@myhost.com"
+ * -b">=10<=20,spambucket@myhost.com"
+ * -b"<20>10,spambucket@myhost.com"
+ * -b"<20>10,spambucket@myhost.com;<30>40,spambucket@myhost.com"
+ * -b"\<spambucket@myhost.com\>"
+ * -b"\<spam1@myhost.com\> \<spam2@yourhost.com\>"
+ * -r"20,Depart from here spammer trash"
+ * -r '-1'
+ * -r20
+ * -r"20Go away\, now please"
+ * -r"disappear"
+ *
+ * -b and -B are interchangeable except -B means remove all 
+ *  real recipients.
+ *
+ * -r and -s are interchangeable except -s means discard the
+ *  message. While spamass-milter supports a discard message,
+ *  sendmail doesnt, so the sender will not see any supplied 
+ *  mesage.
+ *  
+ *  The argument value can be concatenated with a ';' and will
+ *  be interpreted as if two of the same options were given.
+ *  
+ * The charachter [<>,;] should be escaped with \ unless they
+ * mean score is less than, score is greater than or start of
+ * rejection message/bucket destination or additional argument 
+ * , repsectively.
+ */
+struct bBrAction parse_bBr_arg(char opt, char * opt_str, char **end_p)
+{
+	struct bBrAction bbra;
+	char * gt_str = NULL;
+	char * lt_str = NULL;
+	char * bucket_str = NULL;
+	int i;
+	int in_escape = 0;
+	char *p, *q;
+	char end_p_sav = '\0';
+
+	memset(&bbra, '\0', sizeof(bbra));
+	bbra.bBr[0] = opt;
+	bbra.bBr[1] = '\0'; /* not strictly neccessary */
+
+	p = opt_str;
+	i = 0;
+	/* find first unescaped <>,*/ 
+	while (p && *p)
+	{
+		int in_escape = 0;
+
+		if(*p == '>' && !gt_str && !in_escape)
+			gt_str = p + 1;
+		else if (*p == '<' && !lt_str && !in_escape)
+			lt_str = p + 1;
+		else if (*p == ',' && !bucket_str && !in_escape)
+			bucket_str = p + 1;
+		else if (*p == ';' && end_p && !in_escape)
+		{
+			*end_p = p + 1;
+			end_p_sav = *p;
+			*p = '\0';
+			break;
+		}
+		else if (*p == '\\' && !in_escape)
+			in_escape = 1;
+		else
+			in_escape = 0;
+		p++;
+	}
+
+	if (end_p && !end_p_sav)
+		*end_p = NULL;
+
+	if (gt_str)
+	{
+		int str_eq = 0;
+		
+		if (*(gt_str) == '=')
+		{
+			str_eq = 1;
+			gt_str++;
+		}
+		bbra.min_score = strtol(gt_str, &p, 0);
+		bbra.min_score += (str_eq) ? 1 : 0;
+		if (p != gt_str)
+			bbra.min_flag = 1;
+	}
+
+	if (lt_str)
+	{
+		int str_eq = 0;
+		
+		if (*(lt_str) == '=')
+		{
+			str_eq = 1;
+			lt_str++;
+		}
+		bbra.max_score = strtol(lt_str, &p, 0);
+		bbra.max_score -= (str_eq) ? 0 : 1;
+		if (p != lt_str)
+			bbra.max_flag = 1;
+	}
+		
+	
+	if (!gt_str && !lt_str && (p = opt_str))
+	{
+		/* score may be there without <=>*/
+#if BBR_REQUIRE_COMMA
+		/* no score if no ',' unless option is -r*/
+		if (opt == 'r' || bucket_str)
+#endif
+			bbra.min_score = strtol(p, &p, 0);
+		/*only -r supports legacy -1 argument*/
+		if (p != opt_str && (opt != 'r' || bbra.min_score != -1))
+			bbra.min_flag = 1;
+#if !BBR_REQUIRE_COMMA
+		/* XXX no requirement for comma to seperate score,bucket*/
+		if (!bucket_str)
+			bucket_str = p;
+	}
+	else if (!bucket_str && opt_str)
+	{
+		/* no unescaped ',' in optarg */
+		/* advance pointer past all numeric stuff */
+		p = (gt_str > lt_str) ? gt_str : lt_str;
+		(void) strtol(p, &q, 0);
+		if (q)
+			bucket_str = q;
+#endif
+	}
+		
+	/* strip escaping */
+	p = q = bucket_str;
+	i = 0;
+	while (p && p[i])
+	{
+		in_escape = 0;
+		if ((p[i]) == '\\' && !in_escape)	
+		{
+			if (p[i+1] == 'n')
+			{
+				*q = '\n';
+				i++; q++;
+			}
+			else
+				in_escape = 1;
+		}
+		else
+		{
+			in_escape = 0;
+			*q = *(p + i); 
+			q++;
+		}
+		i++;
+	}
+	*q = '\0';
+	bbra.bucket = p;
+
+	if (end_p_sav && end_p && *end_p)
+		*(*end_p - 1) = end_p_sav;
+		
+	return(bbra);
+}
+
+/* this is a noop function, usefull only for debugging.
+ * You might need to ensure un-optimized compilation.
+ * you would examine the contents of bbra after the call to memcpy()
+ */
+static void
+bBr_print_list (struct linked_list * bbra_list)
+{
+	struct bBrAction bbra;
+
+	bbra_list = rewind_linked_list(bbra_list);
+
+	while (bbra_list)
+	{
+		memcpy(&bbra, linked_list_data(bbra_list,NULL), sizeof(bbra));
+		bbra_list = next_linked_list_item(bbra_list);
+	}
+}
+
+int bBr_cmp(void * p1, void * p2)
+{
+	struct bBrAction * bbra1 = (struct bBrAction *)p1;
+	struct bBrAction * bbra2 = (struct bBrAction *)p2;
+
+	char * s1 = bbra1->bucket;
+	char * s2 = bbra2->bucket;
+	int max1 = bbra1->max_score;
+	int max2 = bbra2->max_score;
+	int min1 = bbra1->min_score;
+	int min2 = bbra2->min_score;
+	int min1_f = bbra1->min_flag;
+	int min2_f = bbra2->min_flag;
+	int max1_f = bbra1->max_flag;
+	int max2_f = bbra2->max_flag;
+	int score2 = bbra2->score;
+	int score1 = bbra1->score;
+	int score1_f = bbra1->score_flag;
+	int score2_f = bbra2->score_flag;
+	char * bBr1 = bbra1->bBr;
+	char * bBr2 = bbra2->bBr;
+	int result_flag = 0;
+	int i, j;
+
+	for (i = 0; bBr2[i]; i++)
+	{
+		/* see if any of the actions match */
+		for (j = 0; bBr1[j]; j++)
+		{
+			if (bBr1[j] == bBr2[i])
+			{
+				result_flag = 1;
+				break;
+			}
+		}
+		if (result_flag)
+			break;
+	}		
+				
+	if (result_flag)
+	{
+		if (s2) /* compare bucket strings */
+			return (strcasecmp(s1,s2));
+
+		if (score2 || score2_f) /* comparing score */
+		{
+			if ((score2 <= max1 && max1_f == score2_f) || max1_f < score2_f)
+			{
+				if ((score2 >= min1 && min1_f == score2_f) || min1_f < score2_f)
+					return 0; /* match */
+			}
+			if (score2 == score1 && score1_f == score2_f)
+					return 0; /* match */
+			return 1;
+		}
+		/* comparing minmin maxmax pairs */	
+		if (!((max1 <= max2) && (max1_f == max2_f)) || !(max1_f < max2_f))
+			return 1;
+		if (!((min1 >= min2) && (min1_f == min2_f)) || !(min1_f < min2_f))
+			return 1;
+
+		return 0; /* match */
+	}
+	return 1;
+
+}
 /* closeall() - close all FDs >= a specified value */ 
 void closeall(int fd) 
 {
diff -urN spamass-milt/spamass-milter.h spamass-milt-1027-bbra5/spamass-milter.h
--- spamass-milt/spamass-milter.h	2005-04-06 22:04:24.000000000 -0400
+++ spamass-milt-1027-bbra5/spamass-milter.h	2006-10-27 10:34:32.000000000 -0400
@@ -70,6 +70,19 @@
 };
 
 
+struct bBrAction
+{
+	char * bucket;
+	int max_score;
+	int max_flag;
+	int min_score;
+	int min_flag;
+	int score;
+	int score_flag;
+	char bBr[5];
+};
+
+
 // Debug tokens.
 enum debuglevel 
 {
@@ -185,6 +198,8 @@
 int ip_in_networklist(struct in_addr ip, struct networklist *list);
 void parse_debuglevel(char* string);
 char *strlwr(char *str);
+int bBr_cmp(void *, void *);
+struct bBrAction parse_bBr_arg(char opt, char * optarg, char **end_p);
 void warnmacro(char *macro, char *scope);
 
 #endif
diff -urN spamass-milt/vsnprintf_alloc.c spamass-milt-1027-bbra5/vsnprintf_alloc.c
--- spamass-milt/vsnprintf_alloc.c	1969-12-31 19:00:00.000000000 -0500
+++ spamass-milt-1027-bbra5/vsnprintf_alloc.c	2006-10-27 10:31:33.000000000 -0400
@@ -0,0 +1,191 @@
+#ifdef  __cplusplus
+extern "C" {
+#endif
+#include "vsnprintf_alloc.h"
+
+
+
+int
+vsnprintf_alloc(char **p, size_t len, const char * fmt, va_list argp)
+{
+	char	*buf = NULL;
+	char	*bufp = NULL;
+	short	we_malloced = 0;
+	int	retlen = 0;
+	size_t	print_len = (len) ? len : strlen(fmt) + BUFSIZ;
+
+	if(!fmt || !p || len < 0)
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	if(*p)
+	{
+		buf = realloc(*p, print_len + 1);
+		if(!buf)
+			return -1;
+		*p = buf;
+	}
+	else
+	{
+		if((buf = malloc(print_len + 1)))
+			we_malloced = 1;
+		else
+			return -1;
+	}
+
+	while(1)	
+	{
+		retlen  = vsnprintf(buf, print_len + 1, fmt, argp);
+		
+		buf[print_len] = '\0';
+			
+		if(retlen == -1)
+		{
+			if(errno == EINVAL)
+			{
+				if(we_malloced)
+					free(buf);
+				else
+					*p = buf;
+				errno = EINVAL;
+				return -1;
+			}
+			/* For optimization, switch this to doubling? */
+			print_len += BUFSIZ;
+		} 
+		else if(retlen > print_len) 
+		{
+			/* C99 */
+			print_len = retlen;
+		}
+		else
+		{
+			/* Sucess */
+			break;
+		}
+
+		if(len && print_len > len)
+			print_len = len;
+		
+		bufp = realloc(buf, print_len + 1);
+		if(!bufp)
+		{
+			if(we_malloced)
+				free(buf);
+			else
+				*p = buf;
+			return -1;
+		}
+		else
+			buf = bufp;
+	}
+
+	
+	*p = buf;
+	return(retlen);
+}
+
+int
+vsprintf_alloc(char **p, const char * fmt, va_list argp)
+{
+	if(!fmt || !p)
+	{	
+		errno = EINVAL;
+		return -1;
+	}
+
+	return(vsnprintf_alloc(p, 0, fmt, argp));
+}	
+
+int
+snprintf_alloc(char **p, size_t len, const char * fmt, ...)
+{
+	int retlen = 0;
+	va_list argp;
+
+	if(!fmt || !p || len < 0)
+	{	
+		errno = EINVAL;
+		return -1;
+	}
+
+	va_start(argp, fmt);
+	retlen = vsnprintf_alloc(p, len, fmt, argp);
+	va_end(argp);
+
+	return(retlen);
+}	
+
+int
+sprintf_alloc(char **p, const char * fmt, ...)
+{
+	int retlen = 0;
+	va_list argp;
+
+	if(!fmt || !p)
+	{	
+		errno = EINVAL;
+		return -1;
+	}
+
+	va_start(argp, fmt);
+	retlen = vsprintf_alloc(p, fmt, argp);
+	va_end(argp);
+
+	return(retlen);
+}	
+
+struct vsnprintf_alloc_cfg *
+vsnprintf_alloc_configure(struct vsnprintf_alloc_cfg * cfg)
+{
+
+}
+
+#if !HAVE_ASPRINTF
+int
+asprintf(char **p, const char * fmt, ...)
+{
+	int retlen = 0;
+	char * buf = NULL;
+	va_list argp;
+
+	if(!fmt || !p)
+	{	
+		errno = EINVAL;
+		return -1;
+	}
+
+	va_start(argp, fmt);
+	retlen = vsprintf_alloc(&buf, fmt, argp);
+	if(retlen >= 0)
+		*p = buf;
+	va_end(argp);
+
+	return(retlen);
+}
+#endif /* !HAVE_ASPRINTF */
+
+#if !HAVE_VASPRINTF
+int
+vasprintf(char **p, const char * fmt, va_list argp)
+{
+	char * buf = NULL;
+	int retlen = 0;
+
+	if(!fmt || !p)
+	{	
+		errno = EINVAL;
+		return -1;
+	}
+
+	retlen = vsnprintf_alloc(&buf, 0, fmt, argp);
+	if(retlen >= 0)
+		*p = buf;
+	return(retlen);
+}	
+#endif /* !HAVE_VASPRINTF */ 
+#ifdef  __cplusplus
+}
+#endif
diff -urN spamass-milt/vsnprintf_alloc.h spamass-milt-1027-bbra5/vsnprintf_alloc.h
--- spamass-milt/vsnprintf_alloc.h	1969-12-31 19:00:00.000000000 -0500
+++ spamass-milt-1027-bbra5/vsnprintf_alloc.h	2006-10-27 10:31:33.000000000 -0400
@@ -0,0 +1,96 @@
+#ifndef VSNPRINTF_ALLOC_H
+	#define VSNPRINTF_ALLOC_H
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+#ifndef HAVE_VASPRINTF
+	#include "config.h"
+#endif
+
+#ifndef BUFSIZ
+	#define BUFSIZ	4096
+#endif
+
+/*
+ * Takes: address of pointer to char, optional maximum length, printf(3) format string, and a variable list of arguments
+ * Returns: -1 for error or number of bytes (that would have been) printed into string.
+ * 
+ * The address of pointer to char MUST not be null. 
+ *
+ * If the pointer to char is NOT null, it MUST be a valid malloc'd memory string.
+ * 
+ * if pointer to char is null, malloc will be called and the address stored back in the passed in addressed pointer
+ * otherwise realloc will be used as needed on the passed in pointer address with the result stored back in.
+ *
+ * 
+ * Passing in a NULL address or a negative length or a NULL format string 
+ * OR
+ * Running out of memory will return -1.  
+ *
+ * Doing the former will result in errno == EINVAL. 
+ * Presumably the latter will result in errno == ENOMEM.
+ *
+ * On error the pointer to char will be valid if it was originaly supplied by you and you must free it.
+ *
+ * Passing in a length of zero will allow the function to allocate memory until the entire string is printed.
+ * Otherwise it will only allocate and print into the string up to the size specified, including terminating '\0'.
+ *
+ * 
+ */
+int
+snprintf_alloc(char **p, size_t len, const char *fmt, ...);
+
+/*
+ * Same as above except 0 is assumed for length
+ */
+int
+sprintf_alloc(char **p, const char *fmt, ...);
+
+/*
+ * same as snprintf_alloc except you pass it argp instead of variable list of arguments
+ */
+int
+vsnprintf_alloc(char **p, size_t len, const char *fmt, va_list argp);
+
+/*
+ * same as above except 0 is assumed for length
+ */
+int
+vsprintf_alloc(char **p, const char *fmt, va_list argp);
+
+#if !HAVE_ASPRINTF
+/*
+ * like sprintf_alloc except does not support privately allocated memory. Also does not require *p to be NULL/
+ * On error *p should NOT be used and will probably be NULL.
+ *
+ * WARNING: Passing in address of valid pointer for p will lead to memory leaks! 
+ */
+int
+asprintf(char **p, const char *fmt, ...);
+#endif /* !HAVE_ASPRINF */
+
+#if !HAVE_VASPRINTF
+/*
+ * like vsprintf_alloc except does not support privately allocated memory. Also does not require *p to be NULL/
+ * On error *p should NOT be used and will probably be NULL.
+ *
+ * WARNING: Passing in address of valid pointer for p will lead to memory leaks! 
+ */
+int
+vasprintf(char **p, const char *fmt, va_list argp);
+#endif /* !HAVE_VASPRINF */
+
+
+struct vsnprintf_alloc_cfg 
+{
+	void (*malloc) (size_t);
+	void (*free) (void *, size_t);
+	short 	multiply;
+	short	assertive;
+	size_t	internal_limit;	
+	short	free_on_error;
+};
+
+#endif /* VSNPRINTF_ALLOC_H */
