/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 1999, 2000, 2001, 2002, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sieve.h> static int sieve_mark_deleted (mu_message_t msg, int deleted) { mu_attribute_t attr = 0; int rc; rc = mu_message_get_attribute (msg, &attr); if (!rc) { if (deleted) rc = mu_attribute_set_deleted (attr); else rc = mu_attribute_unset_deleted (attr); } return rc; } static int sieve_action_stop (mu_sieve_machine_t mach, mu_list_t args, mu_list_t tags) { mu_sieve_log_action (mach, "STOP", NULL); mach->pc = 0; return 0; } static int sieve_action_keep (mu_sieve_machine_t mach, mu_list_t args, mu_list_t tags) { mu_sieve_log_action (mach, "KEEP", NULL); if (mu_sieve_is_dry_run (mach)) return 0; sieve_mark_deleted (mach->msg, 0); return 0; } static int sieve_action_discard (mu_sieve_machine_t mach, mu_list_t args, mu_list_t tags) { mu_sieve_log_action (mach, "DISCARD", _("marking as deleted")); if (mu_sieve_is_dry_run (mach)) return 0; sieve_mark_deleted (mach->msg, 1); return 0; } static int sieve_action_fileinto (mu_sieve_machine_t mach, mu_list_t args, mu_list_t tags) { int rc; int mbflags = 0; mu_sieve_value_t *opt; mu_sieve_value_t *val = mu_sieve_value_get (args, 0); if (!val) { mu_sieve_error (mach, _("cannot get filename!")); mu_sieve_abort (mach); } if (mu_sieve_tag_lookup (tags, "permissions", &opt)) { const char *p; if (mu_parse_stream_perm_string (&mbflags, opt->v.string, &p)) { /* Should not happen, but anyway... */ mu_sieve_error (mach, _("invalid permissions (near %s)"), p); return 1; } } mu_sieve_log_action (mach, "FILEINTO", _("delivering into %s"), val->v.string); if (mu_sieve_is_dry_run (mach)) return 0; rc = mu_message_save_to_mailbox (mach->msg, mach->ticket, mach->debug, val->v.string, mbflags); if (rc) mu_sieve_error (mach, _("cannot save to mailbox: %s"), mu_strerror (rc)); else sieve_mark_deleted (mach->msg, 1); return rc; } int mu_sieve_get_message_sender (mu_message_t msg, char **ptext) { int rc; mu_envelope_t envelope; rc = mu_message_get_envelope (msg, &envelope); if (rc) return rc; rc = mu_envelope_aget_sender (envelope, ptext); if (rc) { mu_header_t hdr = NULL; mu_message_get_header (msg, &hdr); if ((rc = mu_header_aget_value (hdr, MU_HEADER_SENDER, ptext))) rc = mu_header_aget_value (hdr, MU_HEADER_FROM, ptext); } return rc; } static void mime_create_reason (mu_mime_t mime, mu_message_t msg, const char *text) { mu_message_t newmsg; mu_stream_t stream; time_t t; struct tm *tm; char *sender; mu_off_t off = 0; mu_body_t body; mu_header_t hdr; char datestr[80]; static char *content_header = "Content-Type: text/plain;charset=" MU_SIEVE_CHARSET "\n" "Content-Transfer-Encoding: 8bit\n"; mu_message_create (&newmsg, NULL); mu_message_get_body (newmsg, &body); mu_body_get_stream (body, &stream); time (&t); tm = localtime (&t); mu_strftime (datestr, sizeof datestr, "%a, %b %d %H:%M:%S %Y %Z", tm); mu_sieve_get_message_sender (msg, &sender); mu_stream_printf (stream, &off, "The original message was received at %s from %s.\n", datestr, sender); free (sender); mu_stream_printf (stream, &off, "Message was refused by recipient's mail filtering program.\n"); mu_stream_printf (stream, &off, "Reason given was as follows:\n\n"); mu_stream_printf (stream, &off, "%s", text); mu_stream_close (stream); mu_header_create (&hdr, content_header, strlen (content_header), newmsg); mu_message_set_header (newmsg, hdr, NULL); mu_mime_add_part (mime, newmsg); mu_message_unref (newmsg); } static void mime_create_ds (mu_mime_t mime, mu_message_t orig) { mu_message_t newmsg; mu_stream_t stream; mu_header_t hdr; mu_off_t off = 0; mu_body_t body; char *email; char datestr[80]; time_t t = time (NULL); struct tm tm, *tmp; mu_timezone tz; mu_envelope_t env; const char *p; mu_message_create (&newmsg, NULL); mu_message_get_header (newmsg, &hdr); mu_header_set_value (hdr, "Content-Type", "message/delivery-status", 1); mu_message_get_body (newmsg, &body); mu_body_get_stream (body, &stream); mu_stream_printf (stream, &off, "Reporting-UA: sieve; %s\n", PACKAGE_STRING); mu_message_get_envelope (orig, &env); if (mu_envelope_sget_date (env, &p) == 0 && mu_parse_ctime_date_time (&p, &tm, &tz) == 0) t = mu_tm2time (&tm, &tz); else /* Use local time instead */ t = time (NULL); tmp = localtime (&t); /* FIXME: timezone info is lost */ mu_strftime (datestr, sizeof datestr, "%a, %b %d %H:%M:%S %Y %Z", tmp); mu_stream_printf (stream, &off, "Arrival-Date: %s\n", datestr); email = mu_get_user_email (NULL); mu_stream_printf (stream, &off, "Final-Recipient: RFC822; %s\n", email ? email : "unknown"); free (email); mu_stream_printf (stream, &off, "Action: deleted\n"); mu_stream_printf (stream, &off, "Disposition: automatic-action/MDN-sent-automatically;deleted\n"); t = time (NULL); tmp = localtime (&t); mu_strftime (datestr, sizeof datestr, "%a, %b %d %H:%M:%S %Y %Z", tmp); mu_stream_printf (stream, &off, "Last-Attempt-Date: %s\n", datestr); mu_stream_close (stream); mu_mime_add_part(mime, newmsg); mu_message_unref (newmsg); } /* Quote original message */ static int mime_create_quote (mu_mime_t mime, mu_message_t msg) { mu_message_t newmsg; mu_stream_t istream, ostream; mu_header_t hdr; size_t ioff = 0, ooff = 0, n; char buffer[512]; mu_body_t body; mu_message_create (&newmsg, NULL); mu_message_get_header (newmsg, &hdr); mu_header_set_value (hdr, "Content-Type", "message/rfc822", 1); mu_message_get_body (newmsg, &body); mu_body_get_stream (body, &ostream); mu_message_get_stream (msg, &istream); while (mu_stream_read (istream, buffer, sizeof buffer - 1, ioff, &n) == 0 && n != 0) { size_t sz; mu_stream_write (ostream, buffer, n, ooff, &sz); if (sz != n) return EIO; ooff += n; ioff += n; } mu_stream_close (ostream); mu_mime_add_part (mime, newmsg); mu_message_unref (newmsg); return 0; } static int build_mime (mu_mime_t *pmime, mu_message_t msg, const char *text) { mu_mime_t mime = NULL; int status; mu_mime_create (&mime, NULL, 0); mime_create_reason (mime, msg, text); mime_create_ds (mime, msg); status = mime_create_quote (mime, msg); if (status) { mu_mime_destroy (&mime); return status; } *pmime = mime; return 0; } static int sieve_action_reject (mu_sieve_machine_t mach, mu_list_t args, mu_list_t tags) { mu_mime_t mime = NULL; mu_mailer_t mailer = mu_sieve_get_mailer (mach); int rc; mu_message_t newmsg; char *addrtext; mu_address_t from, to; mu_sieve_value_t *val = mu_sieve_value_get (args, 0); if (!val) { mu_sieve_error (mach, _("reject: cannot get text!")); mu_sieve_abort (mach); } mu_sieve_log_action (mach, "REJECT", NULL); if (mu_sieve_is_dry_run (mach)) return 0; rc = build_mime (&mime, mach->msg, val->v.string); mu_mime_get_message (mime, &newmsg); mu_sieve_get_message_sender (mach->msg, &addrtext); rc = mu_address_create (&to, addrtext); if (rc) { mu_sieve_error (mach, _("%d: cannot create recipient address <%s>: %s"), mu_sieve_get_message_num (mach), addrtext, mu_strerror (rc)); free (addrtext); goto end; } free (addrtext); rc = mu_address_create (&from, mu_sieve_get_daemon_email (mach)); if (rc) { mu_sieve_error (mach, _("%d: cannot create sender address <%s>: %s"), mu_sieve_get_message_num (mach), mu_sieve_get_daemon_email (mach), mu_strerror (rc)); goto end; } rc = mu_mailer_open (mailer, 0); if (rc) { mu_url_t url = NULL; mu_mailer_get_url (mailer, &url); mu_sieve_error (mach, _("%d: cannot open mailer %s: %s"), mu_sieve_get_message_num (mach), mu_url_to_string (url), mu_strerror (rc)); goto end; } rc = mu_mailer_send_message (mailer, newmsg, from, to); mu_mailer_close (mailer); end: sieve_mark_deleted (mach->msg, rc == 0); mu_mime_destroy (&mime); mu_address_destroy (&from); mu_address_destroy (&to); return rc; } /* rfc3028 says: "Implementations SHOULD take measures to implement loop control," We do this by appending an "X-Loop-Prevention" header to each message being redirected. If one of the "X-Loop-Prevention" headers of the message contains our email address, we assume it is a loop and bail out. */ static int check_redirect_loop (mu_message_t msg) { mu_header_t hdr = NULL; size_t i, num = 0; char buf[512]; int loop = 0; char *email = mu_get_user_email (NULL); mu_message_get_header (msg, &hdr); mu_header_get_field_count (hdr, &num); for (i = 1; !loop && i <= num; i++) { mu_header_get_field_name (hdr, i, buf, sizeof buf, NULL); if (strcasecmp (buf, "X-Loop-Prevention") == 0) { size_t j, cnt = 0; mu_address_t addr; mu_header_get_field_value (hdr, i, buf, sizeof buf, NULL); if (mu_address_create (&addr, buf)) continue; mu_address_get_count (addr, &cnt); for (j = 1; !loop && j <= cnt; j++) { mu_address_get_email (addr, j, buf, sizeof buf, NULL); if (strcasecmp (buf, email) == 0) loop = 1; } mu_address_destroy (&addr); } } free (email); return loop; } static int sieve_action_redirect (mu_sieve_machine_t mach, mu_list_t args, mu_list_t tags) { mu_message_t msg, newmsg = NULL; mu_address_t addr = NULL, from = NULL; mu_header_t hdr = NULL; int rc; char *fromaddr, *p; mu_mailer_t mailer = mu_sieve_get_mailer (mach); mu_sieve_value_t *val = mu_sieve_value_get (args, 0); if (!val) { mu_sieve_error (mach, _("cannot get address!")); mu_sieve_abort (mach); } rc = mu_address_create (&addr, val->v.string); if (rc) { mu_sieve_error (mach, _("%d: parsing recipient address `%s' failed: %s"), mu_sieve_get_message_num (mach), val->v.string, mu_strerror (rc)); return 1; } mu_sieve_log_action (mach, "REDIRECT", _("to %s"), val->v.string); if (mu_sieve_is_dry_run (mach)) return 0; msg = mu_sieve_get_message (mach); if (check_redirect_loop (msg)) { mu_sieve_error (mach, _("%d: Redirection loop detected"), mu_sieve_get_message_num (mach)); goto end; } rc = mu_sieve_get_message_sender (msg, &fromaddr); if (rc) { mu_sieve_error (mach, _("%d: cannot get envelope sender: %s"), mu_sieve_get_message_num (mach), mu_strerror (rc)); goto end; } rc = mu_address_create (&from, fromaddr); if (rc) { mu_sieve_error (mach, _("%d: cannot create sender address <%s>: %s"), mu_sieve_get_message_num (mach), fromaddr, mu_strerror (rc)); free (fromaddr); goto end; } free (fromaddr); rc = mu_message_create_copy (&newmsg, msg); if (rc) { mu_sieve_error (mach, _("%d: cannot copy message: %s"), mu_sieve_get_message_num (mach), mu_strerror (rc)); goto end; } mu_message_get_header (newmsg, &hdr); p = mu_get_user_email (NULL); if (p) { mu_header_set_value (hdr, "X-Loop-Prevention", p, 0); free (p); } else { mu_sieve_error (mach, _("%d: cannot get my email address"), mu_sieve_get_message_num (mach)); goto end; } rc = mu_mailer_open (mailer, 0); if (rc) { mu_url_t url = NULL; mu_mailer_get_url (mailer, &url); mu_sieve_error (mach, _("%d: cannot open mailer %s: %s"), mu_sieve_get_message_num (mach), mu_url_to_string (url), mu_strerror (rc)); goto end; } rc = mu_mailer_send_message (mailer, newmsg, from, addr); mu_mailer_close (mailer); end: sieve_mark_deleted (mach->msg, rc == 0); mu_message_destroy (&newmsg, NULL); mu_address_destroy (&from); mu_address_destroy (&addr); return rc; } mu_sieve_data_type fileinto_args[] = { SVT_STRING, SVT_VOID }; static int perms_tag_checker (const char *name, mu_list_t tags, mu_list_t args) { mu_iterator_t itr; int err = 0; if (!tags || mu_list_get_iterator (tags, &itr)) return 0; for (mu_iterator_first (itr); !err && !mu_iterator_is_done (itr); mu_iterator_next (itr)) { int flag; const char *p; mu_sieve_runtime_tag_t *t; mu_iterator_current (itr, (void **)&t); if (strcmp (t->tag, "permissions") == 0) { if (mu_parse_stream_perm_string (&flag, t->arg->v.string, &p)) { mu_sv_compile_error (&mu_sieve_locus, _("invalid permissions (near %s)"), p); err = 1; } } } mu_iterator_destroy (&itr); return err; } static mu_sieve_tag_def_t perms_tags[] = { { "permissions", SVT_STRING }, { NULL } }; static mu_sieve_tag_group_t fileinto_tag_groups[] = { { perms_tags, perms_tag_checker }, { NULL } }; void mu_sv_register_standard_actions (mu_sieve_machine_t mach) { mu_sieve_register_action (mach, "stop", sieve_action_stop, NULL, NULL, 1); mu_sieve_register_action (mach, "keep", sieve_action_keep, NULL, NULL, 1); mu_sieve_register_action (mach, "discard", sieve_action_discard, NULL, NULL, 1); mu_sieve_register_action (mach, "fileinto", sieve_action_fileinto, fileinto_args, fileinto_tag_groups, 0); mu_sieve_register_action (mach, "reject", sieve_action_reject, fileinto_args, NULL, 0); mu_sieve_register_action (mach, "redirect", sieve_action_redirect, fileinto_args, NULL, 0); }