Here is the 21.K patch. Apologies if this makes for an unacceptably large email.
- It adds a new command "folder" which takes a folder as a parameter to "timsieved" which allows a script to be associated with any folder or heirarchy of folders in the imap store. - It alters lmtpd to pick the appropriate sieve script for the folder being delivered to - i.e. the folder described by the left hand side of the email address. The patch is against current CVS (well, CVS of Thursday). It is made from "cvs diff -u <dir>/<file>" - i.e. from the "cyrus-imapd" directory. It is very much a proof of concept. It compiles and does something ;-) - but I wouldn't rely on it to do any more than that. Regards, Ian Index: imap/lmtpd.c =================================================================== RCS file: /cvs/src/cyrus/imap/lmtpd.c,v retrieving revision 1.75 diff -u -r1.75 lmtpd.c --- lmtpd.c 2001/10/02 21:08:10 1.75 +++ lmtpd.c 2001/11/08 18:29:38 @@ -734,8 +734,14 @@ int ret = 1; if (sd->mailboxname) { - strcpy(namebuf, lmtpd_namespace.prefix[NAMESPACE_INBOX]); - strcat(namebuf, sd->mailboxname); + /* Sieving a public or user folder ? */ + if ( strcmp( sd->username, "anyone" ) ) { + strcpy(namebuf, lmtpd_namespace.prefix[NAMESPACE_INBOX]); + strcat(namebuf, sd->mailboxname); + } + else { + strcpy(namebuf, sd->mailboxname); + } ret = deliver_mailbox(md->data, &mydata->stage, md->size, kc->imapflags->flag, kc->imapflags->nflags, @@ -748,7 +754,13 @@ if (!sd->authstate) return SIEVE_FAIL; - strcpy(namebuf, "INBOX"); + /* sieving a public or user folder ? */ + if ( strcmp( sd->username, "anyone" ) ) { + strcpy(namebuf, "INBOX"); + } + else { + strcpy( namebuf, sd->mailboxname ); + } ret = deliver_mailbox(md->data, &mydata->stage, md->size, kc->imapflags->flag, kc->imapflags->nflags, @@ -1018,10 +1030,30 @@ } } +static int mailbox_parent( char *mailboxname ) +{ + char *p; + int r = 1; + p = strrchr ( mailboxname, '.' ); + if ( p ) { + *p = '\0'; + r = 0; + } + return r; +} + + /* returns true if user has a sieve file */ -static FILE *sieve_find_script(const char *user) +static FILE *sieve_find_script(const char *user, char *mailboxname) { char buf[1024]; + char namebuf[MAX_MAILBOX_NAME]; + char namebuf2[MAX_MAILBOX_NAME]; + char script_path[MAX_MAILBOX_PATH]; + const char *sievesystemscripts = config_getstring( "sievedir", "/usr/sieve" ); + int r; + char *path, *acl; + FILE *sfp; if (strlen(user) > 900) { return NULL; @@ -1031,28 +1063,83 @@ /* duplicate delivery suppression is needed for sieve */ return NULL; } - - if (sieve_usehomedir) { /* look in homedir */ - struct passwd *pent = getpwnam(user); - if (pent == NULL) { - return NULL; + if ( strcmp( user, "anyone" ) ) { + if ( mailboxname ) { + strcpy(namebuf, lmtpd_namespace.prefix[NAMESPACE_INBOX]); + strcat(namebuf, mailboxname); } + else { + strcpy( namebuf, "INBOX" ); + } + + } + else { + strcpy(namebuf, lmtpd_namespace.prefix[NAMESPACE_SHARED]); + strcat(namebuf, mailboxname); + } + + mboxname_hiersep_toexternal(&lmtpd_namespace, namebuf); + + r = (*lmtpd_namespace.mboxname_tointernal)(&lmtpd_namespace, namebuf, + user, namebuf2); + + if ( mboxlist_lookup( namebuf2, &path, &acl, NULL ) == 0 ) { + syslog( LOG_INFO, "1 The folder exists" ); + + mailbox_hash_mbox( script_path, sievesystemscripts, namebuf2 ); + + syslog( LOG_INFO, "4 script is %s", script_path ); + } + else { + return NULL; + } + + snprintf(buf, sizeof(buf), "%s/default", script_path ); + + r = 0; + while (!r && ( syslog(LOG_INFO, "5 file is %s", buf ), sfp = fopen(buf, "r")) == +NULL) { + r = mailbox_parent( namebuf2 ); + mailbox_hash_mbox( script_path, sievesystemscripts, namebuf2 ); + snprintf(buf, sizeof(buf), "%s/default", script_path ); + } + + return sfp; - /* check ~USERNAME/.sieve */ - snprintf(buf, sizeof(buf), "%s/%s", pent->pw_dir, ".sieve"); - } else { /* look in sieve_dir */ - char hash; +#if 0 + if ( !strcmp( user, "anyone" ) ) { + const char *sievesystemscripts = config_getstring( "sievedir", "/usr/sieve" ); - hash = (char) dir_hash_c(user); + if ( sievesystemscripts == (const char *) 0 ) { + return NULL; /* Must have a script dir for this to work */ + } - snprintf(buf, sizeof(buf), "%s/%c/%s/default", sieve_dir, hash, user); + snprintf(buf, sizeof(buf), "%s/default", sievesystemscripts ); } + else { + if (sieve_usehomedir) { /* look in homedir */ + struct passwd *pent = getpwnam(user); + + if (pent == NULL) { + return NULL; + } + + /* check ~USERNAME/.sieve */ + snprintf(buf, sizeof(buf), "%s/%s", pent->pw_dir, ".sieve"); + } else { /* look in sieve_dir */ + char hash; + + hash = (char) dir_hash_c(user); + + snprintf(buf, sizeof(buf), "%s/%c/%s/default", sieve_dir, hash, user); + } + } return (fopen(buf, "r")); +#endif } #else /* USE_SIEVE */ -static FILE *sieve_find_script(const char *user) +static FILE *sieve_find_script(const char *user, const char *mboxname ) { return NULL; } @@ -1157,29 +1244,30 @@ for (n = 0; n < nrcpts; n++) { char *rcpt = xstrdup(msg_getrcpt(msgdata, n)); char *plus; + char *script_user; int quotaoverride = msg_getrcpt_ignorequota(msgdata, n); int r = 0; mydata.cur_rcpt = n; plus = strchr(rcpt, '+'); if (plus) *plus++ = '\0'; - /* case 1: shared mailbox request */ - if (plus && !strcmp(rcpt, BB)) { - strcpy(namebuf, lmtpd_namespace.prefix[NAMESPACE_SHARED]); - strcat(namebuf, plus); - r = deliver_mailbox(msgdata->data, - &mydata.stage, - msgdata->size, - NULL, 0, - mydata.authuser, mydata.authstate, - msgdata->id, NULL, mydata.notifyheader, - namebuf, quotaoverride, 0); - } + + if (plus && !strcmp(rcpt, BB)) { + /* case 1: shared mailbox request */ + script_user = "anyone"; + } + else if (!strchr(rcpt, '.') && + strlen(rcpt) + 30 <= MAX_MAILBOX_PATH) { + /* case 2: ordinary user, might have Sieve script */ + script_user = rcpt; + } + else { + /* Make sure that nothing can go wrong! */ + script_user = ""; + } - /* case 2: ordinary user, might have Sieve script */ - else if (!strchr(rcpt, lmtpd_namespace.hier_sep) && - strlen(rcpt) + 30 <= MAX_MAILBOX_PATH) { - FILE *f = sieve_find_script(rcpt); + if ( *script_user ) { + FILE *f = sieve_find_script(script_user, plus); #ifdef USE_SIEVE if (f != NULL) { @@ -1188,9 +1276,9 @@ sdata = (script_data_t *) xmalloc(sizeof(script_data_t)); - sdata->username = rcpt; + sdata->username = script_user; sdata->mailboxname = plus; - sdata->authstate = auth_newstate(rcpt, (char *)0); + sdata->authstate = auth_newstate(script_user, (char *)0); /* slap the mailboxname back on so we hash the envelope & id when we figure out whether or not to keep the message */ @@ -1230,6 +1318,7 @@ /* if there was an error, r is non-zero and we'll do normal delivery */ + } else { /* no sieve script */ r = 1; /* do normal delivery actions */ @@ -1237,7 +1326,26 @@ #else /* USE_SIEVE */ r = 1; /* normal delivery */ #endif /* USE_SIEVE */ - + } + else { + r = 1; + } + +/* Check to see if it was a public folder */ + if ( !strcmp( script_user, "anyone" ) ) { + if ( r ) { + strcpy(namebuf, lmtpd_namespace.prefix[NAMESPACE_SHARED]); + strcat(namebuf, plus); + r = deliver_mailbox(msgdata->data, + &mydata.stage, + msgdata->size, + NULL, 0, + mydata.authuser, mydata.authstate, + msgdata->id, NULL, mydata.notifyheader, + namebuf, quotaoverride, 0); + } + } + else { if (r && plus && strlen(rcpt) + strlen(plus) + 30 <= MAX_MAILBOX_PATH) { /* normal delivery to + mailbox */ @@ -1267,7 +1375,6 @@ namebuf, quotaoverride, 1); } } - donercpt: free(rcpt); msg_setrcpt_status(msgdata, n, r); Index: perl/sieve/lib/isieve.c =================================================================== RCS file: /cvs/src/cyrus/perl/sieve/lib/isieve.c,v retrieving revision 1.9 diff -u -r1.9 isieve.c --- isieve.c 2001/10/14 13:58:17 1.9 +++ isieve.c 2001/11/08 18:29:44 @@ -403,6 +403,11 @@ return setscriptactive(obj->version,obj->pout, obj->pin, name, errstr); } +int isieve_folder(isieve_t *obj, char *name, char **errstr) +{ + return setscriptfolder(obj->version,obj->pout, obj->pin, name, errstr); +} + int isieve_get(isieve_t *obj,char *name, char **output, char **errstr) { int ret; Index: perl/sieve/lib/isieve.h =================================================================== RCS file: /cvs/src/cyrus/perl/sieve/lib/isieve.h,v retrieving revision 1.3 diff -u -r1.3 isieve.h --- isieve.h 2000/11/16 20:47:45 1.3 +++ isieve.h 2001/11/08 18:29:51 @@ -68,6 +68,7 @@ typedef void *isieve_listcb_t(char *name, int isactive, void *rock); int isieve_list(isieve_t *obj, isieve_listcb_t *cb,void *rock, char **errstr); int isieve_activate(isieve_t *obj, char *name, char **errstr); +int isieve_folder(isieve_t *obj, char *name, char **errstr); int isieve_get(isieve_t *obj,char *name, char **output, char **errstr); #endif /* ISIEVE_H_ */ Index: perl/sieve/lib/request.c =================================================================== RCS file: /cvs/src/cyrus/perl/sieve/lib/request.c,v retrieving revision 1.10 diff -u -r1.10 request.c --- request.c 2000/12/18 04:53:42 1.10 +++ request.c 2001/11/08 18:29:55 @@ -447,6 +447,35 @@ return 0; } +int setscriptfolder(int version, struct protstream *pout, + struct protstream *pin,char *name, char **errstrp) +{ + lexstate_t state; + int res; + int ret; + mystring_t *errstr=NULL; + + /* tell server we want "name" to be the current folder */ + prot_printf(pout, "FOLDER \"%s\"\r\n",name); + prot_flush(pout); + + + /* now let's see what the server said */ + res=yylex(&state, pin); + + ret = handle_response(res,version,pin, &errstr); + + /* if command failed */ + if (ret != 0) { + *errstrp = malloc(128); + snprintf(*errstrp, 127, + "Setting folder: %s",string_DATAPTR(errstr)); + return -1; + } + + return 0; +} + static int viewafile(mystring_t *data, char *name) { printf("%s\r\n", string_DATAPTR(data)); Index: perl/sieve/lib/request.h =================================================================== RCS file: /cvs/src/cyrus/perl/sieve/lib/request.h,v retrieving revision 1.3 diff -u -r1.3 request.h --- request.h 2000/11/16 20:47:45 1.3 +++ request.h 2001/11/08 18:30:00 @@ -71,8 +71,11 @@ int setscriptactive(int version,struct protstream *pout, struct protstream *pin, char *name, char **errstr); +int setscriptfolder(int version,struct protstream *pout, struct protstream *pin, + char *name, char **errstr); + /* - * Getscript. Save {0,1} wheather to save to disk or display on screen + * Getscript. Save {0,1} whether to save to disk or display on screen */ int getscript(int version, struct protstream *pout, struct protstream *pin, Index: perl/sieve/managesieve/managesieve.pm =================================================================== RCS file: /cvs/src/cyrus/perl/sieve/managesieve/managesieve.pm,v retrieving revision 1.5 diff -u -r1.5 managesieve.pm --- managesieve.pm 2000/11/16 20:47:45 1.5 +++ managesieve.pm 2001/11/08 18:30:07 @@ -59,6 +59,7 @@ sieve_delete sieve_list sieve_activate + sieve_folder sieve_get ); $VERSION = '0.01'; Index: perl/sieve/managesieve/managesieve.xs =================================================================== RCS file: /cvs/src/cyrus/perl/sieve/managesieve/managesieve.xs,v retrieving revision 1.8 diff -u -r1.8 managesieve.xs --- managesieve.xs 2001/07/16 16:05:24 1.8 +++ managesieve.xs 2001/11/08 18:30:12 @@ -345,6 +345,16 @@ RETVAL int +sieve_folder(obj,name) + Sieveobj obj + char *name + + CODE: + RETVAL = isieve_folder(obj->isieve, name, &obj->errstr); + OUTPUT: + RETVAL + +int sieve_get(obj,name,output) Sieveobj obj char *name Index: perl/sieve/scripts/sieveshell.pl =================================================================== RCS file: /cvs/src/cyrus/perl/sieve/scripts/sieveshell.pl,v retrieving revision 1.10 diff -u -r1.10 sieveshell.pl --- sieveshell.pl 2001/10/23 20:15:37 1.10 +++ sieveshell.pl 2001/11/08 18:30:16 @@ -52,6 +52,7 @@ my $activatehelp = "activate <name> - set a script as the active script\n"; my $deactivatehelp = "deactivate - deactivate all scripts\n"; my $deletehelp = "delete <name> - delete script.\n"; +my $folderhelp = "folder [<name>] - Set folder to which script applies.\n"; my $username = $ENV{USER}; my $authname = $ENV{USER}; @@ -131,6 +132,7 @@ print $deletehelp; print $activatehelp; print $deactivatehelp; + print $folderhelp; print "quit - quit\n"; } @@ -187,6 +189,17 @@ if ($ret != 0) { my $errstr = sieve_get_error($obj); print "activate failed: $errstr\n"; + } + } elsif (($words[0] eq "folder") || + ($words[0] eq "f")) { + if ($#words != 1) { + print $folderhelp; + next; + } + $ret = sieve_folder($obj, $words[1]); + if ($ret != 0) { + my $errstr = sieve_get_error($obj); + print "folder failed: $errstr\n"; } } elsif (($words[0] eq "deactivate") || ($words[0] eq "da")) { Index: timsieved/actions.c =================================================================== RCS file: /cvs/src/cyrus/timsieved/actions.c,v retrieving revision 1.27 diff -u -r1.27 actions.c --- actions.c 2001/10/14 13:58:17 1.27 +++ actions.c 2001/11/08 18:30:21 @@ -71,20 +71,61 @@ #include "codes.h" #include "actions.h" #include "scripttest.h" +#include "../lib/exitcodes.h" +#include "../et/com_err.h" +#include "../imap/mailbox.h" +#include "../imap/mboxname.h" /* after a user has authentication, our current directory is their Sieve directory! */ char *sieve_dir = NULL; +static struct namespace timsieved_namespace; +void hash_folder(char *buf, const char *root, const char *name) +{ + const char *idx; + char c, *p; + + if (config_getswitch("hashimapspool",0)) { + idx = strchr(name, '.'); + if (idx == NULL) { + idx = name; + } else { + idx++; + } + c = (char) dir_hash_c(idx); + + sprintf(buf, "%s/%c/%s", root, c, name); + } else { + /* standard mailbox placement */ + sprintf(buf, "%s/%s", root, name); + } + + /* change all '.'s to '/' */ + for (p = buf + strlen(root) + 1; *p; p++) { + if (*p == '.') *p = '/'; + } +} + int actions_init(void) { int sieve_usehomedir = 0; + int r; + + /* Set namespace */ + if ((r = mboxname_init_namespace(&timsieved_namespace, 0)) != 0) { + syslog(LOG_ERR, error_message(r)); + fatal(error_message(r), EC_CONFIG); + } + if ( sieve_dir == NULL ) { + sieve_dir = (char *) xmalloc(1024); + } sieve_usehomedir = config_getswitch("sieveusehomedir", 0); if (!sieve_usehomedir) { - sieve_dir = (char *) config_getstring("sievedir", "/usr/sieve"); + strcpy(sieve_dir,(char *) config_getstring("sievedir", "/usr/sieve")); } else { /* can't use home directories with timsieved */ syslog(LOG_ERR, "can't use home directories"); @@ -96,25 +137,66 @@ } +static int mkparentdir ( const char *dir ) +{ + char buf[MAX_MAILBOX_PATH]; + char *p; + + strcpy( buf, dir ); + + p = strrchr( buf, '/' ); + + if ( p ) { + *p = '\0'; + syslog( LOG_INFO, "mkparentdir %s", buf ); + if ( !strcmp( buf, config_getstring( "sievedir", "/usr/sieve") ) ) { + return 1; + } + if ( mkdir( buf, 0755 ) ) { + return mkparentdir( buf ); + } + return 0; + + } + return 1; +} + int actions_setuser(char *userid) { char hash; char *foo=sieve_dir; int result; + char namebuf[MAX_MAILBOX_NAME]; + + if ( sieve_dir == NULL ) { + sieve_dir=(char *) xmalloc(1024); + } - sieve_dir=(char *) xmalloc(1024); + (*timsieved_namespace.mboxname_tointernal)(&timsieved_namespace, "INBOX", userid, +namebuf ); - hash = (char) dir_hash_c(userid); + hash_folder( sieve_dir, config_getstring("sievedir","/usr/sieve"), namebuf ); - snprintf(sieve_dir, 1023, "%s/%c/%s", foo, hash,userid); - + syslog( LOG_INFO, "sieve_dir %s", sieve_dir ); result = chdir(sieve_dir); if (result != 0) { result = mkdir(sieve_dir, 0755); - if (!result) result = chdir(sieve_dir); + if (!result) { + result = chdir(sieve_dir); + } if (result) { - syslog(LOG_ERR, "mkdir %s: %m", sieve_dir); - return TIMSIEVE_FAIL; + + if ( mkparentdir ( sieve_dir ) ) { + syslog(LOG_ERR, "mkdir %s: %m", sieve_dir); + return TIMSIEVE_FAIL; + } + result = mkdir( sieve_dir, 0755 ); + if ( !result) { + result = chdir(sieve_dir); + } + if ( result ) { + syslog(LOG_ERR," mkdir %s: %m", sieve_dir); + return TIMSIEVE_FAIL; + } } } @@ -544,6 +626,46 @@ return TIMSIEVE_OK; } +/* set the folder 'name' to be the current folder */ + +int setfolder(struct protstream *conn, mystring_t *name) +{ + int result; + char filename[1024]; + + /* if string name is empty, folder is the users default one */ + + /* Otherwise the folder must exist AND it must have the correct + acl i.e. an "a" set */ + + strcpy( filename, string_DATAPTR(name)); + + syslog( LOG_INFO, "folder %s", filename ); + + if ( sieve_dir == NULL ) { + sieve_dir=(char *) xmalloc(1024); + } + + hash_folder( sieve_dir, config_getstring("sievedir","/usr/sieve"), filename ); + + syslog( LOG_INFO, "sieve_dir %s", sieve_dir ); + result = chdir(sieve_dir); + if (result != 0) { + result = mkdir(sieve_dir, 0755); + if (!result) result = chdir(sieve_dir); + if (result) { + syslog(LOG_ERR, "mkdir %s: %m", sieve_dir); + prot_printf(conn,"NO \"Unable to create directory - try parent first\"\r\n"); + return TIMSIEVE_FAIL; + } + } + + prot_printf(conn,"OK\r\n"); + return TIMSIEVE_OK; + +} + + int cmd_havespace(struct protstream *conn, mystring_t *sieve_name, unsigned long num) { int result; @@ -585,3 +707,4 @@ prot_printf(conn,"OK\r\n"); return TIMSIEVE_OK; } + Index: timsieved/actions.h =================================================================== RCS file: /cvs/src/cyrus/timsieved/actions.h,v retrieving revision 1.7 diff -u -r1.7 actions.h --- actions.h 2001/10/14 13:58:17 1.7 +++ actions.h 2001/11/08 18:30:28 @@ -109,6 +109,13 @@ int setactive(struct protstream *conn, mystring_t *name); /* + * Set 'name' as the active folder - must have "a" access + * + */ + +int setfolder(struct protstream *conn, mystring_t *name); + +/* * Initialize * */ Index: timsieved/lex.c =================================================================== RCS file: /cvs/src/cyrus/timsieved/lex.c,v retrieving revision 1.18 diff -u -r1.18 lex.c --- lex.c 2001/10/14 13:58:17 1.18 +++ lex.c 2001/11/08 18:30:35 @@ -77,6 +77,10 @@ if (strcmp(str, "deletescript")==0) return DELETESCRIPT; break; + case 'f': + if (strcmp(str, "folder")==0) return SETFOLDER; + break; + case 'g': if (strcmp(str, "getscript")==0) return GETSCRIPT; break; Index: timsieved/lex.h =================================================================== RCS file: /cvs/src/cyrus/timsieved/lex.h,v retrieving revision 1.10 diff -u -r1.10 lex.h --- lex.h 2001/10/14 13:58:17 1.10 +++ lex.h 2001/11/08 18:30:40 @@ -81,6 +81,7 @@ #define CAPABILITY 407 #define HAVESPACE 408 #define STARTTLS 409 +#define SETFOLDER 410 int lex_init(void); Index: timsieved/parser.c =================================================================== RCS file: /cvs/src/cyrus/timsieved/parser.c,v retrieving revision 1.10 diff -u -r1.10 parser.c --- parser.c 2001/10/14 13:58:17 1.10 +++ parser.c 2001/11/08 18:30:44 @@ -311,6 +311,29 @@ break; + case SETFOLDER: + if (timlex(NULL, NULL, sieved_in)!=SPACE) + { + error_msg = "SPACE must occur after SETFOLDER"; + goto error; + } + + if (timlex(&sieve_name, NULL, sieved_in)!=STRING) + { + error_msg = "Did not specify folder name"; + goto error; + } + + if (timlex(NULL, NULL, sieved_in)!=EOL) + { + error_msg = "Expected EOL"; + goto error; + } + + setfolder(sieved_out, sieve_name); + + break; + case DELETESCRIPT: if (timlex(NULL, NULL, sieved_in)!=SPACE) { Index: timsieved/Makefile.in =================================================================== RCS file: /cvs/src/cyrus/timsieved/Makefile.in,v retrieving revision 1.17 diff -u -r1.17 Makefile.in --- Makefile.in 2001/10/14 13:58:17 1.17 +++ Makefile.in 2001/11/09 15:49:25 @@ -70,7 +70,7 @@ IMAP_COM_ERR_LIBS = @IMAP_COM_ERR_LIBS@ LIB_WRAP = @LIB_WRAP@ LIBS = $(IMAP_COM_ERR_LIBS) -DEPLIBS=../sieve/libsieve.a ../imap/libimap.a ../lib/libcyrus.a @DEPLIBS@ +DEPLIBS=../sieve/libsieve.a ../imap/libimap.a ../lib/libcyrus.a ../acap/libacap.a +@DEPLIBS@ PURIFY=/usr/local/bin/purify PUREOPT=-best-effort.