You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by pa...@apache.org on 2005/05/18 14:29:42 UTC

svn commit: r170740 - in /spamassassin/trunk: lib/Mail/SpamAssassin/Client.pm spamc/ spamc/libspamc.c spamc/libspamc.h spamc/spamc.c spamd/PROTOCOL spamd/spamd.raw

Author: parker
Date: Wed May 18 05:29:42 2005
New Revision: 170740

URL: http://svn.apache.org/viewcvs?rev=170740&view=rev
Log:
Implement TELL spamd protocol command and remove LEARN and
COLLABREPORT commands.  Also convert the spamc -L and -C options over
to use the new TELL interface.

Modified:
    spamassassin/trunk/lib/Mail/SpamAssassin/Client.pm
    spamassassin/trunk/spamc/   (props changed)
    spamassassin/trunk/spamc/libspamc.c
    spamassassin/trunk/spamc/libspamc.h
    spamassassin/trunk/spamc/spamc.c
    spamassassin/trunk/spamd/PROTOCOL
    spamassassin/trunk/spamd/spamd.raw

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Client.pm
URL: http://svn.apache.org/viewcvs/spamassassin/trunk/lib/Mail/SpamAssassin/Client.pm?rev=170740&r1=170739&r2=170740&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Client.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Client.pm Wed May 18 05:29:42 2005
@@ -219,10 +219,27 @@
 
   my $msgsize = length($msg.$EOL);
 
-  print $remote "LEARN $PROTOVERSION$EOL";
+  print $remote "TELL $PROTOVERSION$EOL";
   print $remote "Content-length: $msgsize$EOL";
   print $remote "User: $self->{username}$EOL" if ($self->{username});
-  print $remote "Learn-type: $learntype$EOL";
+
+  if ($learntype == 0) {
+    print $remote "Message-class: spam$EOL";
+    print $remote "Set: local$EOL";
+  }
+  elsif ($learntype == 1) {
+    print $remote "Message-class: ham$EOL";
+    print $remote "Set: local$EOL";
+  }
+  elsif ($learntype == 2) {
+    print $remote "Remove: local$EOL";
+  }
+  else { # bad learntype
+    $self->{resp_code} = 00;
+    $self->{resp_msg} = 'do not know';
+    return undef;
+  }
+
   print $remote "$EOL";
   print $remote $msg;
   print $remote "$EOL";
@@ -236,17 +253,19 @@
 
   return undef unless ($resp_code == 0);
 
-  my $learned_p = 0;
   my $found_blank_line_p = 0;
 
+  my $did_set;
+  my $did_remove;
+
   while (!$found_blank_line_p) {
     $line = <$remote>;
 
-    if ($line =~ /Learned: yes/i) {
-      $learned_p = 1;
+    if ($line =~ /DidSet: (.*)/i) {
+      $did_set = $1;
     }
-    elsif ($line =~ /Learned: no/i) {
-      $learned_p = 0;
+    elsif ($line =~ /DidRemove: (.*)/i) {
+      $did_remove = $1;
     }
     elsif ($line =~ /$EOL/) {
       $found_blank_line_p = 1;
@@ -255,7 +274,12 @@
 
   close $remote;
 
-  return $learned_p;
+  if ($learntype == 0 || $learntype == 1) {
+    return $did_set =~ /local/;
+  }
+  else { #safe since we've already checked the $learntype values
+    return $did_remove =~ /local/;
+  }
 }
 
 =head2 report
@@ -270,7 +294,49 @@
 sub report {
   my ($self, $msg) = @_;
 
-  return $self->_report_or_revoke($msg, 0);
+  $self->_clear_errors();
+
+  my $remote = $self->_create_connection();
+
+  return undef unless ($remote);
+
+  my $msgsize = length($msg.$EOL);
+
+  print $remote "TELL $PROTOVERSION$EOL";
+  print $remote "Content-length: $msgsize$EOL";
+  print $remote "User: $self->{username}$EOL" if ($self->{username});
+  print $remote "Message-class: spam$EOL";
+  print $remote "Set: local,remote$EOL";
+  print $remote "$EOL";
+  print $remote $msg;
+  print $remote "$EOL";
+
+  my $line = <$remote>;
+
+  my ($version, $resp_code, $resp_msg) = $self->_parse_response_line($line);
+
+  $self->{resp_code} = $resp_code;
+  $self->{resp_msg} = $resp_msg;
+
+  return undef unless ($resp_code == 0);
+
+  my $reported_p = 0;
+  my $found_blank_line_p = 0;
+
+  while (!$reported_p && !$found_blank_line_p) {
+    $line = <$remote>;
+
+    if ($line =~ /DidSet:\s+.*remote/i) {
+      $reported_p = 1;
+    }
+    elsif ($line =~ /^$EOL$/) {
+      $found_blank_line_p = 1;
+    }
+  }
+
+  close $remote;
+
+  return $reported_p;
 }
 
 =head2 revoke
@@ -285,7 +351,50 @@
 sub revoke {
   my ($self, $msg) = @_;
 
-  return $self->_report_or_revoke($msg, 1);
+  $self->_clear_errors();
+
+  my $remote = $self->_create_connection();
+
+  return undef unless ($remote);
+
+  my $msgsize = length($msg.$EOL);
+
+  print $remote "TELL $PROTOVERSION$EOL";
+  print $remote "Content-length: $msgsize$EOL";
+  print $remote "User: $self->{username}$EOL" if ($self->{username});
+  print $remote "Message-class: ham$EOL";
+  print $remote "Set: local$EOL";
+  print $remote "Remove: remote$EOL";
+  print $remote "$EOL";
+  print $remote $msg;
+  print $remote "$EOL";
+
+  my $line = <$remote>;
+
+  my ($version, $resp_code, $resp_msg) = $self->_parse_response_line($line);
+
+  $self->{resp_code} = $resp_code;
+  $self->{resp_msg} = $resp_msg;
+
+  return undef unless ($resp_code == 0);
+
+  my $revoked_p = 0;
+  my $found_blank_line_p = 0;
+
+  while (!$revoked_p && !$found_blank_line_p) {
+    $line = <$remote>;
+
+    if ($line =~ /DidRemove:\s+remote/i) {
+      $revoked_p = 1;
+    }
+    elsif ($line =~ /^$EOL$/) {
+      $found_blank_line_p = 1;
+    }
+  }
+
+  close $remote;
+
+  return $revoked_p;
 }
 
 
@@ -392,73 +501,6 @@
   $self->{resp_code} = undef;
   $self->{resp_msg} = undef;
 }
-
-
-=head2 _report_or_revoke
-
-public instance (Boolean) report_or_revoke (String $msg, Integer $reporttype)
-
-Description:
-This method implements the report or revoke call.  C<$learntype> should
-be an integer, 0 for report or 1 for revoke.  The return value is a
-boolean indicating if the message was learned or not.
-
-An undef return value indicates that there was an error and you
-should check the resp_code/resp_error values to determine what
-the error was.
-
-=cut
-
-sub _report_or_revoke {
-  my ($self, $msg, $reporttype) = @_;
-
-  $self->_clear_errors();
-
-  my $remote = $self->_create_connection();
-
-  return undef unless ($remote);
-
-  my $msgsize = length($msg.$EOL);
-
-  print $remote "COLLABREPORT $PROTOVERSION$EOL";
-  print $remote "Content-length: $msgsize$EOL";
-  print $remote "User: $self->{username}$EOL" if ($self->{username});
-  print $remote "CollabReport-type: $reporttype$EOL";
-  print $remote "$EOL";
-  print $remote $msg;
-  print $remote "$EOL";
-
-  my $line = <$remote>;
-
-  my ($version, $resp_code, $resp_msg) = $self->_parse_response_line($line);
-
-  $self->{resp_code} = $resp_code;
-  $self->{resp_msg} = $resp_msg;
-
-  return undef unless ($resp_code == 0);
-
-  my $reported_p = 0;
-  my $found_blank_line_p = 0;
-
-  while (!$found_blank_line_p) {
-    $line = <$remote>;
-
-    if ($line =~ /Reported: yes/i) {
-      $reported_p = 1;
-    }
-    elsif ($line =~ /Reported: no/i) {
-      $reported_p = 0;
-    }
-    elsif ($line =~ /$EOL/) {
-      $found_blank_line_p = 1;
-    }
-  }
-
-  close $remote;
-
-  return $reported_p;
-}
-
 
 1;
 

Propchange: spamassassin/trunk/spamc/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Wed May 18 05:29:42 2005
@@ -8,3 +8,4 @@
 config.log
 config.status
 version.h
+spamc.h

Modified: spamassassin/trunk/spamc/libspamc.c
URL: http://svn.apache.org/viewcvs/spamassassin/trunk/spamc/libspamc.c?rev=170740&r1=170739&r2=170740&view=diff
==============================================================================
--- spamassassin/trunk/spamc/libspamc.c (original)
+++ spamassassin/trunk/spamc/libspamc.c Wed May 18 05:29:42 2005
@@ -766,12 +766,12 @@
 
 static int
 _handle_spamd_header(struct message *m, int flags, char *buf, int len,
-		     int *actionok)
+		     int *didtellflags)
 {
     char is_spam[6];
     char s_str[21], t_str[21];
-    char is_learned[4];
-    char is_reported[4];
+    char didset_ret[15];
+    char didremove_ret[15];
 
     UNUSED_VARIABLE(len);
 
@@ -818,36 +818,23 @@
 	}
 	return EX_OK;
     }
-    else if (sscanf(buf, "Learned: %3s", is_learned) == 1) {
-        if(strcmp(is_learned, "yes") == 0 || strcmp(is_learned, "Yes") == 0) {
-	  *actionok = 1;
+    else if (sscanf(buf, "DidSet: %s", didset_ret) == 1) {
+      if (strstr(didset_ret, "local")) {
+	  *didtellflags |= SPAMC_SET_LOCAL;
 	}
-	else if(strcmp(is_learned, "no") == 0 || strcmp(is_learned, "No") == 0) {
-	  *actionok = 0;
+	if (strstr(didset_ret, "remote")) {
+	  *didtellflags |= SPAMC_SET_REMOTE;
 	}
-	else {
-	  libspamc_log(flags, LOG_ERR, "spamd responded with bad Learned state '%s'",
-		       buf);
-	  return EX_PROTOCOL;
-	}
-	return EX_OK;
-	}
-	else if (sscanf(buf, "Reported: %3s", is_reported) == 1) {
-        if(strcmp(is_reported, "yes") == 0 || strcmp(is_reported, "Yes") == 0) {
-	  *actionok = 1;
-	}
-	else if(strcmp(is_reported, "no") == 0 || strcmp(is_reported, "No") == 0) {
-	  *actionok = 0;
+    }
+    else if (sscanf(buf, "DidRemove: %s", didremove_ret) == 1) {
+        if (strstr(didremove_ret, "local")) {
+	  *didtellflags |= SPAMC_REMOVE_LOCAL;
 	}
-	else {
-	  libspamc_log(flags, LOG_ERR, "spamd responded with bad Reported state '%s'",
-		       buf);
-	  return EX_PROTOCOL;
+	if (strstr(didremove_ret, "remote")) {
+	  *didtellflags |= SPAMC_REMOVE_REMOTE;
 	}
-	return EX_OK;
     }
 
-    /* skip any other headers that may be locally defined */
     return EX_OK;
 }
 
@@ -1122,8 +1109,9 @@
     }
 }
 
-int message_learn(struct transport *tp, const char *username, int flags,
-		  struct message *m, int learntype, int *islearned)
+int message_tell(struct transport *tp, const char *username, int flags,
+		 struct message *m, int msg_class,
+		 uint tellflags, uint *didtellflags)
 {
     char buf[8192];
     size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
@@ -1131,7 +1119,6 @@
     int sock = -1;
     int rc;
     char versbuf[20];
-    char strlearntype[1];
     float version;
     int response;
     int failureval;
@@ -1163,7 +1150,7 @@
     m->out_len = 0;
 
     /* Build spamd protocol header */
-    strcpy(buf, "LEARN ");
+    strcpy(buf, "TELL ");
 
     len = strlen(buf);
     if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
@@ -1175,201 +1162,50 @@
     strcat(buf, "\r\n");
     len = strlen(buf);
 
-    if ((learntype > 2) | (learntype < 0 )) {
-      free(m->out);
-      m->out = m->msg;
-      m->out_len = m->msg_len;
-      return EX_OSERR;
-    }
-    sprintf(strlearntype,"%d",learntype);
-    strcpy(buf + len, "Learn-type: ");
-    strcat(buf + len, strlearntype);
-    strcat(buf + len, "\r\n");
-    len += strlen(buf + len);
-
-    if (username != NULL) {
-	if (strlen(username) + 8 >= (bufsiz - len)) {
-	    _use_msg_for_out(m);
-	    return EX_OSERR;
-	}
-	strcpy(buf + len, "User: ");
-	strcat(buf + len, username);
-	strcat(buf + len, "\r\n");
-	len += strlen(buf + len);
+    if (msg_class != 0) {
+      strcpy(buf + len, "Message-class: ");
+      if (msg_class == SPAMC_MESSAGE_CLASS_SPAM) {
+	strcat(buf + len, "spam\r\n");
+      }
+      else {
+	strcat(buf + len, "ham\r\n");
+      }
+      len += strlen(buf + len);
+    }
+
+    if ((tellflags & SPAMC_SET_LOCAL) || (tellflags & SPAMC_SET_REMOTE)) {
+      int needs_comma_p = 0;
+      strcat(buf + len, "Set: ");
+      if (tellflags & SPAMC_SET_LOCAL) {
+	strcat(buf + len, "local");
+	needs_comma_p = 1;
+      }
+      if (tellflags & SPAMC_SET_REMOTE) {
+	if (needs_comma_p == 1) {
+	  strcat(buf + len, ",");
+	}
+	strcat(buf + len, "remote");
+      }
+      strcat(buf + len, "\r\n");
+      len += strlen(buf + len);
+    }
+
+    if ((tellflags & SPAMC_REMOVE_LOCAL) || (tellflags & SPAMC_REMOVE_REMOTE)) {
+      int needs_comma_p = 0;
+      strcat(buf + len, "Remove: ");
+      if (tellflags & SPAMC_REMOVE_LOCAL) {
+	strcat(buf + len, "local");
+	needs_comma_p = 1;
+      }
+      if (tellflags & SPAMC_REMOVE_REMOTE) {
+	if (needs_comma_p == 1) {
+	  strcat(buf + len, ",");
+	}
+	strcat(buf + len, "remote");
+      }
+      strcat(buf + len, "\r\n");
+      len += strlen(buf + len);
     }
-    if ((m->msg_len > 9999999) || ((len + 27) >= (bufsiz - len))) {
-	_use_msg_for_out(m);
-	return EX_OSERR;
-    }
-    len += sprintf(buf + len, "Content-length: %d\r\n\r\n", m->msg_len);
-
-    libspamc_timeout = m->timeout;
-
-    if (tp->socketpath)
-	rc = _try_to_connect_unix(tp, &sock);
-    else
-	rc = _try_to_connect_tcp(tp, &sock);
-
-    if (rc != EX_OK) {
-	_use_msg_for_out(m);
-	return rc;      /* use the error code try_to_connect_*() gave us. */
-    }
-
-    if (flags & SPAMC_USE_SSL) {
-#ifdef SPAMC_SSL
-	ssl = SSL_new(ctx);
-	SSL_set_fd(ssl, sock);
-	SSL_connect(ssl);
-#endif
-    }
-
-    /* Send to spamd */
-    if (flags & SPAMC_USE_SSL) {
-#ifdef SPAMC_SSL
-	SSL_write(ssl, buf, len);
-	SSL_write(ssl, m->msg, m->msg_len);
-#endif
-    }
-    else {
-	full_write(sock, 0, buf, len);
-	full_write(sock, 0, m->msg, m->msg_len);
-	shutdown(sock, SHUT_WR);
-    }
-
-    /* ok, now read and parse it.  SPAMD/1.2 line first... */
-    failureval =
-	_spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
-    if (failureval != EX_OK) {
-	goto failure;
-    }
-
-    if (sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response) != 2) {
-	libspamc_log(flags, LOG_ERR, "spamd responded with bad string '%s'", buf);
-	failureval = EX_PROTOCOL;
-	goto failure;
-    }
-
-    versbuf[19] = '\0';
-    version = _locale_safe_string_to_float(versbuf, 20);
-    if (version < 1.0) {
-	libspamc_log(flags, LOG_ERR, "spamd responded with bad version string '%s'",
-	       versbuf);
-	failureval = EX_PROTOCOL;
-	goto failure;
-    }
-
-    m->score = 0;
-    m->threshold = 0;
-    m->is_spam = EX_TOOBIG;
-    *islearned = 0;
-    while (1) {
-	failureval =
-	    _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
-	if (failureval != EX_OK) {
-	    goto failure;
-	}
-
-	if (len == 0 && buf[0] == '\0') {
-	    break;		/* end of headers */
-	}
-
-	if (_handle_spamd_header(m, flags, buf, len, islearned) < 0) {
-	    failureval = EX_PROTOCOL;
-	    goto failure;
-	}
-    }
-
-    len = 0;			/* overwrite those headers */
-
-    shutdown(sock, SHUT_RD);
-    closesocket(sock);
-    sock = -1;
-
-    libspamc_timeout = 0;
-
-    return EX_OK;
-
-  failure:
-    _use_msg_for_out(m);
-    if (sock != -1) {
-        closesocket(sock);
-    }
-    libspamc_timeout = 0;
-
-    if (flags & SPAMC_USE_SSL) {
-#ifdef SPAMC_SSL
-	SSL_free(ssl);
-	SSL_CTX_free(ctx);
-#endif
-    }
-    return failureval;
-}
-
-
-int message_collabreport(struct transport *tp, const char *username, int flags,
-			 struct message *m, int reporttype, int *isreported)
-{
-    char buf[8192];
-    size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
-    size_t len;
-    int sock = -1;
-    int rc;
-    char versbuf[20];
-    char strreporttype[1];
-    float version;
-    int response;
-    int failureval;
-    SSL_CTX *ctx = NULL;
-    SSL *ssl = NULL;
-    SSL_METHOD *meth;
-
-    if (flags & SPAMC_USE_SSL) {
-#ifdef SPAMC_SSL
-        SSLeay_add_ssl_algorithms();
-	meth = SSLv2_client_method();
-	SSL_load_error_strings();
-	ctx = SSL_CTX_new(meth);
-#else
-	UNUSED_VARIABLE(ssl);
-	UNUSED_VARIABLE(meth);
-	UNUSED_VARIABLE(ctx);
-	libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
-	return EX_SOFTWARE;
-#endif
-    }
-
-    m->is_spam = EX_TOOBIG;
-    if ((m->outbuf = malloc(m->max_len + EXPANSION_ALLOWANCE + 1)) == NULL) {
-	failureval = EX_OSERR;
-	goto failure;
-    }
-    m->out = m->outbuf;
-    m->out_len = 0;
-
-    /* Build spamd protocol header */
-    strcpy(buf, "COLLABREPORT ");
-
-    len = strlen(buf);
-    if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
-	_use_msg_for_out(m);
-	return EX_OSERR;
-    }
-
-    strcat(buf, PROTOCOL_VERSION);
-    strcat(buf, "\r\n");
-    len = strlen(buf);
-
-    if ((reporttype > 2) | (reporttype < 0 )) {
-        free(m->out);
-        m->out = m->msg;
-        m->out_len = m->msg_len;
-        return EX_OSERR;
-    }
-    sprintf(strreporttype,"%d",reporttype);
-    strcpy(buf + len, "CollabReport-type: ");
-    strcat(buf + len, strreporttype);
-    strcat(buf + len, "\r\n");
-    len += strlen(buf + len);
 
     if (username != NULL) {
 	if (strlen(username) + 8 >= (bufsiz - len)) {
@@ -1437,7 +1273,7 @@
     version = _locale_safe_string_to_float(versbuf, 20);
     if (version < 1.0) {
 	libspamc_log(flags, LOG_ERR, "spamd responded with bad version string '%s'",
-		     versbuf);
+	       versbuf);
 	failureval = EX_PROTOCOL;
 	goto failure;
     }
@@ -1445,7 +1281,6 @@
     m->score = 0;
     m->threshold = 0;
     m->is_spam = EX_TOOBIG;
-    *isreported = 0;
     while (1) {
 	failureval =
 	    _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
@@ -1457,7 +1292,7 @@
 	    break;		/* end of headers */
 	}
 
-	if (_handle_spamd_header(m, flags, buf, len, isreported) < 0) {
+	if (_handle_spamd_header(m, flags, buf, len, didtellflags) < 0) {
 	    failureval = EX_PROTOCOL;
 	    goto failure;
 	}
@@ -1478,7 +1313,6 @@
     if (sock != -1) {
         closesocket(sock);
     }
-
     libspamc_timeout = 0;
 
     if (flags & SPAMC_USE_SSL) {
@@ -1489,8 +1323,6 @@
     }
     return failureval;
 }
-
-
 
 void message_cleanup(struct message *m)
 {

Modified: spamassassin/trunk/spamc/libspamc.h
URL: http://svn.apache.org/viewcvs/spamassassin/trunk/spamc/libspamc.h?rev=170740&r1=170739&r2=170740&view=diff
==============================================================================
--- spamassassin/trunk/spamc/libspamc.h (original)
+++ spamassassin/trunk/spamc/libspamc.h Wed May 18 05:29:42 2005
@@ -80,28 +80,38 @@
 #define SPAMC_RAW_MODE       0
 #define SPAMC_BSMTP_MODE     1
 
-#define SPAMC_USE_SSL	     (1<<27)
-#define SPAMC_SAFE_FALLBACK  (1<<28)
-#define SPAMC_CHECK_ONLY     (1<<29)
+#define SPAMC_USE_SSL	      (1<<27)
+#define SPAMC_SAFE_FALLBACK   (1<<28)
+#define SPAMC_CHECK_ONLY      (1<<29)
 
 /* Jan 30, 2003 ym: added reporting options */
-#define SPAMC_REPORT         (1<<26)
-#define SPAMC_REPORT_IFSPAM  (1<<25)
+#define SPAMC_REPORT          (1<<26)
+#define SPAMC_REPORT_IFSPAM   (1<<25)
 
 /* Feb  1 2003 jm: might as well fix bug 191 as well */
-#define SPAMC_SYMBOLS        (1<<24)
+#define SPAMC_SYMBOLS         (1<<24)
 
 /* 2003/04/16 SJF: randomize hostname order (quasi load balancing) */
 #define SPAMC_RANDOMIZE_HOSTS (1<<23)
 
 /* log to stderr */
-#define SPAMC_LOG_TO_STDERR  (1<<22)
+#define SPAMC_LOG_TO_STDERR   (1<<22)
 
 /* Nov 24, 2004 NP: added learning support */
-#define SPAMC_LEARN	     (1<<21)
+#define SPAMC_LEARN	      (1<<21)
 
 /* May 5, 2005 NP: added list reporting support */
-#define SPAMC_COLLABREPORT   (1<<20)
+#define SPAMC_REPORT_MSG      (1<<20)
+
+
+#define SPAMC_MESSAGE_CLASS_SPAM 1
+#define SPAMC_MESSAGE_CLASS_HAM  2
+
+#define SPAMC_SET_LOCAL          1
+#define SPAMC_SET_REMOTE         2
+
+#define SPAMC_REMOVE_LOCAL       4
+#define SPAMC_REMOVE_REMOTE      8
 
 /* Aug 14, 2002 bj: A struct for storing a message-in-progress */
 typedef enum
@@ -214,21 +224,14 @@
 int message_filter(struct transport *tp, const char *username,
 		   int flags, struct message *m);
 
-/* Process the message through the spamd learn, making as many connection
- * attempts as are implied by the transport structure. To make this do
- * failover, more than one host is defined, but if there is only one there,
- * no failover is done.
- */
-int message_learn(struct transport *tp, const char *username, int flags,
-		  struct message *m, int learntype, int *islearned);
-
-/* Process the message through the spamd collandreport, making as many
+/* Process the message through the spamd tell command, making as many
  * connection attempts as are implied by the transport structure. To make
  * this do failover, more than one host is defined, but if there is only
  * one there, no failover is done.
  */
-int message_collabreport(struct transport *tp, const char *username, int flags,
-			 struct message *m, int reporttype, int *isreported);
+int message_tell(struct transport *tp, const char *username, int flags,
+		 struct message *m, int msg_class,
+		 uint tellflags, uint *didtellflags);
 
 /* Dump the message. If there is any data in the message (typically, m->type
  * will be MESSAGE_ERROR) it will be message_writed. Then, fd_in will be piped

Modified: spamassassin/trunk/spamc/spamc.c
URL: http://svn.apache.org/viewcvs/spamassassin/trunk/spamc/spamc.c?rev=170740&r1=170739&r2=170740&view=diff
==============================================================================
--- spamassassin/trunk/spamc/spamc.c (original)
+++ spamassassin/trunk/spamc/spamc.c Wed May 18 05:29:42 2005
@@ -311,7 +311,7 @@
 	    }
         case 'C':
 	    {
-	        flags |= SPAMC_COLLABREPORT;
+	        flags |= SPAMC_REPORT_MSG;
 		if (strcmp(optarg,"report") == 0) {
 		    *extratype = 0;
 		}
@@ -383,7 +383,7 @@
 	    libspamc_log(flags, LOG_ERR, "Learning excludes symbols");
 	    ret = EX_USAGE;
 	}
-	if (flags & SPAMC_COLLABREPORT) {
+	if (flags & SPAMC_REPORT_MSG) {
 	    libspamc_log(flags, LOG_ERR, "Learning excludes reporting to collaborative filtering databases");
 	    ret = EX_USAGE;
 	}
@@ -757,10 +757,70 @@
 	if (ret == EX_OK) {
 
  	    if (flags & SPAMC_LEARN) {
-	      ret = message_learn(&trans, username, flags, &m, extratype, &islearned);
+	      int msg_class = 0;
+	      uint tellflags = 0;
+	      uint didtellflags = 0;
+
+	      if ((extratype == 0) || (extratype == 1)) {
+		if (extratype == 0) {
+		  msg_class = SPAMC_MESSAGE_CLASS_SPAM;
+		}
+		else {
+		  msg_class = SPAMC_MESSAGE_CLASS_HAM;
+		}
+		tellflags |= SPAMC_SET_LOCAL;
+	      }
+	      else {
+		tellflags |= SPAMC_REMOVE_LOCAL;
+	      }
+
+	      ret = message_tell(&trans, username, flags, &m, msg_class,
+				 tellflags, &didtellflags);
+
+	      if (ret == EX_OK) {
+		if ((extratype == 0) || (extratype == 1)) {
+		  if (didtellflags & SPAMC_SET_LOCAL) {
+		    islearned = 1;
+		  }
+		}
+		else {
+		  if (didtellflags & SPAMC_REMOVE_LOCAL) {
+		    islearned = 1;
+		  }
+		}
+	      }
 	    }
- 	    else if (flags & SPAMC_COLLABREPORT) {
-	      ret = message_collabreport(&trans, username, flags, &m, extratype, &isreported);
+ 	    else if (flags & SPAMC_REPORT_MSG) {
+	      int msg_class = 0;
+	      uint tellflags = 0;
+	      uint didtellflags = 0;
+
+	      if (extratype == 0) {
+		msg_class = SPAMC_MESSAGE_CLASS_SPAM;
+		tellflags |= SPAMC_SET_REMOTE;
+		tellflags |= SPAMC_SET_LOCAL;
+	      }
+	      else {
+		msg_class = SPAMC_MESSAGE_CLASS_HAM;
+		tellflags |= SPAMC_SET_LOCAL;
+		tellflags |= SPAMC_REMOVE_REMOTE;
+	      }
+
+	      ret = message_tell(&trans, username, flags, &m, msg_class,
+				 tellflags, &didtellflags);
+
+	      if (ret == EX_OK) {
+		if (extratype == 0) {
+		  if (didtellflags & SPAMC_SET_REMOTE) {
+		    isreported = 1;
+		  }
+		}
+		else {
+		  if (didtellflags & SPAMC_REMOVE_REMOTE) {
+		    isreported = 1;
+		  }
+		}
+	      }
 	    }
 	    else {
 	      ret = message_filter(&trans, username, flags, &m);
@@ -782,7 +842,7 @@
 		    message_cleanup(&m);
 		    goto finish;
 		}
-		else if (flags & SPAMC_COLLABREPORT) {
+		else if (flags & SPAMC_REPORT_MSG) {
 		    if (isreported == 1) {
   		        printf("Message successfully reported/revoked\n");
 		    }

Modified: spamassassin/trunk/spamd/PROTOCOL
URL: http://svn.apache.org/viewcvs/spamassassin/trunk/spamd/PROTOCOL?rev=170740&r1=170739&r2=170740&view=diff
==============================================================================
--- spamassassin/trunk/spamd/PROTOCOL (original)
+++ spamassassin/trunk/spamd/PROTOCOL Wed May 18 05:29:42 2005
@@ -56,10 +56,10 @@
 PROCESS       --  Process this message as described above and return modified
                   message
 
-LEARN         --  Learn message as spam, ham or forget.
+TELL          --  Tell what type of we are to process and what should be done
+                  with that message.  This includes setting or removing a local
+                  or a remote database (learning, reporting, forgetting, revoking).
 
-COLLABREPORT  --  Send message to any configured collaborative reporting
-		  databses
 
 CHECK command returns just a header (terminated by "\r\n\r\n") with the first
 line as for PROCESS (ie a response code and message), and then a header called
@@ -108,10 +108,6 @@
 REPORT_IFSPAM returns the same as REPORT if the message is spam, or nothing at
 all if the message is non-spam.
 
-LEARN returns just a header, Learned:, with a Yes or a No value.
-
-COLLABREPORT returns just a header, Reported:, with a Yes or a No value
-
 The PING command does not actually trigger any spam checking, and (as with
 SKIP) no additional input is expected. It returns a simple confirmation
 response, like this:
@@ -121,3 +117,28 @@
 This facility may be useful for monitoring programs which wish to check that
 the daemon is alive and providing at least a basic response within a reasonable
 time frame.
+
+TELL accepts three new headers, Message-class, Set and Remove and will return
+two possible headers, DidSet and DidRemove which indicate which action was
+taken.  It is up to the caller to determine if the proper action happened.  Here
+are some examples:
+
+To learn a message as spam:
+TELL SPAMC/1.3
+Message-class: spam
+Set: local
+
+To forget a learned message:
+TELL SPAMC/1.3
+Remove: local
+
+To report a spam message:
+TELL SPAMC/1.3
+Message-class: spam
+Set: local, remove
+
+To revoke a ham message:
+TELL SPAMC/1.3
+Message-class: spam
+Set: local
+Remove: remote

Modified: spamassassin/trunk/spamd/spamd.raw
URL: http://svn.apache.org/viewcvs/spamassassin/trunk/spamd/spamd.raw?rev=170740&r1=170739&r2=170740&view=diff
==============================================================================
--- spamassassin/trunk/spamd/spamd.raw (original)
+++ spamassassin/trunk/spamd/spamd.raw Wed May 18 05:29:42 2005
@@ -1047,30 +1047,6 @@
     info(sprintf("spamd: skipped large message in %3d seconds", time - $start));
   }
 
-  # COLLABREPORT must come before REPORT, since the regex is overgenerous
-  elsif (/(COLLABREPORT) SPAMC\/(.*)/) {
-    my $method = $1;
-    my $version = $2;
-    eval {
-      Mail::SpamAssassin::Util::trap_sigalrm_fully(sub {
-						     die "child processing timeout";
-						   });
-      alarm $timeout_child if ($timeout_child);
-      report($method, $version, $start, $remote_hostname, $remote_hostaddr);
-    };
-    alarm 0;
-
-    if ($@) {
-      if ($@ =~ /child processing timeout/) {
-        service_timeout("($timeout_child second timeout while trying to $method)");
-      } else {
-	warn "spamd: $@";
-      }
-      $client->close();
-      return 0;
-    }
-  }
-
   # It might be a CHECK message, meaning that we should just check
   # if it's spam or not, then return the appropriate response.
   # If we get the PROCESS command, the client is going to send a
@@ -1098,7 +1074,7 @@
     }
   }
 
-  elsif (/(LEARN) SPAMC\/(.*)/) {
+  elsif (/(TELL) SPAMC\/(.*)/) {
     my $method = $1;
     my $version = $2;
     eval {
@@ -1106,7 +1082,7 @@
 						     die "child processing timeout";
 						   });
       alarm $timeout_child if ($timeout_child);
-      learn($method, $version, $start, $remote_hostname, $remote_hostaddr);
+      dotell($method, $version, $start, $remote_hostname, $remote_hostaddr);
     };
     alarm 0;
 
@@ -1368,20 +1344,26 @@
   return 1;
 }
 
-sub learn {
+sub dotell {
   my ($method, $version, $start_time, $remote_hostname, $remote_hostaddr) = @_;
   local ($_);
   my $expected_length;
-  my $learn_type;
-  my $forget = 0;
-  my $isspam = 1;
 
   my $hdrs = {};
 
   return 0 unless (parse_headers($hdrs, $client));
 
   $expected_length = $hdrs->{expected_length};
-  $learn_type = $hdrs->{learn_type};
+
+  if ($hdrs->{set_local} && $hdrs->{remove_local}) {
+    protocol_error("Unable to set local and remove local in the same operation.");
+    return 0;
+  }
+
+  if ($hdrs->{set_remote} && $hdrs->{remove_remote}) {
+    protocol_error("Unable to set remote and remove remote in the same operation.");
+    return 0;
+  }
 
   &handle_setuid_to_user if ($setuid_to_user && $> == 0);
 
@@ -1413,30 +1395,6 @@
   $msgid        ||= "(unknown)";
   $current_user ||= "(unknown)";
 
-  my $learn_type_desc;
-  my $learn_type_desc_past;
-
-  if ($learn_type == 0) {
-    $learn_type_desc = "learning spam";
-    $learn_type_desc_past = "learned spam";
-    $isspam = 1;
-  }
-  elsif ($learn_type == 1) {
-    $learn_type_desc = "learning ham";
-    $learn_type_desc_past = "learned ham";
-    $isspam = 0;
-  }
-  elsif ($learn_type == 2) {
-    $learn_type_desc = "forgetting";
-    $learn_type_desc_past = "forgot";
-    $forget = 1;
-  }
-
-  info("spamd: $learn_type_desc"
-      . " message $msgid"
-      . ( $rmsgid ? " aka $rmsgid" : "" )
-      . " for ${current_user}:$>");
-
   # Check length if we're supposed to.
   if (defined $expected_length && $actual_length != $expected_length) {
     protocol_error("(Content-Length mismatch: Expected $expected_length bytes, got $actual_length bytes)");
@@ -1444,110 +1402,49 @@
     return 0;
   }
 
-  my $status = $spamtest->learn($mail, undef, $isspam, $forget);
-  my $hdr;
-
-  if ($status->did_learn()) {
-    $hdr .= "Learned: Yes";
-  }
-  else {
-    $hdr .= "Learned: No";
-  }
-
-  print $client "SPAMD/1.1 $resphash{$resp} $resp\r\n",
-    $hdr . "\r\n\r\n";
-
-  my $scantime = sprintf( "%.1f", time - $start_time );
-
-  info("spamd: $learn_type_desc_past message for $current_user:$> in"
-       . " $scantime seconds, $actual_length bytes");
-  $status->finish();    # added by jm to allow GC'ing
-  $mail->finish();
-  return 1;
-}
-
-sub report {
-  my ($method, $version, $start_time, $remote_hostname, $remote_hostaddr) = @_;
-  local ($_);
-  my $expected_length;
-  my $report_type;
-
-  my $hdrs = {};
-
-  return 0 unless (parse_headers($hdrs, $client));
-
-  $expected_length = $hdrs->{expected_length};
-  $report_type = $hdrs->{collabreport_type};
-
-  &handle_setuid_to_user if ($setuid_to_user && $> == 0);
+  my @did_set;
+  my @did_remove;
 
-  if ($opt{'sql-config'} && !defined($current_user)) {
-    unless (handle_user_sql('nobody')) {
-      service_unavailable_error("Error fetching user preferences via SQL");
-      return 0;
-    }
-  }
+  if ($hdrs->{set_local}) {
+    my $status = $spamtest->learn($mail, undef, ($hdrs->{message_class} eq 'spam' ? 1 : 0), 0);
 
-  if ($opt{'ldap-config'} && !defined($current_user)) {
-    handle_user_ldap('nobody');
+    push(@did_set, 'local') if ($status->did_learn());
+    $status->finish();
   }
 
-  my $resp = "EX_OK";
-
-  # generate mail object from input
-  my ($mail, $actual_length) = parse_body($client, $expected_length);
-
-  # Check length if we're supposed to.
-  if (defined $expected_length && $actual_length != $expected_length) {
-    protocol_error("(Content-Length mismatch: Expected $expected_length bytes, got $actual_length bytes)");
-    $mail->finish();
-    return 0;
-  }
+  if ($hdrs->{remove_local}) {
+    my $status = $spamtest->learn($mail, undef, undef, 1);
 
-  if ( $mail->get_header("X-Spam-Checker-Version") ) {
-    my $new_mail = $spamtest->parse($spamtest->remove_spamassassin_markup($mail), 1);
-    $mail->finish();
-    $mail = $new_mail;
+    push(@did_remove, 'local') if ($status->did_learn());
+    $status->finish();
   }
 
-  # attempt to fetch the message ids
-  my ($msgid, $rmsgid) = parse_msgids($mail);
+  if ($hdrs->{set_remote}) {
+    require Mail::SpamAssassin::Reporter;
+    my $msgrpt = Mail::SpamAssassin::Reporter->new($spamtest, $mail);
 
-  $msgid        ||= "(unknown)";
-  $current_user ||= "(unknown)";
+    push(@did_set, 'remote') if ($msgrpt->report());
+  }
 
-  my $report_type_desc;
-  my $report_type_desc_past;
+  if ($hdrs->{remove_remote}) {
+    require Mail::SpamAssassin::Reporter;
+    my $msgrpt = Mail::SpamAssassin::Reporter->new($spamtest, $mail);
 
-  if ($report_type == 0) {
-    $report_type_desc = "reporting spam";
-    $report_type_desc_past = "reported spam";
-  }
-  elsif ($report_type == 1) {
-    $report_type_desc = "revoking ham";
-    $report_type_desc_past = "revoked ham";
-  }
-
-  info("spamd: $report_type_desc"
-      . " message $msgid"
-      . ( $rmsgid ? " aka $rmsgid" : "" )
-      . " for ${current_user}:$>");
+    push(@did_remove, 'remote') if ($msgrpt->revoke());
+  }
 
   my $hdr;
-  my $status = 1;
+  my $info_set_str;
+  my $info_remove_str;
 
-  if ($report_type) {
-    $status = $spamtest->revoke_as_spam($mail);
-  }
-  else {
-    $status = $spamtest->report_as_spam($mail);
+  if (scalar(@did_set)) {
+    $hdr .= "DidSet: " . join(',', @did_set) . "\r\n";
+    $info_set_str = " Setting " . join(',', @did_set) . " ";
   }
 
-  if ($status) {
-    $hdr .= "Reported: Yes";
-  }
-  else {
-    $hdr .= "Reported: No";
+  if (scalar(@did_remove)) {
+    $hdr .= "DidRemove: " . join(',', @did_remove) . "\r\n";
+    $info_remove_str = " Removing " . join(',', @did_remove) . " ";
   }
 
   print $client "SPAMD/1.1 $resphash{$resp} $resp\r\n",
@@ -1555,7 +1452,7 @@
 
   my $scantime = sprintf( "%.1f", time - $start_time );
 
-  info("spamd: $report_type_desc_past message for $current_user:$> in"
+  info("spamd:$info_set_str$info_remove_str for $current_user:$> in"
        . " $scantime seconds, $actual_length bytes");
 
   $mail->finish();
@@ -1571,6 +1468,7 @@
   # max 255 headers
   for my $hcount ( 0 .. 255 ) {
     my $line = $client->getline;
+
     unless (defined $line) {
       protocol_error("(EOF during headers)");
       return 0;
@@ -1590,11 +1488,14 @@
     elsif ($header eq 'User') {
       return 0 unless &got_user_header($hdrs, $header, $value);
     }
-    elsif ($header eq 'Learn-type') {
-      return 0 unless &got_learn_type_header($hdrs, $header, $value);
+    elsif ($header eq 'Message-class') {
+      return 0 unless &got_message_class_header($hdrs, $header, $value);
+    }
+    elsif ($header eq 'Set') {
+      return 0 unless &got_set_header($hdrs, $header, $value);
     }
-    elsif ($header eq 'CollabReport-type') {
-      return 0 unless &got_collabreport_type_header($hdrs, $header, $value);
+    elsif ($header eq 'Remove') {
+      return 0 unless &got_remove_header($hdrs, $header, $value);
     }
   }
 
@@ -1672,33 +1573,49 @@
   return 1;
 }
 
-sub got_learn_type_header {
+sub got_message_class_header {
   my ($hdrs, $header, $value) = @_;
-  if ($value !~ /^(\d*)$/) {
-    protocol_error("(Learn-type contains non-numeric bytes)");
+
+  unless (lc($value) ne 'spam' || lc($value) ne 'ham') {
+    protocol_error("(Message-class header contains invalid class)");
     return 0;
   }
-  my $type = $1;
-  if ($type != 0 && $type != 1 && $type != 2) {
-    protocol_error("(Learn-type contains invalid type)");
-    return 0;
+  $hdrs->{message_class} = $value;
+
+  return 1;
+}
+
+sub got_set_header {
+  my ($hdrs, $header, $value) = @_;
+
+  $hdrs->{set_local} = 0;
+  $hdrs->{set_remote} = 0;
+
+  if ($value =~ /local/i) {
+    $hdrs->{set_local} = 1;
   }
-  $hdrs->{learn_type} = $type;
+
+  if ($value =~ /remote/i) {
+    $hdrs->{set_remote} = 1;
+  }
+
   return 1;
 }
 
-sub got_collabreport_type_header {
+sub got_remove_header {
   my ($hdrs, $header, $value) = @_;
-  if ($value !~ /^(\d*)$/) {
-    protocol_error("(CollabReport-type contains non-numeric bytes)");
-    return 0;
+
+  $hdrs->{remove_local} = 0;
+  $hdrs->{remove_remote} = 0;
+
+  if ($value =~ /local/i) {
+    $hdrs->{remove_local} = 1;
   }
-  my $type = $1;
-  if ($type != 0 && $type != 1) {
-    protocol_error("(CollabReport-type contains invalid type)");
-    return 0;
+
+  if ($value =~ /remote/i) {
+    $hdrs->{remove_remote} = 1;
   }
-  $hdrs->{collabreport_type} = $type;
+
   return 1;
 }