diff --git a/TODO b/TODO index 1305d77f21bffd58d948c61298099929fddce9ed..a351964854253766f742b69de03445162e48900e 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,8 @@ +Current activities: + +* Build a sieve tool to filter an entire existing mailbox through a Sieve + script. + Next (in order of descending priority/precedence): * Finish the ereject extension @@ -16,10 +21,6 @@ Next (in order of descending priority/precedence): * Cleanup the test suite - Restructure test scripts - Add more comment on purpose of tests -* Build a sieve tool to filter an entire existing mailbox through a Sieve - script: - - Add commandline options to fully customize execution - - Write manual page * Implement index extension * Update include extension to latest draft (v05 currently): - Implement required ManageSieve behavior (pending IETF discussion) diff --git a/doc/man/sieve-filter.1.in b/doc/man/sieve-filter.1.in index 5c27038ffc88db8ac19bb0c3cf7bc9fc3c9fb544..05191ef53c33eaca01238ac4bb5c63970bc5393f 100644 --- a/doc/man/sieve-filter.1.in +++ b/doc/man/sieve-filter.1.in @@ -38,47 +38,31 @@ remains unchanged. Use the \fB\-W\fP to allow changes in the source mailbox. .SS CAUTION Although this is a very useful tool, it can also be very destructive when used improperly. A small bug in your Sieve script in combination with the wrong -command line options could cause it to discard the wrong e\-mails. Therefore, -users are advised to read this manual carefully and to use the simulation mode -first to check what the script will do. +command line options could cause it to discard the wrong e\-mails. And, even if +the source mailbox is opened in read\-only mode to prevent such mishaps, it can +still litter other mailboxes with spurious copies of your e\-mails if your Sieve +script decides to do so. Therefore, users are advised to read this manual +carefully and to use the simulation mode first to check what the script will do. +And, of course: .PP \fBMAKING A BACKUP IS IMPERATIVE FOR ANY IMPORTANT MAIL!\fP -.PP -By default, it will open the source mailbox in a read\-only mode, such that it -will not delete any of your e\-mails. However, it can still litter other -mailboxes with spurious copies of your e\-mails if your Sieve script decides to -do so. + .\"------------------------------------------------------------------------ .SH OPTIONS .TP .BI \-c\ config\-file Alternative Dovecot configuration file path. -.TP -.BI \-D\ source\-action -By default, the sieve\-filter command does not delete the messages from the -source mailbox. This means that a copy operation is executed by default and the -source mailbox is not altered. The \fIsource\-action\fP parameter of the -\fB\-D\fP option can take four different values: -.RS 7 -.TP -\fBkeep\fP (default) -Keep messages in source folder. If \fB\-W\fR is specified and the source mailbox -is the destination of a keep or fileinto action, flags can be changed by the -Sieve script. Messages are never duplicated in the source mailbox. -.TP -\fBflag\fP -Flag messages as \\DELETED. -.TP -\fBmove\fP [\fIfolder\fP] -Move messages to the indicated \fIfolder\fP. -.TP -\fBexpunge\fP -Expunge messages, meaning that these are removed irreversibly when the tool -finishes filtering. -.PP -Note that values other than `keep' have no effect, unless the \fB\-W\fP option -is specified as well. -.RE +.TP +.B \-C +Force compilation. By default, the compiled binary is stored on disk. When this +binary is found during the next execution of \fBsieve\-filter\fP and its +modification time is more recent than the script file, it is used and the script +is not compiled again. This option forces the script to be compiled, thus +ignoring any present binary. Refer to \fBsievec\fP(1) for more information about +Sieve compilation. +.TP +.B \-D +Enable Sieve debugging. .TP .B \-e Turns on execution mode. By default, the sieve\-filter command runs in @@ -87,33 +71,61 @@ in any way and no actions are performed. It only prints what would be done. Using this option the sieve\-filter command becomes active and performs the requested actions. .TP -.BI \-f\ envelope\-sender -The envelope sender or return path. This is what Sieve\(aqs envelope test will -compare to when the \(dqfrom\(dq envelope part is requested. Also, this is -where response messages are sent to. -.TP .BI \-m\ default\-mailbox -The mailbox where the keep action stores the message. This is \(dqINBOX\(dq +The mailbox where the keep action stores messages. This is \(dqINBOX\(dq by default. .TP -.BI \-Q\ mail\-command -Send outgoing e\-mail through the specified program. By default, +.BI \-q\ output\-mailbox +Store outgoing e\-mail into the indicated \fIoutput\-mailbox\fP. By default, the sieve\-filter command ignores Sieve actions such as redirect, reject, -vacation and notify, but using this option outgoing messages can be fed to the -\fBstdin\fP of an external shell command. This option has no effect in -simulation mode. Unless you really know what you are doing, \fBDO NOT USE THIS -TO FEED MAIL TO SENDMAIL!\f. +vacation and notify, but using this option outgoing messages can be appended to +the indicated mailbox. This option has no effect in simulation mode. .TP -.BI \-r\ recipient\-address -The envelope recipient address. This is what Sieve\(aqs envelope test will -compare to when the \(dqto\(dq envelope part is requested. Some tests and -actions will also use this as the owner\(aqs e\-mail address. +.BI \-Q\ mail\-command +Send outgoing e\-mail (e.g. as produced by redirect, reject and vacation) +through the specified program. By default, the sieve\-filter command ignores +Sieve actions such as redirect, reject, vacation and notify, but using this +option outgoing messages can be fed to the \fBstdin\fP of an external shell +command. This option has no effect in simulation mode. Unless you really know +what you are doing, \fBDO NOT USE THIS TO FEED MAIL TO SENDMAIL!\fP. +.TP +.BI \-R\ source\-action +Specifies what needs to be done with messages in the source mailbox once +processed by the Sieve script. By default, the sieve\-filter command does not +remove the messages from the source mailbox. In particular this means that the +keep and fileinto actions will cause the messages to be copied. To alter this +behavior, the \fIsource\-action\fP parameter of the \fB\-R\fP option accepts +one of the following values: +.RS 7 +.TP +\fBkeep\fP (default) +Keep processed messages in source folder. If \fB\-W\fR is specified and the +source mailbox is the destination of a keep or fileinto action, flags may be +changed by the Sieve script, but messages are never duplicated there. +.TP +\fBflag\fP +Flag processed messages as \\DELETED. +.TP +\fBmove\fP [\fIfolder\fP] +Move processed messages to the indicated \fIfolder\fP. +.TP +\fBexpunge\fP +Expunge processed messages, meaning that these are removed irreversibly when the +tool finishes filtering. +.PP +Note that the chosen \fIsource\-action\fP only has an effect on the source +mailbox when the \fB\-W\fP option is specified as well. +.RE .TP .BI \-s\ script\-file Specify additional scripts to be executed before the main script. Multiple \fB\-s\fP arguments are allowed and the specified scripts are executed sequentially in the order specified at the command -line..TP +line. +.TP +.BI \-u\ user +Run the Sieve script for the given \fIuser\fP. +.TP .B \-W Enables write access to the source mailbox. This allows deleting the messages from the source mailbox and changing the assigned IMAP flags and keywords. diff --git a/src/sieve-tools/sieve-filter.c b/src/sieve-tools/sieve-filter.c index 972ca0ad739fb16055ef5f7f6b91160de88f5ca1..3a67e6659eb0e33423d10f24ecf23c4a1586a00c 100644 --- a/src/sieve-tools/sieve-filter.c +++ b/src/sieve-tools/sieve-filter.c @@ -11,6 +11,7 @@ #include "mail-namespace.h" #include "mail-storage.h" #include "mail-search-build.h" +#include "imap-utf7.h" #include "sieve.h" #include "sieve-extensions.h" @@ -36,25 +37,30 @@ static void print_help(void) printf( "Usage: sieve-filter [-m <mailbox>] [-x <extensions>] [-s <script-file>] [-c]\n" " <script-file> <src-mail-store> [<dest-mail-store>]\n" +"Usage: sieve-filter [-c <config-file>] [-C] [-D] [-e]\n" +" [-m <default-mailbox>] [-P <plugin>]\n" +" [-r <recipient-address>] [-s <script-file>]\n" +" [-t <trace-file>] [-T <trace-option>] [-x <extensions>]\n" +" <script-file> <mail-file>\n" ); } -enum discard_action_type { - DISCARD_ACTION_KEEP, /* Always keep messages in source folder */ - DISCARD_ACTION_DELETE, /* Flag discarded messages as \DELETED */ - DISCARD_ACTION_TRASH_FOLDER, /* Move discarded messages to Trash folder */ - DISCARD_ACTION_EXPUNGE /* Expunge discarded messages */ +enum source_action_type { + SOURCE_ACTION_KEEP, /* Always keep messages in source folder */ + SOURCE_ACTION_DELETE, /* Flag discarded messages as \DELETED */ + SOURCE_ACTION_TRASH_FOLDER, /* Move discarded messages to Trash folder */ + SOURCE_ACTION_EXPUNGE /* Expunge discarded messages */ }; -struct discard_action { - enum discard_action_type type; +struct source_action { + enum source_action_type type; const char *trash_folder; }; static int filter_message (struct mail *mail, struct sieve_binary *main_sbin, struct sieve_script_env *senv, struct sieve_error_handler *ehandler, - struct discard_action discard_action) + struct source_action source_action) { struct sieve_exec_status estatus; struct sieve_binary *sbin; @@ -72,7 +78,8 @@ static int filter_message memset(&msgdata, 0, sizeof(msgdata)); msgdata.mail = mail; msgdata.return_path = sender; - msgdata.to_address = recipient; + msgdata.orig_envelope_to = recipient; + msgdata.final_envelope_to = recipient; msgdata.auth_user = senv->username; (void)mail_get_first_header(mail, "Message-ID", &msgdata.id); @@ -85,24 +92,24 @@ static int filter_message /* Handle message in source folder */ if ( ret > 0 && !estatus.keep_original ) { - switch ( discard_action.type ) { + switch ( source_action.type ) { /* Leave it there */ - case DISCARD_ACTION_KEEP: - sieve_info(ehandler, NULL, "message left in source folder"); + case SOURCE_ACTION_KEEP: + sieve_info(ehandler, NULL, "message left in source mailbox"); break; /* Flag message as \DELETED */ - case DISCARD_ACTION_DELETE: - sieve_info(ehandler, NULL, "message flagged as deleted in source folder"); + case SOURCE_ACTION_DELETE: + sieve_info(ehandler, NULL, "message flagged as deleted in source mailbox"); mail_update_flags(mail, MODIFY_ADD, MAIL_DELETED); break; /* Move message to Trash folder */ - case DISCARD_ACTION_TRASH_FOLDER: + case SOURCE_ACTION_TRASH_FOLDER: sieve_info(ehandler, NULL, "message in source folder moved to folder '%s'", - discard_action.trash_folder); + source_action.trash_folder); break; /* Expunge the message immediately */ - case DISCARD_ACTION_EXPUNGE: + case SOURCE_ACTION_EXPUNGE: sieve_info(ehandler, NULL, "message removed from source folder"); mail_expunge(mail); break; @@ -132,9 +139,9 @@ static void mail_search_build_add_flags } static int filter_mailbox -(struct mailbox *box, struct sieve_binary *main_sbin, +(struct mailbox *src_box, struct sieve_binary *main_sbin, struct sieve_script_env *senv, struct sieve_error_handler *ehandler, - struct discard_action discard_action) + struct source_action source_action) { struct mail_search_args *search_args; struct mailbox_transaction_context *t; @@ -144,7 +151,7 @@ static int filter_mailbox /* Sync mailbox */ - if ( mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0 ) { + if ( mailbox_sync(src_box, MAILBOX_SYNC_FLAG_FULL_READ) < 0 ) { return -1; } @@ -155,7 +162,7 @@ static int filter_mailbox /* Iterate through all requested messages */ - t = mailbox_transaction_begin(box, 0); + t = mailbox_transaction_begin(src_box, 0); search_ctx = mailbox_search_init(t, search_args, NULL); mail_search_args_unref(&search_args); @@ -182,7 +189,7 @@ static int filter_mailbox sieve_info(ehandler, NULL, "filtering: [%s; %"PRIuUOFF_T" bytes] %s", date, size, subject); - ret = filter_message(mail, main_sbin, senv, ehandler, discard_action); + ret = filter_message(mail, main_sbin, senv, ehandler, source_action); } mail_free(&mail); @@ -203,13 +210,23 @@ static int filter_mailbox /* Sync mailbox */ - if ( mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_WRITE) < 0 ) { + if ( mailbox_sync(src_box, MAILBOX_SYNC_FLAG_FULL_WRITE) < 0 ) { return -1; } return ret; } +static const char *mailbox_name_to_mutf7(const char *mailbox_utf8) +{ + string_t *str = t_str_new(128); + + if (imap_utf8_to_utf7(mailbox_utf8, str) < 0) + return mailbox_utf8; + else + return str_c(str); +} + /* * Tool implementation */ @@ -217,26 +234,27 @@ static int filter_mailbox int main(int argc, char **argv) { struct sieve_instance *svinst; - const char *scriptfile, *recipient, *sender, - *src_mailbox, *dst_mailbox, *src_mailstore, *dst_mailstore; + ARRAY_TYPE (const_string) scriptfiles; + const char *scriptfile, *src_mailbox, *dst_mailbox; + struct source_action source_action = { SOURCE_ACTION_KEEP, "Trash" }; struct mail_user *mail_user; - struct mail_namespace *src_ns, *dst_ns; struct sieve_binary *main_sbin; struct sieve_script_env scriptenv; struct sieve_error_handler *ehandler; - bool force_compile = FALSE; + bool force_compile = FALSE, execute = FALSE, source_write = FALSE; + struct mail_namespace *ns; + struct mailbox *src_box; enum mailbox_flags open_flags = MAILBOX_FLAG_KEEP_RECENT | MAILBOX_FLAG_IGNORE_ACLS; enum mail_error error; - struct discard_action discard_action = - { DISCARD_ACTION_KEEP, "Trash" }; - struct mailbox *src_box; int c; - sieve_tool = sieve_tool_init("sieve-filter", &argc, &argv, "m:x:P:CD", FALSE); + sieve_tool = sieve_tool_init("sieve-filter", &argc, &argv, "m:s:x:P:CD", FALSE); + + t_array_init(&scriptfiles, 16); /* Parse arguments */ - scriptfile = recipient = sender = src_mailstore = dst_mailstore = NULL; + scriptfile = NULL; src_mailbox = dst_mailbox = "INBOX"; force_compile = FALSE; while ((c = sieve_tool_getopt(sieve_tool)) > 0) { @@ -245,9 +263,28 @@ int main(int argc, char **argv) /* default mailbox (keep box) */ dst_mailbox = optarg; break; + case 's': + /* scriptfile executed before main script */ + { + const char *file; + + file = t_strdup(optarg); + array_append(&scriptfiles, &file, 1); + + /* FIXME: */ + i_fatal_status(EX_USAGE, + "The -s argument is currently NOT IMPLEMENTED"); + } + break; + case 'e': + execute = TRUE; + break; case 'C': force_compile = TRUE; break; + case 'W': + source_write = TRUE; + break; default: print_help(); i_fatal_status(EX_USAGE, "Unknown argument: %c", c); @@ -263,14 +300,10 @@ int main(int argc, char **argv) } if ( optind < argc ) { - src_mailstore = t_strdup(argv[optind++]); + src_mailbox = t_strdup(argv[optind++]); } else { print_help(); - i_fatal_status(EX_USAGE, "Missing <mailstore> argument"); - } - - if ( optind < argc ) { - dst_mailstore = t_strdup(argv[optind++]); + i_fatal_status(EX_USAGE, "Missing <source-mailbox> argument"); } if ( optind != argc ) { @@ -284,39 +317,35 @@ int main(int argc, char **argv) (void) sieve_extension_register(svinst, &debug_extension, TRUE); /* Create error handler */ - ehandler = sieve_stderr_ehandler_create(0); + ehandler = sieve_stderr_ehandler_create(svinst, 0); sieve_system_ehandler_set(ehandler); sieve_error_handler_accept_infolog(ehandler, TRUE); /* Compile main sieve script */ if ( force_compile ) { main_sbin = sieve_tool_script_compile(svinst, scriptfile, NULL); - (void) sieve_save(main_sbin, NULL); + if ( main_sbin != NULL ) + (void) sieve_save(main_sbin, NULL, TRUE, NULL); } else { main_sbin = sieve_tool_script_open(svinst, scriptfile); } - sieve_tool_init_mail_user(sieve_tool, src_mailstore); + /* Initialize mail user */ mail_user = sieve_tool_get_mail_user(sieve_tool); -/* if ( dst_mailstore != NULL ) { - folder = "#src/"; - src_ns = mail_namespace_find(mail_user->namespaces, &folder); - - folder = "/"; - dst_ns = mail_namespace_find(mail_user->namespaces, &folder); - - discard_action.type = DISCARD_ACTION_KEEP; - } else {*/ - dst_ns = src_ns = mail_user->namespaces; - discard_action.type = DISCARD_ACTION_DELETE; -/* }*/ + /* Find namespace for source mailbox */ + src_mailbox = mailbox_name_to_mutf7(src_mailbox); + ns = mail_namespace_find(mail_user->namespaces, &src_mailbox); + if ( ns == NULL ) + i_fatal("Unknown namespace for source mailbox '%s'", src_mailbox); /* Open the source mailbox */ - src_box = mailbox_alloc(src_ns->list, src_mailbox, open_flags); + if ( !source_write ) + open_flags |= MAILBOX_FLAG_READONLY; + src_box = mailbox_alloc(ns->list, src_mailbox, open_flags); if ( mailbox_open(src_box) < 0 ) { i_fatal("Couldn't open mailbox '%s': %s", - src_mailbox, mail_storage_get_last_error(src_ns->storage, &error)); + src_mailbox, mail_storage_get_last_error(ns->storage, &error)); } /* Compose script environment */ @@ -329,13 +358,10 @@ int main(int argc, char **argv) scriptenv.postmaster_address = "postmaster@example.com"; scriptenv.smtp_open = NULL; scriptenv.smtp_close = NULL; - scriptenv.duplicate_mark = NULL; - scriptenv.duplicate_check = NULL; - scriptenv.trace_stream = NULL; /* Apply Sieve filter to all messages found */ (void) filter_mailbox - (src_box, main_sbin, &scriptenv, ehandler, discard_action); + (src_box, main_sbin, &scriptenv, ehandler, source_action); /* Close the mailbox */ if ( src_box != NULL ) @@ -343,7 +369,6 @@ int main(int argc, char **argv) /* Cleanup error handler */ sieve_error_handler_unref(&ehandler); - sieve_system_ehandler_reset(); sieve_tool_deinit(&sieve_tool);