-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Howdy, as I've written previously, the LEARN and COLLABREPORT commands
are being replaced by a single TELL command.  Here is the latest diff
that implements the TELL command.  I'm pretty happy with everything,
even the C portion, which was broken but I've just discovered the
problem so is now ready to go.  It has the added benefit that it also
fixes some wonkiness on the Solaris platform with the report/revoke
spamc stuffs (probably bad C code).

Feel free to take a look, I probably want to clean up the Client.pm
interface a bit, but other than that I believe it is ready to go.  If I
don't hear anything in the next day or so I'll go ahead and commit.

Michael
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.2 (GNU/Linux)
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org

iD8DBQFCiTOnG4km+uS4gOIRAucJAKCSXJQkyRg0VyE1317UFuHxGnnOIgCfT1yf
GKZUE5ohkKwGGZnn1tz4qp0=
=fFF9
-----END PGP SIGNATURE-----
Index: lib/Mail/SpamAssassin/Client.pm
===================================================================
--- lib/Mail/SpamAssassin/Client.pm     (revision 170485)
+++ lib/Mail/SpamAssassin/Client.pm     (working copy)
@@ -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;
 }
 
 
@@ -393,72 +502,5 @@
   $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;
 

Property changes on: spamc
___________________________________________________________________
Name: svn:ignore
   - Makefile
spamc
sslspamc
libspamc.so
libsslspamc.so
qmail-spamc
config.h
config.log
config.status
version.h

   + Makefile
spamc
sslspamc
libspamc.so
libsslspamc.so
qmail-spamc
config.h
config.log
config.status
version.h
spamc.h


Index: spamc/libspamc.h
===================================================================
--- spamc/libspamc.h    (revision 170485)
+++ spamc/libspamc.h    (working copy)
@@ -80,29 +80,39 @@
 #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
Index: spamc/spamc.c
===================================================================
--- spamc/spamc.c       (revision 170485)
+++ spamc/spamc.c       (working copy)
@@ -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");
                    }
Index: spamc/libspamc.c
===================================================================
--- spamc/libspamc.c    (revision 170485)
+++ spamc/libspamc.c    (working copy)
@@ -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;
+    }
+    else if (sscanf(buf, "DidRemove: %s", didremove_ret) == 1) {
+        if (strstr(didremove_ret, "local")) {
+         *didtellflags |= SPAMC_REMOVE_LOCAL;
        }
-       return EX_OK;
+       if (strstr(didremove_ret, "remote")) {
+         *didtellflags |= SPAMC_REMOVE_REMOTE;
        }
-       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 {
-         libspamc_log(flags, LOG_ERR, "spamd responded with bad Reported state 
'%s'",
-                      buf);
-         return EX_PROTOCOL;
-       }
-       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,202 +1162,51 @@
     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;
+    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);
     }
-    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;
+    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, ",");
        }
-       strcpy(buf + len, "User: ");
-       strcat(buf + len, username);
-       strcat(buf + len, "\r\n");
-       len += strlen(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 ((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, ",");
        }
-
-       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;
-       }
+       strcat(buf + len, "remote");
+      }
+      strcat(buf + len, "\r\n");
+      len += strlen(buf + len);
     }
 
-    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)) {
            _use_msg_for_out(m);
@@ -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) {
@@ -1490,8 +1324,6 @@
     return failureval;
 }
 
-
-
 void message_cleanup(struct message *m)
 {
     if (m->outbuf)
Index: spamd/PROTOCOL
===================================================================
--- spamd/PROTOCOL      (revision 170485)
+++ spamd/PROTOCOL      (working copy)
@@ -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
Index: spamd/spamd.raw
===================================================================
--- spamd/spamd.raw     (revision 170485)
+++ spamd/spamd.raw     (working copy)
@@ -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,21 +1344,27 @@
   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);
 
   if ($opt{'sql-config'} && !defined($current_user)) {
@@ -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,118 +1402,57 @@
     return 0;
   }
 
-  my $status = $spamtest->learn($mail, undef, $isspam, $forget);
-  my $hdr;
+  my @did_set;
+  my @did_remove;
 
-  if ($status->did_learn()) {
-    $hdr .= "Learned: Yes";
+  if ($hdrs->{set_local}) {
+    my $status = $spamtest->learn($mail, undef, ($hdrs->{message_class} eq 
'spam' ? 1 : 0), 0);
+
+    push(@did_set, 'local') if ($status->did_learn());
+    $status->finish();
   }
-  else {
-    $hdr .= "Learned: No";
-  }
 
-  print $client "SPAMD/1.1 $resphash{$resp} $resp\r\n",
-    $hdr . "\r\n\r\n";
+  if ($hdrs->{remove_local}) {
+    my $status = $spamtest->learn($mail, undef, undef, 1);
 
-  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);
-
-  if ($opt{'sql-config'} && !defined($current_user)) {
-    unless (handle_user_sql('nobody')) {
-      service_unavailable_error("Error fetching user preferences via SQL");
-      return 0;
-    }
+    push(@did_remove, 'local') if ($status->did_learn());
+    $status->finish();
   }
 
-  if ($opt{'ldap-config'} && !defined($current_user)) {
-    handle_user_ldap('nobody');
-  }
+  if ($hdrs->{set_remote}) {
+    require Mail::SpamAssassin::Reporter;
+    my $msgrpt = Mail::SpamAssassin::Reporter->new($spamtest, $mail);
 
-  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;
+    push(@did_set, 'remote') if ($msgrpt->report());
   }
 
-  if ( $mail->get_header("X-Spam-Checker-Version") ) {
-    my $new_mail = 
$spamtest->parse($spamtest->remove_spamassassin_markup($mail), 1);
-    $mail->finish();
-    $mail = $new_mail;
-  }
+  if ($hdrs->{remove_remote}) {
+    require Mail::SpamAssassin::Reporter;
+    my $msgrpt = Mail::SpamAssassin::Reporter->new($spamtest, $mail);
 
-  # attempt to fetch the message ids
-  my ($msgid, $rmsgid) = parse_msgids($mail);
-
-  $msgid        ||= "(unknown)";
-  $current_user ||= "(unknown)";
-
-  my $report_type_desc;
-  my $report_type_desc_past;
-
-  if ($report_type == 0) {
-    $report_type_desc = "reporting spam";
-    $report_type_desc_past = "reported spam";
+    push(@did_remove, 'remote') if ($msgrpt->revoke());
   }
-  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}:$>");
-
   my $hdr;
-  my $status = 1;
+  my $info_set_str;
+  my $info_remove_str;
 
-  if ($report_type) {
-    $status = $spamtest->revoke_as_spam($mail);
+  if (scalar(@did_set)) {
+    $hdr .= "DidSet: " . join(',', @did_set) . "\r\n";
+    $info_set_str = " Setting " . join(',', @did_set) . " ";
   }
-  else {
-    $status = $spamtest->report_as_spam($mail);
-  }
 
-  if ($status) {
-    $hdr .= "Reported: Yes";
+  if (scalar(@did_remove)) {
+    $hdr .= "DidRemove: " . join(',', @did_remove) . "\r\n";
+    $info_remove_str = " Removing " . join(',', @did_remove) . " ";
   }
-  else {
-    $hdr .= "Reported: 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: $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,12 +1488,15 @@
     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 'CollabReport-type') {
-      return 0 unless &got_collabreport_type_header($hdrs, $header, $value);
+    elsif ($header eq 'Set') {
+      return 0 unless &got_set_header($hdrs, $header, $value);
     }
+    elsif ($header eq 'Remove') {
+      return 0 unless &got_remove_header($hdrs, $header, $value);
+    }
   }
 
   # avoid too-many-headers DOS attack
@@ -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;
 }
 

Reply via email to