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, ¬ify_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;
}