Mailing List Archive

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;
}