/* 
   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 <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <regex.h>
#include <errno.h>
#include <fcntl.h>

#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
 *
 ****************************************************************************/


syntax highlighted by Code2HTML, v. 0.9.1