Here's my rewrite of pipe.c, implementing the temporary storage of
messages to a "dbmail user" and adding a hook for filters.

Next up will be a rewrite of forward.c to implement a limitation on the
number of external processes that get spawned at once. I'm also
considering *removing* the ability to forward directly from a FILE*,
because otherwise I'd have to duplicate the temporary storage code.

If there's anything pertinent that I'm leaving out, let me know and I'll
see if I can code it in! Notable exception: the schema stays as-is :-P

Aaron
/* $Id: pipe.c,v 1.95 2003/03/17 16:04:08 roel Exp $
 * (c) 2000-2002 IC&S, The Netherlands 
 *
 * Functions for reading the pipe from the MTA */


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <time.h>
#include <ctype.h>
#include "db.h"
#include "auth.h"
#include "debug.h"
#include "list.h"
#include "bounce.h"
#include "forward.h"
#include "dbmail.h"
#include "pipe.h"
#include "debug.h"
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include "dbmd5.h"
#include "misc.h"


#define QUERY_SIZE 255
#define MAX_U64_STRINGSIZE 40
#define MAX_COMM_SIZE 512

#define AUTO_NOTIFY_SENDER "[EMAIL PROTECTED]"
#define AUTO_NOTIFY_SUBJECT "NEW MAIL NOTIFICATION"

#define DBMAIL_USERIDNR 0
#define DBMAIL_TEMPMBOX "INBOX"

extern struct list smtpItems, sysItems;


void create_unique_id(char *target, u64_t messageid)
{
  trace (TRACE_DEBUG,"create_unique_id(): creating id");
  srand((int) ((int) time(NULL) + (int) getpid()) );
  snprintf (target,UID_SIZE,"%s",makemd5( itoa((int) rand() * (int) messageid) 
));
  trace (TRACE_DEBUG,"create_unique_id(): created: %s",target);
}


/* Automate the grunt work of figuring
 * out where this 'address' really leads!
 * */
int resolve_address(char *address, struct list userids, struct list forwards, 
struct list bounces)
{
  int userid = 0, this_user = 0, this_domain = 0;
  char userid_string[MAX_U64_STRINGSIZE];
  char *domain = NULL;

  /* See if the given as a numeric string from the dbase */
  userid = auth_user_exists(address);
  /* An error occurred */
  if (userid == -1)
    {
      trace(TRACE_ERROR,"insert_messages(): error checking user [%s]", address);
    }
  /* The address was a valid username */
  else if (userid > 0)
    {
      snprintf(userid_string, MAX_U64_STRINGSIZE, "%llu", userid);
      if (list_nodeadd(&userids, userid_string, strlen(userid_string)+1) == 0)
          trace(TRACE_FATAL, "insert_messages(): out of memory");

      trace(TRACE_DEBUG, "insert_messages(): added user [%s] id [%s] to 
delivery list",
              address, userid_string);
    }
  /* The address needs to be looked up */
  else
    {
      this_user = auth_check_user_ext(address, &userids, &forwards, -1);
      trace (TRACE_DEBUG,"insert_messages(): "
              "user [%s] found total of [%d] aliases",address,
              this_user);

      /* No aliases found for this user */
      if (this_user==0)
        {
          /* I needed to change this because my girlfriend said so
             and she was actually right. Domain forwards are last resorts
             if a delivery cannot be found with an existing address then
             and only then we need to check if there are domain delivery's */
  
          trace (TRACE_INFO,"insert_messages(): no users found to deliver to. "
                  "Checking for domain forwards");  
  
          domain = strchr(address,'@');

          /* This should always be the case! */
          if (domain!=NULL)
            {
              trace (TRACE_DEBUG,"insert_messages(): "
                      "checking for domain aliases. Domain = [%s]",domain);
  
              /* Checking for domain aliases */
              this_domain = auth_check_user_ext(domain, &userids, &forwards, 
-1);
              trace (TRACE_DEBUG,"insert_messages(): "
                      "domain [%s] found total of [%d] aliases",domain,
                      this_domain);
            }
        }
  
      /* user does not exists in aliases tables
         so bounce this message back with an error message */
      if (this_user==0 && this_domain==0)
        {
          /* still no effective deliveries found, create bouncelist */
          list_nodeadd(&bounces, address, strlen(address)+1);
        }
    }
  return 0;
}

  
/* Read from insteam until eof, and store to the
 * dedicated dbmail user account. Later, we'll
 * read the message back for forwarding and 
 * filtering for local users before db_copymsg()'ing
 * it into their own mailboxes.
 *
 * returns a message id number, or -1 on error.
 * */
u64_t store_message_temp( FILE *instream, char *header, u64_t headersize )
{
  u64_t msgidnr=0, i=0;
  u64_t usedmem=0, totalmem=0;
  char *unique_id=NULL, *strblock=NULL;

  create_unique_id(unique_id, 0); 

  /* create a message record */
  msgidnr = db_insert_message( DBMAIL_USERIDNR, DBMAIL_TEMPMBOX, unique_id );

  if (db_insert_message_block(header, headersize, msgidnr) == -1)
    {
      trace(TRACE_STOP, "store_message_temp(): error inserting msgblock 
[header]\n");
      return -1;
    }

  trace (TRACE_DEBUG,"store_message_temp(): allocating [%d] bytes of memory for 
readblock", READ_BLOCK_SIZE);

  memtst ((strblock = (char *)my_malloc(READ_BLOCK_SIZE+1))==NULL);
  
  totalmem = 0; /* reset totalmem counter */

  /* we have local deliveries */ 
  while (!feof(instream))
    {
      usedmem = fread (strblock, sizeof(char), READ_BLOCK_SIZE, instream);
      if (ferror(instream))
        {
          trace(TRACE_ERROR,"store_message_temp(): error on instream: [%s]", 
strerror(errno));
          return -1;
        }
     
      /* replace all errorneous '\0' by ' ' (space) */
      for (i=0; i<usedmem; i++)
        {
          if (strblock[i] == '\0')
            {
              strblock[i] = ' '; 
            }
        }

      /* fread won't do this for us! */ 
      strblock[usedmem]='\0';
        
      if (usedmem>0) /* usedmem is 0 with an EOF */
        {
          totalmem = totalmem + usedmem;
        
          if (db_insert_message_block (strblock, usedmem, msgidnr)==-1)
            {
              trace(TRACE_STOP, "store_message_temp(): error inserting 
msgblock\n");
              return -1;
            }
        }

      /* resetting strlen for strblock */
      strblock[0] = '\0';
      usedmem = 0;
    }

  trace (TRACE_DEBUG, "store_message_temp(): end of instream");

  trace (TRACE_DEBUG, "store_message_temp(): uniqueid freed");
  my_free(unique_id);

  trace (TRACE_DEBUG, "store_message_temp(): strblock freed");
  my_free (strblock);

  return msgidnr;
}


/*
 * Send an automatic notification using sendmail
 */
int send_notification(const char *to, const char *from, const char *subject)
{
  FILE *mailpipe = NULL;
  field_t sendmail;
  int result;

  GetConfigValue("SENDMAIL", &smtpItems, sendmail);
  if (sendmail[0] == '\0')
    trace(TRACE_FATAL, "send_notification(): SENDMAIL not configured (see 
config file). Stop.");

  trace(TRACE_DEBUG, "send_notification(): found sendmail command to be [%s]", 
sendmail);

  if (! (mailpipe = popen(sendmail, "w")) )
    {
      trace(TRACE_ERROR, "send_notification(): could not open pipe to sendmail 
using cmd [%s]", sendmail);
      return 1;
    }

  trace(TRACE_DEBUG, "send_notification(): pipe opened, sending data");

  fprintf(mailpipe, "To: %s\n", to);
  fprintf(mailpipe, "From: %s\n", from);
  fprintf(mailpipe, "Subject: %s\n", subject);
  fprintf(mailpipe, "\n");

  result = pclose(mailpipe);
  trace(TRACE_DEBUG, "send_notification(): pipe closed");

  if (result != 0)
    trace(TRACE_ERROR,"send_notification(): reply could not be sent: sendmail 
error");

  return 0;
}
  

/*
 * Send an automatic reply using sendmail
 */
int send_reply(struct list *headerfields, const char *body)
{
  struct element *el;
  struct mime_record *record;
  char *from = NULL, *to = NULL, *replyto = NULL, *subject = NULL;
  FILE *mailpipe = NULL;
  char comm[MAX_COMM_SIZE];
  field_t sendmail;
  int result;

  GetConfigValue("SENDMAIL", &smtpItems, sendmail);
  if (sendmail[0] == '\0')
    trace(TRACE_FATAL, "send_reply(): SENDMAIL not configured (see config 
file). Stop.");

  trace(TRACE_DEBUG, "send_reply(): found sendmail command to be [%s]", 
sendmail);
  
  /* find To: and Reply-To:/From: field */
  el = list_getstart(headerfields);
  
  while (el)
    {
      record = (struct mime_record*)el->data;
      
      if (strcasecmp(record->field, "from") == 0)
  {
    from = record->value;
    trace(TRACE_DEBUG, "send_reply(): found FROM [%s]", from);
  }
      else if  (strcasecmp(record->field, "reply-to") == 0)
  {
    replyto = record->value;
    trace(TRACE_DEBUG, "send_reply(): found REPLY-TO [%s]", replyto);
  }
      else if  (strcasecmp(record->field, "subject") == 0)
  {
    subject = record->value;
    trace(TRACE_DEBUG, "send_reply(): found SUBJECT [%s]", subject);
  }
      else if  (strcasecmp(record->field, "deliver-to") == 0)
  {
    to = record->value;
    trace(TRACE_DEBUG, "send_reply(): found TO [%s]", to);
  }

      el = el->nextnode;
    }

  if (!from && !replyto)
    {
      trace(TRACE_ERROR, "send_reply(): no address to send to");
      my_free(sendmail);
      return 0;
    }

  trace(TRACE_DEBUG, "send_reply(): header fields scanned; opening pipe to 
sendmail");
  snprintf(comm, MAX_COMM_SIZE, "%s %s", sendmail, replyto ? replyto : from);

  if (! (mailpipe = popen(comm, "w")) )
    {
      trace(TRACE_ERROR, "send_reply(): could not open pipe to sendmail using 
cmd [%s]", comm);
      return 1;
    }

  trace(TRACE_DEBUG, "send_reply(): sending data");
  
  fprintf(mailpipe, "To: %s\n", replyto ? replyto : from);
  fprintf(mailpipe, "From: %s\n", to ? to : "(unknown)");
  fprintf(mailpipe, "Subject: AW: %s\n", subject ? subject : "<no subject>");
  fprintf(mailpipe, "\n");
  fprintf(mailpipe, "%s\n", body ? body : "--");

  result = pclose(mailpipe);
  trace(TRACE_DEBUG, "send_reply(): pipe closed");
  if (result != 0)
    trace(TRACE_ERROR,"send_reply(): reply could not be sent: sendmail error");

  return 0;
}

  
/* Yeah, RAN. That's Reply And Notifu ;-) */
int execute_auto_ran(u64_t useridnr, struct list *headerfields)
{
  field_t val;
  int do_auto_notify = 0, do_auto_reply = 0;
  char *reply_body = NULL;
  char *notify_address = NULL;

  /* message has been succesfully inserted, perform auto-notification & 
auto-reply */
  GetConfigValue("AUTO_NOTIFY", &smtpItems, val);
  if (strcasecmp(val, "yes") == 0)
      do_auto_notify = 1;

  GetConfigValue("AUTO_REPLY", &smtpItems, val);
  if (strcasecmp(val, "yes") == 0)
     do_auto_reply = 1;

  if (do_auto_notify)
    {
      trace(TRACE_DEBUG, "execute_auto_ran(): starting auto-notification 
procedure");

      if (db_get_nofity_address(useridnr, &notify_address) != 0)
          trace(TRACE_ERROR, "insert_messages(): error fetching notification 
address");
      else
        {
          if (notify_address == NULL)
              trace(TRACE_DEBUG, "execute_auto_ran(): no notification address 
specified, skipping");
          else
            {
              trace(TRACE_DEBUG, "insert_messages(): sending notifcation to 
[%s]", notify_address);
              send_notification(notify_address, AUTO_NOTIFY_SENDER, 
AUTO_NOTIFY_SUBJECT);
              my_free(notify_address);
            }
        }
    }
        
  if (do_auto_reply)
    {
      trace(TRACE_DEBUG, "insert_messages(): starting auto-reply procedure");

      if (db_get_reply_body(useridnr, &reply_body) != 0)
          trace(TRACE_ERROR, "insert_messages(): error fetching reply body");
      else
        {
          if (reply_body == NULL || reply_body[0] == '\0')
              trace(TRACE_DEBUG, "insert_messages(): no reply body specified, 
skipping");
          else
            {
              send_reply(headerfields, reply_body);
              my_free(reply_body);
            }
        }
    }

  return 0;
}
            
        
/* Here's the real *meat* of this source file!
 *
 * Function: insert_messages()
 * What we get:
 *   - A pointer to the incoming message stream
 *   - The header of the message 
 *   - A list of destination addresses / useridnr's
 *   - The default mailbox to delivery to
 *
 * What we do:
 *   - Read in the rest of the message
 *   - Store the message to the DBMAIL user
 *   - Process the destination addresses into lists:
 *     - Local useridnr's
 *     - External forwards
 *     - No such user bounces
 *   - Store the local useridnr's
 *     - Run the message through each user's filters
 *     - Potentially alter the delivery:
 *       - Different mailbox
 *       - Bounce
 *       - Reply with vacation message
 *       - Forward to another address
 *     - Check the user's quota before delivering
 *       - Do this *after* their filters, since the
 *         filter might not store the message anyways
 *   - Send out the no such user bounces
 *   - Send out the external forwards
 *   - Delete the temporary message from the database
 */
int insert_messages(FILE *instream, char *header, u64_t headersize,
    struct list *users, struct list *returnpath,
    int users_are_usernames, char *deliver_to_mailbox, struct list 
*headerfields)
{
  struct element *tmp,*ret_path;
  char *unique_id;
  char *strblock;
  char *domain, *ptr;
  char *tmpbuffer=NULL;
  char *bounce_id;
  size_t usedmem=0, totalmem=0;
  char userid_string[MAX_U64_STRINGSIZE];
  struct list userids;
  struct list forwards;
  struct list bounces;
  u64_t tmpmsgidnr, msgidnr;
  u64_t userid, mailbox, bounce_userid;
  int i, this_user;
  char *reply_body, *notify_address;

  
  memtst((unique_id = (char *)my_malloc(UID_SIZE))==NULL);

  /* Initialize several lists */
  list_init(&userids);
  list_init(&forwards);
  list_init(&bounces);

  /* Get the first target address */
  tmp = list_getstart(users);
  while (tmp!=NULL)
  {
      /* Search for where this thing is gonna go */
      resolve_address((char *)tmp->data, userids, forwards, bounces);
      /* Get the next taget in list */
      tmp=tmp->nextnode;
  }

  /* Read in the rest of the stream and store it into a temporary message */
  tmpmsgidnr = store_message_temp(instream, header, headersize);
  
  /* Get first target userid */
  tmp = list_getstart(&userids);
  while (tmp!=NULL)
    { 
      /* Make the id numeric */
      userid = strtoull((char *)tmp->data, NULL, 10);

      mailbox = db_findmailbox(deliver_to_mailbox, userid);
      
      /* db_copymsg() won't deliver over the quota */
      msgidnr = db_copymsg(tmpmsgidnr, mailbox);
      switch (msgidnr)
        {
          case -2:
            bounce_id = auth_get_userid(&userid);
            bounce (header, headersize, bounce_id, 
BOUNCE_STORAGE_LIMIT_REACHED);
            my_free (bounce_id);
            break;
          case -1:
            trace(TRACE_ERROR, "insert_messages(): error copying message to 
user [%llu]", userid);
            break;
          default:
            trace (TRACE_MESSAGE,"insert_messages(): message id=%llu, size=%llu 
is inserted",
             msgidnr, totalmem+headersize);

            /* Create a unique ID for this message;
             * Each message for each user must have a unique ID! 
             * */
            create_unique_id (unique_id, msgidnr); 
            db_update_message (msgidnr, unique_id, totalmem+headersize, 0);

            /* Run the user's filters on this message
             * Limitations: since we already had to copy the message
             * to the user, we aren't able to do anything if they're
             * over quota. This might have to change... for example,
             * why bounce the message for a quota violation if the
             * filters are going to forward and not keep it anyways?
             *
             * On the other hand, this approach is more consistent;
             * it would be confusing if some messages "worked" and
             * some "didn't work" from the user's point of view.
             * */
            //execute_filters(msgidnr, userid);
            
            execute_auto_ran(userid, headerfields);

            break;
        }

      /* Get next item */ 
      tmp=tmp->nextnode;
    }

  trace(TRACE_DEBUG,"insert_messages(): we need to bounce [%ld] "
      "messages for non existent users", list_totalnodes(&bounces));
  
  /* handle all bounced messages */
  if (list_totalnodes(&bounces)>0)
    {
      /* bouncing invalid messages */
      tmp=list_getstart(&bounces);
      while (tmp!=NULL)
        { 
          bounce (header, headersize, (char *)tmp->data, BOUNCE_NO_SUCH_USER);
          tmp=tmp->nextnode;  
        }
    }

  trace(TRACE_DEBUG,"insert_messages(): we need to deliver [%ld] "
      "messages to external addresses", list_totalnodes(&forwards));
  
  /* do we have forward addresses ? */
  if (list_totalnodes(&forwards)>0)
    {
      /* sending the message to forwards */
  
      trace (TRACE_DEBUG,"insert_messages(): delivering to external addresses");
  
      ret_path = list_getstart(returnpath);
      
      /* deliver using database */
      pipe_forward (stdin, &forwards, ret_path ? ret_path->data: 
"DBMAIL-MAILER", header, tmpmsgidnr);
    }

  trace (TRACE_DEBUG,"insert_messages(): deleting temporary message from 
database");
  /* Contrived use of this function.
   * It was written for imapcommands.c, not general use :-\
   * */
  /* db_set_msgflag(tmpmsgidnr, tmpmailbox, blah[2]=1, IMAPFA_ADD); */

  /* There, just get rid of it already! */
  db_delete_message(tmpmsgidnr);
  
  trace (TRACE_DEBUG,"insert_messages(): Freeing memory blocks");

  /* memory cleanup */
  if (tmpbuffer!=NULL)
    {
      trace (TRACE_DEBUG,"insert_messages(): tmpbuffer freed");
      my_free(tmpbuffer);
      tmpbuffer = NULL;
    }

  trace (TRACE_DEBUG,"insert_messages(): header freed");
  my_free(header);

  trace (TRACE_DEBUG,"insert_messages(): End of function");
  
  list_freelist(&bounces.start);
  list_freelist(&userids.start);
  list_freelist(&forwards.start);
  
  return 0;
}

Reply via email to