/**
* \file local.c
* Local delivery of mail via a MDA.
*/
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include "local.h"
#include "main.h"
#include "xmalloc.h"
char *mda = NULL;
char *force_mda = NULL;
FILE *mda_fp = NULL;
int local_address(const char *address)
{
if (!force_mda)
{
/* TODO:
*
* A litle more searching
* Does the regex"[^@]*\@(.*)" resolve to 127.0.0.1 or a local ip?
*
* Maybe it should be delivered locally?
*/
return !strchr(address, '@');
}
return 1;
}
/** replace ' by _ */
static void sanitize(char *s)
{
char *cp;
for (cp = s; (cp = strchr (cp, '\'')); cp++)
*cp = '_';
}
/**
* Pipe the message to the MDA for local delivery.
*
* Based on fetchmail's open_mda_sink().
*/
void local_init(message_t *message)
{
int length = 0, fromlen = 0, nameslen = 0;
char *names = NULL, *before, *after, *from = NULL;
if (!mda)
{
fprintf(stderr, "Local delivery not possible without a MDA\n");
exit(EX_OSFILE);
}
length = strlen(mda);
before = xstrdup(mda);
/* get user addresses for %T */
if (strstr(before, "%T"))
{
if (!force_mda)
{
struct list_head *ptr;
char *p;
/*
* We go through this in order to be able to handle very
* long lists of users and (re)implement %s.
*/
nameslen = 0;
list_for_each(ptr, &message->local_recipients)
{
recipient_t *recipient = list_entry(ptr, recipient_t, list);
assert(recipient->address);
nameslen += (strlen(recipient->address) + 3); /* string + quotes + ' ' */
}
names = (char *)xmalloc(nameslen + 1); /* account for '\0' */
p = names;
list_for_each(ptr, &message->local_recipients)
{
recipient_t *recipient = list_entry(ptr, recipient_t, list);
int written;
sanitize(recipient->address);
written = sprintf(p, "'%s' ", recipient->address);
if (written < 0)
{
perror(NULL);
exit(EX_OSERR);
}
p += written;
}
names[--nameslen] = '\0'; /* chop trailing space */
} else {
nameslen = (strlen(force_mda) + 3); // 'force_mda'
names = (char *)xmalloc(nameslen + 1); // 'force_mda'\0
// Let's skip sanitization?
// Hope the config isn't evil to us 0.o
if (sprintf(names, "'%s' ", force_mda) < 0)
{
perror(NULL);
exit(EX_OSERR);
}
}
}
/* get From address for %F */
if (strstr(before, "%F"))
{
from = xstrdup(message->reverse_path ? message->reverse_path : "");
sanitize(from);
fromlen = strlen(from);
}
/* do we have to build an mda string? */
if (names || from)
{
char *sp, *dp;
/* find length of resulting mda string */
sp = before;
while ((sp = strstr(sp, "%s"))) {
length += nameslen; /* subtract %s and add '' */
sp += 2;
}
sp = before;
while ((sp = strstr(sp, "%T"))) {
length += nameslen; /* subtract %T and add '' */
sp += 2;
}
sp = before;
while ((sp = strstr(sp, "%F"))) {
length += fromlen; /* subtract %F and add '' */
sp += 2;
}
after = xmalloc(length + 1);
/* copy mda source string to after, while expanding %[sTF] */
for (dp = after, sp = before; (*dp = *sp); dp++, sp++) {
if (sp[0] != '%') continue;
/* need to expand? BTW, no here overflow, because in
** the worst case (end of string) sp[1] == '\0' */
if (sp[1] == 'T') {
strcpy(dp, names);
dp += nameslen;
sp++; /* position sp over [sT] */
dp--; /* adjust dp */
} else if (sp[1] == 'F') {
*dp++ = '\'';
strcpy(dp, from);
dp += fromlen;
*dp++ = '\'';
sp++; /* position sp over F */
dp--; /* adjust dp */
}
}
if (names) {
free(names);
names = NULL;
}
if (from) {
free(from);
from = NULL;
}
free(before);
before = after;
}
if(!(mda_fp = popen(before, "w")))
{
fprintf(stderr, "Failed to connect to MDA\n");
exit(EX_OSERR);
}
if(verbose)
fprintf(stdout, "Connected to MDA: %s\n", before);
free(before);
}
void local_flush(message_t *message)
{
char buffer[BUFSIZ];
size_t n;
do {
n = message_read(message, buffer, BUFSIZ);
if(ferror(mda_fp))
{
perror(NULL);
exit(EX_OSERR);
}
} while(n == BUFSIZ);
}
void local_cleanup(void)
{
if(mda_fp)
{
int status;
if((status = pclose(mda_fp)))
{
if(WIFSIGNALED(status))
fprintf(stderr, "MDA died of signal %d\n", WTERMSIG(status));
else if(WIFEXITED(status))
fprintf(stderr, "MDA returned nonzero status %d\n", WEXITSTATUS(status));
else
fprintf(stderr, "MDA failed\n");
exit(EX_OSERR);
}
mda_fp = NULL;
if(verbose)
fprintf(stdout, "Disconnected to MDA\n");
}
if(mda)
free(mda);
if(force_mda)
free(force_mda);
}