/* elmo - ELectronic Mail Operator Copyright (C) 2003, 2004 rzyjontko This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ---------------------------------------------------------------------- */ /**************************************************************************** * IMPLEMENTATION HEADERS ****************************************************************************/ #include #include #include #include #include #include #include #include #include "procmail.h" #include "debug.h" #include "xmalloc.h" #include "rarray.h" #include "error.h" #include "mail.h" #include "mybox.h" #include "file.h" #include "misc.h" #include "wrapbox.h" #include "mlex.h" #include "gettext.h" #include "ask.h" /**************************************************************************** * IMPLEMENTATION PRIVATE DEFINITIONS / ENUMERATIONS / SIMPLE TYPEDEFS ****************************************************************************/ #ifdef __GNUC__ # define FORMAT_2 __attribute__ ((format (printf, 2, 3))) #else # define FORMAT_2 #endif enum field { FIELD_INVALID, FIELD_TO, FIELD_FROM, FIELD_SUBJECT, FIELD_CC, FIELD_TOCC, FIELD_ANY, }; /**************************************************************************** * IMPLEMENTATION PRIVATE CLASS PROTOTYPES / EXTERNAL CLASS REFERENCES ****************************************************************************/ /**************************************************************************** * IMPLEMENTATION PRIVATE STRUCTURES / UTILITY CLASSES ****************************************************************************/ struct constraint { enum field field; regex_t re; }; struct rule { struct rule *next; char *name; rarray_t *constraints; char *action; int stop; }; /**************************************************************************** * IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID) ****************************************************************************/ /**************************************************************************** * IMPLEMENTATION PRIVATE DATA ****************************************************************************/ /* These two variables define a list of rules. Last is used to append to the end of the list instead of prepending. */ struct rule *rules = NULL; struct rule *last = NULL; /* This variable holds a new rule, while it is parsed in config file. */ struct rule *prepared = NULL; struct constraint *constraint = NULL; static char *logfile = NULL; /**************************************************************************** * INTERFACE DATA ****************************************************************************/ /**************************************************************************** * IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES ****************************************************************************/ static void log (FILE *fp, const char *fmt, ...) FORMAT_2; /**************************************************************************** * IMPLEMENTATION PRIVATE FUNCTIONS ****************************************************************************/ static void destroy_constraint (struct constraint *c) { regfree (& c->re); xfree (c); } static void destroy_constraints (rarray_t *c) { int i; for (i = 0; i < c->count; i++){ destroy_constraint ((struct constraint *) c->array[i]); } rarray_destroy (c); } static void destroy_rule (struct rule *rule) { if (rule == NULL) return; destroy_rule (rule->next); if (rule->name) xfree (rule->name); if (rule->constraints) destroy_constraints (rule->constraints); if (rule->action) xfree (rule->action); xfree (rule); } static void store (void) { if (rules == NULL){ rules = prepared; last = prepared; } else { last->next = prepared; last = prepared; } prepared = NULL; } static int str_match (struct constraint *c, char *str) { int ret; if (str == NULL) return 0; ret = regexec (& c->re, str, 0, NULL, 0); if (ret && ret != REG_NOMATCH){ error_regex (ret, & c->re, NULL); } return ! ret; } static int addr_match (struct constraint *c, address_t *addr) { if (addr == NULL || addr->full == NULL) return 0; return str_match (c, addr->full); } static int raddr_match (struct constraint *c, raddress_t *ptr) { int i; if (ptr == NULL) return 0; for (i = 0; i < ptr->count; i++){ if (addr_match (c, ptr->array[i])) return 1; } return 0; } static void log (FILE *fp, const char *fmt, ...) { va_list ap; if (fp == NULL) return; va_start (ap, fmt); vfprintf (fp, fmt, ap); va_end (ap); } static int condition_met (struct constraint *c, mail_t *mail) { unsigned int i; switch (c->field){ case FIELD_INVALID: return 0; case FIELD_TO: return raddr_match (c, mail->to); case FIELD_FROM: return addr_match (c, mail->from); case FIELD_SUBJECT: return str_match (c, mail->subject); case FIELD_CC: return raddr_match (c, mail->cc); case FIELD_TOCC: return raddr_match (c, mail->to) || raddr_match (c, mail->cc); case FIELD_ANY: if (mail->headers) { for (i = 0; i< mail->headers->count; ++i) { char *txt = mail->headers->array[i]; if (str_match (c, txt)) return 1; } } return 0; } return 0; } static char * find_rule (struct rule *rule, mail_t *mail, FILE *fp) { int i; struct constraint *c; if (rule == NULL) return NULL; for (i = 0; i < rule->constraints->count; i++){ c = (struct constraint *) rule->constraints->array[i]; if (! condition_met (c, mail)) break; } if (i == rule->constraints->count){ log (fp, _("All constraints of the rule %s have been met. " "Delivering message to %s.\n"), rule->name, rule->action); return rule->action; } return find_rule (rule->next, mail, fp); } static int write_message (char *msg, FILE *fp) { char *seek = msg - 1; while (*++seek){ if (*seek == '.'){ if (seek >= msg + 2 && seek[-1] == '\n' && seek[-2] == '\r'){ if (seek[1] == '\r' && seek[2] == '\n') break; else continue; } } else if (*seek == '\r'){ continue; } if (fputc (*seek, fp) == EOF){ error_ (errno, "%s", _("Error while writing message. " "Message dropped.")); fclose (fp); return 1; } } return 0; } /**************************************************************************** * INTERFACE FUNCTIONS ****************************************************************************/ void procmail_init (void) { logfile = ask_for_default ("procmail_log", NULL); } void procmail_free_resources (void) { destroy_rule (rules); destroy_rule (prepared); if (constraint) destroy_constraint (constraint); rules = NULL; last = NULL; prepared = NULL; constraint = NULL; } void procmail_setup_name (char *name) { if (prepared != NULL) destroy_rule (prepared); prepared = xmalloc (sizeof (struct rule)); prepared->next = NULL; prepared->name = xstrdup (name); prepared->constraints = rarray_create_size (3); prepared->action = NULL; prepared->stop = 0; } void procmail_setup_header (char *header) { if (prepared == NULL) return; if (constraint != NULL) destroy_constraint (constraint); constraint = xmalloc (sizeof (struct constraint)); if (strcmp (header, "SUBJECT") == 0) constraint->field = FIELD_SUBJECT; else if (strcmp (header, "TO") == 0) constraint->field = FIELD_TO; else if (strcmp (header, "FROM") == 0) constraint->field = FIELD_FROM; else if (strcmp (header, "CC") == 0) constraint->field = FIELD_CC; else if (strcmp (header, "TOCC") == 0) constraint->field = FIELD_TOCC; else if (strcmp (header, "ANY") == 0) constraint->field = FIELD_ANY; else constraint->field = FIELD_INVALID; } void procmail_setup_re (char *re) { int ret; if (prepared == NULL || constraint == NULL) return; ret = regcomp (& constraint->re, re, REG_NOSUB | REG_NEWLINE); if (ret){ error_regex (ret, & constraint->re, re); destroy_constraint (constraint); destroy_rule (prepared); constraint = NULL; prepared = NULL; return; } rarray_add (prepared->constraints, constraint); constraint = NULL; } void procmail_setup_str (char *str) { char *re = misc_re_from_str (str); procmail_setup_re (re); xfree (re); } void procmail_setup_action (char *action, int stop) { if (prepared == NULL) return; prepared->action = xstrdup (action); prepared->stop = stop; store (); } char * procmail_box (mail_t *mail) { FILE *fp = NULL; char *box; if (logfile != NULL){ fp = fopen (logfile, "a"); if (fp == NULL) error_ (errno, _("Couldn't open logfile %s."), logfile); } box = find_rule (rules, mail, fp); if (box == NULL){ log (fp, _("The message didn't match any rule. Delivering " "to inbox.\n")); } if (fp) fclose (fp); if (box) return file_with_dir (mybox_dir, box); return mybox_inbox (); } void procmail_deliver (char *message) { char *box; char *fname; FILE *fp; while (1){ fname = wrapbox_fetch_where (1); fp = file_open (fname, "w+", O_RDWR | O_CREAT | O_EXCL, 0600); if (fp == NULL && errno == EEXIST) xfree (fname); else break; } if (fp == NULL){ error_ (errno, _("Couldn't open file %s. Message dropped."), fname); xfree (fname); return; } if (write_message (message, fp)){ xfree (fname); return; } rewind (fp); yyin = fp; mlex_scan_file (0, 1); fclose (fp); box = procmail_box (newmail); if (wrapbox_deliver_to (fname, box)){ char *inbox = mybox_inbox (); error_ (0, _("Couldn't deliver message to %s. Moving to " "%s instead."), box, inbox); if (wrapbox_deliver_to (fname, inbox)){ error_ (0, _("Delivery to %s failed too. Please " "move the file %s manually to one of " "your boxes."), inbox, fname); } xfree (inbox); } mail_destroy (newmail, BOX_INVALID); xfree (box); xfree (fname); } /**************************************************************************** * INTERFACE CLASS BODIES ****************************************************************************/ /**************************************************************************** * * END MODULE procmail.c * ****************************************************************************/