diff -ur spamass-milt.orig/spamass-milter.cpp spamass-milt.Rcpt/spamass-milter.cpp
--- spamass-milt.orig/spamass-milter.cpp	2004-11-11 16:19:27.000000000 -0500
+++ spamass-milt.Rcpt/spamass-milter.cpp	2004-11-15 01:34:34.000000000 -0500
@@ -148,13 +148,16 @@
 
 const char *const debugstrings[] = {
 	"ALL", "FUNC", "POLL", "UORI", "STR", "MISC", "NET", "SPAMC", "RCPT",
-	"COPY",
+	"COPY", "RFIL",
 	NULL
 };
 
 int flag_debug = (1<<D_ALWAYS);
 bool flag_reject = false;
 int reject_score = -1;
+bool flag_rcpt_filter = false;
+char *rcpt_filter_program = NULL;
+bool flag_rcpt_loop = true;
 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;
@@ -180,7 +185,7 @@
 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:LmMp:P:r:R:u:D:i:b:B:e:x";
    char *sock = NULL;
    bool dofork = false;
    char *pidfilename = NULL;
@@ -212,6 +217,9 @@
 				debug(D_MISC, "Parsing ignore list");
 				parse_networklist(optarg, &ignorenets);
 				break;
+			case 'L':
+				flag_rcpt_loop = false;
+				break;
 			case 'm':
 				dontmodifyspam = true;
 				smfilter.xxfi_flags &= ~SMFIF_CHGBODY;
@@ -231,6 +239,10 @@
 				flag_reject = true;
 				reject_score = atoi(optarg);
 				break;
+			case 'R':
+				flag_rcpt_filter = true;
+				rcpt_filter_program = optarg;
+				break;
 			case 'u':
 				flag_sniffuser = true;
 				defaultuser = strdup(optarg);
@@ -279,8 +291,9 @@
       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 << "                      [-e defaultdomain] [-f] [-i networks] [-m] [-M]" << endl;
+      cout << "                      [-e defaultdomain] [-f] [-i networks] [-L] [-m] [-M]" << endl;
       cout << "                      [-P pidfile] [-r nn] [-u defaultuser] [-x]" << endl;
+      cout << "                      [-R recipient_filter_prog]" << 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;
@@ -293,11 +306,13 @@
       cout << "   -f: fork into background" << endl;
       cout << "   -i: skip (ignore) checks from these IPs or netblocks" << endl;
       cout << "          example: -i 192.168.12.5,10.0.0.0/8,172.16/255.255.0.0" << endl;
+      cout << "   -L: Turn of loop detection of spambucket addresses." << endl;
       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 << "   -R cmd: filter out recipients with zero exit status."<< 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;
@@ -408,6 +423,7 @@
 int 
 assassinate(SMFICTX* ctx, SpamAssassin* assassin)
 {
+  struct context *sctx = (struct context*)smfi_getpriv(ctx);
   // find end of header (eol in last line of header)
   // and beginning of body
   string::size_type eoh1 = assassin->d().find("\n\n");
@@ -448,6 +464,10 @@
 				do_reject = true;
 		}
 	}
+
+	if (sctx->filtered) //Rcpt Filtering
+		do_reject = false;
+	 
 	if (do_reject)
 	{
 		debug(D_MISC, "Rejecting");
@@ -702,6 +722,9 @@
 
 	/* allocate a structure to store the IP address (and SA object) in */
 	sctx = (struct context *)malloc(sizeof(*sctx));
+	if(!sctx)
+		return SMFIS_TEMPFAIL;
+	memset(sctx, '\0', sizeof(*sctx)); 
 	if (!hostaddr)
 	{
 		/* not a socket; probably a local user calling sendmail directly */
@@ -713,6 +736,8 @@
 	}
 	sctx->assassin = NULL;
 	sctx->helo = NULL;
+	sctx->rcpts = 0;
+	sctx->filtered = 0;
 	
 	/* store a pointer to it with setpriv */
 	smfi_setpriv(ctx, sctx);
@@ -808,6 +833,37 @@
 
 	debug(D_FUNC, "mlfi_envrcpt: enter");
 
+	/*
+	Here is where we prevent loops of processing emails destined only
+	to the spambucket(s).
+
+	Also see address expansion.
+
+	If you are already filtering the spambucket rcpts with the -R flag
+	or through sendmail milter rulesets than you want to use the -L flag. 
+	*/
+	if(flag_bucket && flag_rcpt_loop && 
+	    (!cmp_email_addr(spambucket,envrcpt[0]))
+	   )
+	{
+		debug(D_FUNC, "mlfi_envrcpt: early exit");
+		// leaving now does not increase sctx->rcpts
+		// which means early milter SMFIS_ACCEPT if
+		// there are no other un-filtered rcpts
+		// 
+		// this avoids loops, but means that mail sent 
+		// only to the spambucket will not be processed
+		return SMFIS_CONTINUE;
+	}
+	
+	if((flag_rcpt_filter && rcpt_filter_program) || flag_rcpt_loop)
+		if(rcpt_filter(ctx, envrcpt))
+		{
+			debug(D_FUNC, "mlfi_envrcpt: early exit");
+			return SMFIS_CONTINUE;
+		}	
+
+
 	if (flag_expand)
 	{
 		/* open a pipe to sendmail so we can do address expansion */
@@ -855,10 +911,22 @@
 				*/
 				if (strstr(buf, "... deliverable: mailer "))
 				{
-					char *p=strstr(buf,", user ");
+					char *p=strstr(buf,", user ")+7;
+					
 					/* anything after ", user " is the email address */
-					debug(D_RCPT, "user: %s", p+7);
-					assassin->expandedrcpt.push_back(p+7);
+					debug(D_RCPT, "user: %s", p);
+
+					if (flag_bucket && !cmp_email_addr(spambucket,p))
+						continue; //has to be before rcpt_filter
+					
+					if (((flag_rcpt_filter && rcpt_filter_program ) ||
+						flag_rcpt_loop) &&
+					    !(strcasecmp(p, envrcpt[0])) &&
+					    rcpt_filter (ctx, &p)
+					   )
+						continue;
+					
+					assassin->expandedrcpt.push_back(p);
 				}
 			}
 			pclose(p); p = NULL;
@@ -953,6 +1021,115 @@
 	return SMFIS_CONTINUE;
 }
 
+bool
+rcpt_filter(SMFICTX* ctx, char** envrcpt)
+{
+	struct context *sctx = (struct context*)smfi_getpriv(ctx);
+	char * queue_id = smfi_getsymval(ctx,"i");
+	int rcpt_filter_result = 1;
+	int size = 0;
+	char *tmp_rcpt = NULL;
+	int tmp_rcpt_borrowed = 0;
+	int child_pid = 0;
+	int child_status = 0;
+	char *child_argv[3];
+	
+	debug(D_FUNC,"%s: rcpt_filter: enter", queue_id);	
+	
+	sctx->rcpts++; // when this is positive SA is called.
+	// Must get here if neither -R or -L is flagged....
+	// XXX Might be better to integrate this with assassion->set_rcpts()
+	
+	if(!flag_rcpt_filter || !rcpt_filter_program)
+		return false;
+	
+	debug(D_RFIL,"%s: rcpt_filter: filtering",queue_id);
+	
+	if((tmp_rcpt = strdup(envrcpt[0])) == NULL)
+		return false;
+ 	
+	size = strlen(tmp_rcpt);
+	if(tmp_rcpt[size-1] == '>')
+	{
+		tmp_rcpt[size-1] = '\0';
+	}
+	if(tmp_rcpt[0] == '<')
+	{
+		tmp_rcpt_borrowed = true;
+		tmp_rcpt++;
+	}
+	
+	debug(D_RFIL,"%s: rcpt_filter: tmp_rcpt <%s>",queue_id, tmp_rcpt);
+
+	switch((child_pid = fork()))
+	{
+		case 0:
+			// Child
+			child_argv[0] = rcpt_filter_program;
+			child_argv[1] = tmp_rcpt;
+			child_argv[2] = NULL;	
+			
+			debug(D_RFIL,"%s: rcpt_filter: Child here",queue_id);
+			
+			execvp(child_argv[0], child_argv);
+			_exit(errno);
+			break;;
+		case -1:
+			// Failed
+			debug(D_RFIL,"%s: rcpt_filter: fork failed: %d", queue_id, errno);
+			break;
+		default:
+			// Parent
+			if(waitpid(child_pid, &child_status, 0)<0)
+				debug(D_RFIL, "%s: rcpt_filter: waitpid failed: %d", queue_id, errno);
+			if(WIFEXITED(child_status))
+			{
+				rcpt_filter_result = WEXITSTATUS(child_status);
+				debug(D_RFIL,"%s: rcpt_filter: Child exit status %d",queue_id, rcpt_filter_result);
+			}
+			else
+				debug(D_RFIL,"%s: rcpt_filter: Child exited abnormally",queue_id);
+			
+			break;
+	}
+	
+	if(tmp_rcpt_borrowed)
+		tmp_rcpt--;
+	free(tmp_rcpt);
+	
+	if(!rcpt_filter_result)
+	{
+		debug(D_RFIL,"%s: rcpt_filter, removing rcpt", queue_id);
+		sctx->rcpts--; //filter recipient
+		sctx->filtered++; //negate any possible rejection
+		return true;
+	} else
+		debug(D_RFIL,"%s: rcpt_filter, leaving rcpt", queue_id);
+
+	debug(D_FUNC,"%s: rcpt_filter: leaving normally", queue_id);	
+	return false;
+}
+
+static bool
+rcpt_filter_chk(SMFICTX* ctx, char *f_n)
+{
+	struct context * sctx = (struct context *) smfi_getpriv(ctx);
+	char * queue_id = smfi_getsymval(ctx,"i");
+	
+	if((flag_rcpt_filter || flag_rcpt_loop) && !sctx->rcpts) 
+	{	//sctx->rcpts is handled by rcpt_filter 
+		debug(D_RFIL,"%s: rcpt_filter, accepting message without processing", queue_id);
+		debug(D_FUNC,"%s: %s: rcpt_filter leave", f_n, queue_id);
+		mlfi_abort(ctx); // For lack of specific cleanup 
+		return true;
+	}
+	else
+	{
+		debug(D_RFIL,"%s: rcpt_filter, processing message", queue_id);
+		return false;
+	}
+}
+//
 //
 // Gets called repeatedly for all header fields
 //
@@ -972,6 +1149,11 @@
   SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
   debug(D_FUNC, "mlfi_header: enter");
 
+  if ( !(assassin->connected) && rcpt_filter_chk(ctx,"mlfi_header"))
+  { //Only once, before SPAMC connects 
+	return SMFIS_ACCEPT;
+  }
+
   // Check if the SPAMC program has already been run, if not we run it.
   if ( !(assassin->connected) )
      {
@@ -1060,6 +1242,11 @@
   SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
 
   debug(D_FUNC, "mlfi_eoh: enter");
+  
+  if ( !(assassin->connected) && rcpt_filter_chk(ctx,"mlfi_eoh"))
+  { //Only once, before SPAMC connects 
+	return SMFIS_ACCEPT;
+  }
 
   // Check if the SPAMC program has already been run, if not we run it.
   if ( !(assassin->connected) )
@@ -1180,7 +1367,10 @@
   if (sctx == NULL)
   {
     /* the context should have been set in mlfi_connect */
-  	debug(D_ALWAYS, "NULL context in mlfi_close! Should not happen!");
+    /* it is perfectly valid for mlfi_connect to have not been
+       called by sendmail. see 
+       http://www.milter.org/milter_api/xxfi_close.html
+    */
     return SMFIS_ACCEPT;
   }
   if (sctx->helo)
@@ -1203,8 +1393,12 @@
   SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
 
   debug(D_FUNC, "mlfi_abort");
-  ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
-  delete assassin;
+  if(assassin)
+  {
+  	((struct context *)smfi_getpriv(ctx))->assassin=NULL;
+  	delete assassin;
+  } else
+	debug(D_MISC,"%s: mlfi_abort: assassin is NULL!",smfi_getsymval(ctx,"i"));
 
   return SMFIS_ACCEPT;
 }
@@ -2075,5 +2269,63 @@
     return str;
 }
 
+int
+cmp_email_addr(char *addr1,char *addr2)
+{
+	/* we strdup() so that we can toy with global strings
+	   without thread locking issues.
+	*/
+	char *s1 = NULL;
+	char *s2 = NULL;
+	int s1_borrowed = 0;
+	int s2_borrowed = 0;
+	int ret_status = 0;
+
+	debug(D_FUNC,"cmp_email_addr: enter");
+	if(addr1)
+		s1 = strdup(addr1);
+	if(addr2)
+		s2 = strdup(addr2);
+	debug(D_RCPT,"cmp_email_addr: \"%s\" and \"%s\"",(s1)?s1:"NULL",(s2)?s2:"NULL");
+	
+	/* XXX no way to show malloc fail*/
+	if( (!s1) && (s2) )
+	    return 1;
+	if( (!s1) && (!s2))
+	    return 0;
+	if( (s1) && (!s2) )
+	    return -1;
+
+	/*de bracketize. XXX fails if trailing chars*/
+	if(s1[0] == '<') {
+	   s1_borrowed = 1;
+	   s1++;
+	}
+	if(s2[0] == '<') {
+	   s2_borrowed = 1;
+	   s2++;
+	}
+	
+	if(s1[strlen(s1)-1] == '>') 
+	   s1[strlen(s1)-1] = '\0';
+	if(s2[strlen(s2)-1] == '>') 
+	   s2[strlen(s2)-1] = '\0';
+
+	ret_status = strcasecmp(s1,s2);
+
+	if(s1_borrowed)
+	   s1--;
+	if(s2_borrowed)
+	   s2--;
+
+	free(s1);
+	free(s2);
+
+	debug(D_RCPT,"cmp_email_addr: results: %d", ret_status);
+	debug(D_FUNC,"cmp_email_addr: exit");
+
+	return ret_status;
+}
+
 // }}}
 // vim6:ai:noexpandtab
diff -ur spamass-milt.orig/spamass-milter.h spamass-milt.Rcpt/spamass-milter.h
--- spamass-milt.orig/spamass-milter.h	2004-11-11 16:19:27.000000000 -0500
+++ spamass-milt.Rcpt/spamass-milter.h	2004-09-26 15:54:19.000000000 -0400
@@ -74,7 +74,7 @@
 enum debuglevel 
 {
 	D_ALWAYS, D_FUNC, D_POLL, D_UORI, D_STR, D_MISC, D_NET, D_SPAMC, D_RCPT,
-	D_COPY,
+	D_COPY, D_RFIL,
 	D_MAX // must be last
 };
 
@@ -168,6 +168,8 @@
 	struct in_addr connect_ip;	// remote IP address
 	char *helo;
 	SpamAssassin *assassin; // pointer to the SA object if we're processing a message
+	int rcpts; // must be positive if we are to do ANY spamassassin processing
+	int filtered; //when non-zero one or more recipients has been filtered from processing
 };
 
 /* This hack is the only way to call pointers to member functions! */
@@ -185,5 +187,6 @@
 int ip_in_networklist(struct in_addr ip, struct networklist *list);
 void parse_debuglevel(char* string);
 char *strlwr(char *str);
-
+bool rcpt_filter(SMFICTX*, char**);
+int cmp_email_addr(char *, char *);
 #endif
