Oh, very very cool ... I've been doign it in postfix's content_filter, but then its doing *everyone*'s email ... this is soooooo much better ...
Any chance we'll see this in CVS sometime soon? Maybe in time for 2.1.4?? On Thu, 25 Apr 2002, Bob Finch wrote: > I've gotten a handful of requests for the "spam" sieve extension I > mentioned yesterday. The diffs aren't too big, so I'll post them to > the list. > > The diffs add a test to sieve that passes the message to SpamAssassin. > If SpamAssassin scores the messages as spam, the test fails, otherwise > the test succeeds. Here's a simple sieve script using the spam > extension: > > require [ "spam", "fileinto" ]; > if spam { > fileinto "spamfolder"; > } > > The diffs are based on cyrus-imapd-2.1.3 -- I haven't tried them with > other versions. You'll also need to install SpamAssassin (see > http://www.spamassassin.org/) and have spamd running. I'm currently > running SpamAssassin 2.11. I've been running it for about a month on > several mailboxes that typically get about 500 messages a day. > > The extension adds three new imapd.conf parameters: > > max_size: 256000 > The spam test will always return false for messages larger than > max_size. Messages larger than max_size will not be passed to > spamd. > > spam_spamd_host: 127.0.0.1 > The IP addresses or hostname of the spamd server. > > spam_spamd_port: 783 > The port number of the spamd server. > > When sieve evaluates the spam test, it calls a callback in > imap/lmptd.c that opens a connection to spamd. It then sends a CHECK > command writes the message to spamd. It parses the response from > spamd and returns the result of the test to sieve. > > Things on my to-do list: > > * Add a configure option and ifdefs to conditionally include the > spam extension > > * Provide a way for users to get more information about how > SpamAssassin scores messages. This will require help from spamd, > since it currently just returns the score and threshold. > > * Documentation > > -- Bob > > > diff -cr cyrus-imapd-2.1.3-orig/imap/lmtpd.c cyrus-imapd-2.1.3/imap/lmtpd.c > *** cyrus-imapd-2.1.3-orig/imap/lmtpd.c Thu Mar 7 09:55:28 2002 > --- cyrus-imapd-2.1.3/imap/lmtpd.c Thu Apr 18 21:14:10 2002 > *************** > *** 111,116 **** > --- 111,124 ---- > > char *authuser; /* user who submitted message */ > struct auth_state *authstate; > + > + /* spam stuff */ > + > + /* This is in script_data, but the spam callback can't get to it */ > + /* so we put a copy here */ > + char *username; /* Username of mailbox */ > + int spam_result_valid; /* != 0 iff spam result is valid */ > + int spam_result; /* != iff message is spam */ > } mydata_t; > > /* data per script */ > *************** > *** 910,915 **** > --- 918,1087 ---- > static char *markflags[] = { "\\flagged" }; > static sieve_imapflags_t mark = { markflags, 1 }; > > + /* spam support */ > + > + static int > + getline (int s, char *buf, int len) > + { > + char *bp = buf; > + int ret = 1; > + char ch; > + > + while ((ret = read (s, &ch, 1)) == 1 && ch != '\n') { > + if (len > 0) { > + *bp++ = ch; > + len--; > + } > + } > + if (len > 0) > + *bp = '\0'; > + return (buf != bp); > + } > + > + > + static int > + full_write (int s, char *buf, int len) > + { > + int total; > + int ret; > + > + for (total = 0; total < len; total += ret) { > + ret = write (s, buf + total, len - total); > + if (ret < 0) > + return 0; > + } > + return total == len; > + } > + > + > + static int > + read_response (int s, int *result) > + { > + char is_spam[6]; > + char buf[1024]; > + int major; > + int minor; > + int response; > + int score; > + int threshold; > + > + if (! getline (s, buf, sizeof (buf))) { > + syslog (LOG_ERR, "read_response: response getline failed"); > + return SIEVE_FAIL; > + } > + if (sscanf (buf, "SPAMD/%d.%d %d %*s", &major, &minor, &response) != 3) { > + syslog (LOG_ERR, "read_response: response sscanf failed, buf: %s", > + buf); > + return SIEVE_FAIL; > + } > + if (major < 1 || (major == 1 && minor < 1)) { > + syslog (LOG_ERR, "read_response: bad spamd version: %d.%d", > + major, minor); > + return SIEVE_FAIL; > + } > + if (! getline (s, buf, sizeof (buf))) { > + syslog (LOG_ERR, "read_response: header getline failed"); > + return SIEVE_FAIL; > + } > + if (sscanf (buf, "Spam: %5s ; %d / %d", is_spam, &score, &threshold) != 3) { > + syslog (LOG_ERR, "read_response: header sscanf failed, buf: %s", > + buf); > + return SIEVE_FAIL; > + } > + > + *result = ! strcmp(is_spam, "True"); > + return SIEVE_OK; > + } > + > + > + int spam (void *mc, int *is_spam) > + { > + mydata_t *d = (mydata_t *) mc; > + message_data_t *m = d->m; > + int s; > + struct sockaddr_in addr; > + struct hostent *host; > + char header[128]; > + int max_size = config_getint ("spam_max_size", 250 * 1024); > + const char *hostname = config_getstring ("spam_spamd_host", "127.0.0.1"); > + int port = config_getint ("spam_spamd_port", 783); > + char *msg_buf; > + int ret; > + > + /* Assume message isn't spam if it is larger than max_size */ > + if (m->size > max_size) { > + syslog (LOG_INFO, "spam: skipping message bigger than %d", max_size); > + return SIEVE_FAIL; > + } > + > + memset (&addr, 0, sizeof(addr)); > + addr.sin_family = AF_INET; > + addr.sin_port = htons(port); > + > + if ((host = gethostbyname (hostname)) == NULL) { > + syslog (LOG_ERR, "spam: gethostbyname failed"); > + return SIEVE_FAIL; > + } > + memcpy (&addr.sin_addr, host->h_addr, sizeof (addr.sin_addr)); > + > + if((s = socket (PF_INET, SOCK_STREAM, 0)) < 0) { > + syslog (LOG_ERR, "spam: socket failed"); > + return SIEVE_FAIL; > + } > + > + if (connect (s, (const struct sockaddr *) &addr, sizeof (addr)) < 0) { > + syslog (LOG_ERR, "spam: connect failed"); > + close (s); > + return SIEVE_FAIL; > + } > + > + if ((msg_buf = malloc (m->size)) == NULL) { > + syslog (LOG_ERR, "spam: malloc(%d) failed", m->size); > + close (s); > + return SIEVE_FAIL; > + } > + rewind (m->f); > + if (fread (msg_buf, 1, m->size, m->f) != m->size || ferror (m->f)) { > + syslog (LOG_ERR, "spam: read message failed"); > + free (msg_buf); > + close (s); > + return SIEVE_FAIL; > + } > + > + if (d->username) { > + snprintf (header, sizeof (header), > + "CHECK SPAMC/1.2\r\nUser: %s\r\nContent-length: %d\r\n\r\n", > + d->username, m->size); > + } > + else { > + snprintf (header, sizeof (header), > + "CHECK SPAMC/1.2\r\nContent-length: %d\r\n\r\n", m->size); > + } > + if (! full_write (s, header, strlen (header))) { > + syslog (LOG_ERR, "spam: write header failed"); > + free (msg_buf); > + close (s); > + return SIEVE_FAIL; > + } > + if (! full_write (s, msg_buf, m->size)) { > + syslog (LOG_ERR, "spam: write message failed"); > + free (msg_buf); > + close (s); > + return SIEVE_FAIL; > + } > + > + shutdown (s, SHUT_WR); > + ret = read_response (s, is_spam); > + shutdown (s, SHUT_RD); > + > + free (msg_buf); > + close (s); > + > + syslog(LOG_DEBUG, "spam result: %d\n", ret); > + return ret; > + } > + > + > int sieve_parse_error_handler(int lineno, const char *msg, void *ic, void *sc) > { > script_data_t *sd = (script_data_t *) sc; > *************** > *** 999,1004 **** > --- 1171,1182 ---- > fatal("sieve_register_vacation()", EC_SOFTWARE); > } > > + res = sieve_register_spam(sieve_interp, &spam); > + if (res != SIEVE_OK) { > + syslog(LOG_ERR, "sieve_register_spam() returns %d\n", res); > + fatal("sieve_register_spam()", EC_SOFTWARE); > + } > + > res = sieve_register_parse_error(sieve_interp, &sieve_parse_error_handler); > if (res != SIEVE_OK) { > syslog(LOG_ERR, "sieve_register_parse_error() returns %d\n", res); > *************** > *** 1148,1154 **** > mydata.notifyheader = generate_notify(msgdata); > mydata.authuser = authuser; > mydata.authstate = authstate; > ! > /* loop through each recipient, attempting delivery for each */ > for (n = 0; n < nrcpts; n++) { > char *rcpt = xstrdup(msg_getrcpt(msgdata, n)); > --- 1326,1335 ---- > mydata.notifyheader = generate_notify(msgdata); > mydata.authuser = authuser; > mydata.authstate = authstate; > ! mydata.username = NULL; > ! mydata.spam_result = 0; > ! mydata.spam_result_valid = 0; > ! > /* loop through each recipient, attempting delivery for each */ > for (n = 0; n < nrcpts; n++) { > char *rcpt = xstrdup(msg_getrcpt(msgdata, n)); > *************** > *** 1187,1192 **** > --- 1368,1376 ---- > sdata->username = rcpt; > sdata->mailboxname = plus; > sdata->authstate = auth_newstate(rcpt, (char *)0); > + > + /* Make a copy of mailbox username for spam stuff */ > + mydata.username = sdata->username; > > /* slap the mailboxname back on so we hash the envelope & id > when we figure out whether or not to keep the message */ > diff -cr cyrus-imapd-2.1.3-orig/sieve/interp.c cyrus-imapd-2.1.3/sieve/interp.c > *** cyrus-imapd-2.1.3-orig/sieve/interp.c Tue Oct 2 14:08:13 2001 > --- cyrus-imapd-2.1.3/sieve/interp.c Sun Mar 24 11:39:14 2002 > *************** > *** 154,159 **** > --- 154,166 ---- > return SIEVE_OK; > } > > + int sieve_register_spam(sieve_interp_t *interp, sieve_spam *f) > + { > + interp->spam = f; > + > + return SIEVE_OK; > + } > + > /* add the callbacks for messages. again, undefined if used after > sieve_script_parse */ > int sieve_register_size(sieve_interp_t *interp, sieve_get_size *f) > diff -cr cyrus-imapd-2.1.3-orig/sieve/interp.h cyrus-imapd-2.1.3/sieve/interp.h > *** cyrus-imapd-2.1.3-orig/sieve/interp.h Mon Feb 21 23:56:40 2000 > --- cyrus-imapd-2.1.3/sieve/interp.h Sun Mar 24 11:40:53 2002 > *************** > *** 35,40 **** > --- 35,41 ---- > sieve_callback *redirect, *discard, *reject, *fileinto, *keep; > sieve_callback *notify; > sieve_vacation_t *vacation; > + sieve_spam *spam; > > sieve_get_size *getsize; > sieve_get_header *getheader; > diff -cr cyrus-imapd-2.1.3-orig/sieve/script.c cyrus-imapd-2.1.3/sieve/script.c > *** cyrus-imapd-2.1.3-orig/sieve/script.c Wed Feb 27 13:05:13 2002 > --- cyrus-imapd-2.1.3/sieve/script.c Thu Apr 18 21:02:51 2002 > *************** > *** 102,107 **** > --- 102,114 ---- > return 1; > } else if (!strcmp("comparator-i;ascii-casemap", req)) { > return 1; > + } else if (!strcmp("spam",req)) { > + if (s->interp.spam) { > + s->support.spam = 1; > + return 1; > + } else { > + return 0; > + } > } > return 0; > } > *************** > *** 361,366 **** > --- 368,381 ---- > res = (sz < t->u.sz.n); > } > break; > + } > + case SPAM: > + { > + int is_spam; > + > + if (i->spam == NULL || i->spam (m, &is_spam) != SIEVE_OK) > + break; > + res = is_spam; > } > } > > diff -cr cyrus-imapd-2.1.3-orig/sieve/script.h cyrus-imapd-2.1.3/sieve/script.h > *** cyrus-imapd-2.1.3-orig/sieve/script.h Wed Feb 9 16:39:14 2000 > --- cyrus-imapd-2.1.3/sieve/script.h Sun Mar 24 11:39:13 2002 > *************** > *** 45,50 **** > --- 45,51 ---- > int notify : 1; > int regex : 1; > int subaddress: 1; > + int spam : 1; > } support; > > void *script_context; > diff -cr cyrus-imapd-2.1.3-orig/sieve/sieve-lex.l cyrus-imapd-2.1.3/sieve/sieve-lex.l > *** cyrus-imapd-2.1.3-orig/sieve/sieve-lex.l Tue Feb 19 10:09:46 2002 > --- cyrus-imapd-2.1.3/sieve/sieve-lex.l Sat Mar 23 18:43:22 2002 > *************** > *** 90,95 **** > --- 90,96 ---- > <INITIAL>header return HEADER; > <INITIAL>not return NOT; > <INITIAL>size return SIZE; > + <INITIAL>spam return SPAM; > <INITIAL>reject return REJCT; > <INITIAL>fileinto return FILEINTO; > <INITIAL>redirect return REDIRECT; > diff -cr cyrus-imapd-2.1.3-orig/sieve/sieve.y cyrus-imapd-2.1.3/sieve/sieve.y > *** cyrus-imapd-2.1.3-orig/sieve/sieve.y Tue Mar 5 08:15:01 2002 > --- cyrus-imapd-2.1.3/sieve/sieve.y Sun Mar 24 11:36:18 2002 > *************** > *** 141,146 **** > --- 141,147 ---- > %token SETFLAG ADDFLAG REMOVEFLAG MARK UNMARK > %token NOTIFY DENOTIFY > %token ANYOF ALLOF EXISTS SFALSE STRUE HEADER NOT SIZE ADDRESS ENVELOPE > + %token SPAM > %token COMPARATOR IS CONTAINS MATCHES REGEX OVER UNDER > %token ALL LOCALPART DOMAIN USER DETAIL > %token DAYS ADDRESSES SUBJECT MIME > *************** > *** 398,403 **** > --- 399,409 ---- > | NOT test { $$ = new_test(NOT); $$->u.t = $2; } > | SIZE sizetag NUMBER { $$ = new_test(SIZE); $$->u.sz.t = $2; > $$->u.sz.n = $3; } > + | SPAM { if (!parse_script->support.spam) { > + yyerror("spam not required"); > + YYERROR; > + } > + $$ = new_test(SPAM); } > | error { $$ = NULL; } > ; > > diff -cr cyrus-imapd-2.1.3-orig/sieve/sieve_interface.h >cyrus-imapd-2.1.3/sieve/sieve_interface.h > *** cyrus-imapd-2.1.3-orig/sieve/sieve_interface.h Tue Feb 19 10:09:46 2002 > --- cyrus-imapd-2.1.3/sieve/sieve_interface.h Sun Mar 24 19:46:44 2002 > *************** > *** 50,55 **** > --- 50,57 ---- > typedef int sieve_get_envelope(void *message_context, > const char *field, > const char ***contents); > + typedef int sieve_spam(void *message_context, int *is_spam); > + > > typedef struct sieve_vacation { > int min_response; /* 0 -> defaults to 3 */ > *************** > *** 121,126 **** > --- 123,129 ---- > int sieve_register_vacation(sieve_interp_t *interp, sieve_vacation_t *v); > int sieve_register_imapflags(sieve_interp_t *interp, sieve_imapflags_t *mark); > int sieve_register_notify(sieve_interp_t *interp, sieve_callback *f); > + int sieve_register_spam(sieve_interp_t *interp, sieve_spam *f); > > /* add the callbacks for messages. again, undefined if used after > sieve_script_parse */ > diff -cr cyrus-imapd-2.1.3-orig/timsieved/scripttest.c >cyrus-imapd-2.1.3/timsieved/scripttest.c > *** cyrus-imapd-2.1.3-orig/timsieved/scripttest.c Sun Dec 17 20:53:43 2000 > --- cyrus-imapd-2.1.3/timsieved/scripttest.c Sun Mar 24 13:12:06 2002 > *************** > *** 171,176 **** > --- 171,182 ---- > return TIMSIEVE_FAIL; > } > > + res = sieve_register_spam(i, (sieve_spam *) &foo); > + if (res != SIEVE_OK) { > + syslog (LOG_ERR, "sieve_register_spam() returns %d\n", res); > + return TIMSIEVE_FAIL; > + } > + > res = sieve_register_parse_error(i, &mysieve_error); > if (res != SIEVE_OK) { > syslog(LOG_ERR, "sieve_register_parse_error() returns %d\n", res); > >