Commit 7ee2b81a 7ee2b81ae5a0c39961542b27037374e538daa231 by Wojciech Polak

Moved from ../../mailbox

1 parent 2085f447
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2004, 2005,
2006, 2007 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301 USA */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef ENABLE_SENDMAIL
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <mailutils/address.h>
#include <mailutils/debug.h>
#include <mailutils/message.h>
#include <mailutils/observer.h>
#include <mailutils/property.h>
#include <mailutils/stream.h>
#include <mailutils/url.h>
#include <mailutils/header.h>
#include <mailutils/body.h>
#include <mailutils/errno.h>
#include <mailer0.h>
#include <registrar0.h>
static struct _mu_record _sendmail_record =
{
MU_SENDMAIL_PRIO,
MU_SENDMAIL_SCHEME,
_url_sendmail_init, /* url init. */
NULL, /* Mailbox entry. */
_mailer_sendmail_init, /* Mailer entry. */
NULL, /* Folder entry. */
NULL, /* No need for a back pointer. */
NULL, /* _is_scheme method. */
NULL, /* _get_url method. */
NULL, /* _get_mailbox method. */
NULL, /* _get_mailer method. */
NULL /* _get_folder method. */
};
/* We export, url parsing and the initialisation of
the mailbox, via the register entry/record. */
mu_record_t mu_sendmail_record = &_sendmail_record;
struct _sendmail
{
int dsn;
char *path;
pid_t pid;
off_t offset;
int fd;
enum sendmail_state { SENDMAIL_CLOSED, SENDMAIL_OPEN, SENDMAIL_SEND } state;
};
typedef struct _sendmail * sendmail_t;
static void sendmail_destroy (mu_mailer_t);
static int sendmail_open (mu_mailer_t, int);
static int sendmail_close (mu_mailer_t);
static int sendmail_send_message (mu_mailer_t, mu_message_t, mu_address_t, mu_address_t);
int
_mailer_sendmail_init (mu_mailer_t mailer)
{
sendmail_t sendmail;
/* Allocate memory specific to sendmail mailer. */
sendmail = mailer->data = calloc (1, sizeof (*sendmail));
if (mailer->data == NULL)
return ENOMEM;
sendmail->state = SENDMAIL_CLOSED;
mailer->_destroy = sendmail_destroy;
mailer->_open = sendmail_open;
mailer->_close = sendmail_close;
mailer->_send_message = sendmail_send_message;
/* Set our properties. */
{
mu_property_t property = NULL;
mu_mailer_get_property (mailer, &property);
mu_property_set_value (property, "TYPE", "SENDMAIL", 1);
}
return 0;
}
static void
sendmail_destroy(mu_mailer_t mailer)
{
sendmail_t sendmail = mailer->data;
if (sendmail)
{
if (sendmail->path)
free (sendmail->path);
free (sendmail);
mailer->data = NULL;
}
}
static int
sendmail_open (mu_mailer_t mailer, int flags)
{
sendmail_t sendmail = mailer->data;
int status;
char *path;
/* Sanity checks. */
if (sendmail == NULL)
return EINVAL;
mailer->flags = flags;
if ((status = mu_url_aget_path (mailer->url, &path)))
return status;
if (access (path, X_OK) == -1)
{
free (path);
return errno;
}
sendmail->path = path;
sendmail->state = SENDMAIL_OPEN;
MAILER_DEBUG1 (mailer, MU_DEBUG_TRACE, "sendmail (%s)\n", sendmail->path);
return 0;
}
static int
sendmail_close (mu_mailer_t mailer)
{
sendmail_t sendmail = mailer->data;
/* Sanity checks. */
if (sendmail == NULL)
return EINVAL;
if(sendmail->path)
free(sendmail->path);
sendmail->path = NULL;
sendmail->state = SENDMAIL_CLOSED;
return 0;
}
static int
mailer_property_is_set (mu_mailer_t mailer, const char *name)
{
mu_property_t property = NULL;
mu_mailer_get_property (mailer, &property);
return mu_property_is_set (property, name);
}
/* Close FD unless it is part of pipe P */
#define SCLOSE(fd,p) if (p[0]!=fd&&p[1]!=fd) close(fd)
static int
sendmail_send_message (mu_mailer_t mailer, mu_message_t msg, mu_address_t from,
mu_address_t to)
{
sendmail_t sendmail = mailer->data;
int status = 0;
if (sendmail == NULL || msg == NULL)
return EINVAL;
switch (sendmail->state)
{
case SENDMAIL_CLOSED:
return EINVAL;
case SENDMAIL_OPEN:
{
int tunnel[2];
int argc = 0;
const char **argvec = NULL;
size_t tocount = 0;
const char *emailfrom = NULL;
/* Count the length of the arg vec: */
argc++; /* terminating NULL */
argc++; /* sendmail */
argc++; /* -oi (do not treat '.' as message
terminator) */
if (from)
{
if ((status = mu_address_sget_email (from, 1, &emailfrom)) != 0)
goto OPEN_STATE_CLEANUP;
if (!emailfrom)
{
/* the address wasn't fully qualified, choke (for now) */
status = EINVAL;
MAILER_DEBUG1 (mailer, MU_DEBUG_TRACE,
"envelope from (%s) not fully qualifed\n",
emailfrom);
goto OPEN_STATE_CLEANUP;
}
argc += 2; /* -f from */
}
if (to)
{
status = mu_address_get_email_count (to, &tocount);
assert (!status);
assert (tocount);
argc += tocount; /* 1 per to address */
}
argc++; /* -t */
/* Allocate arg vec: */
if ((argvec = calloc (argc, sizeof (*argvec))) == 0)
{
status = ENOMEM;
goto OPEN_STATE_CLEANUP;
}
argc = 0;
argvec[argc++] = sendmail->path;
argvec[argc++] = "-oi";
if (from)
{
argvec[argc++] = "-f";
argvec[argc++] = emailfrom;
}
if (!to || mailer_property_is_set (mailer, "READ_RECIPIENTS"))
{
argvec[argc++] = "-t";
}
else
{
int i = 1;
size_t count = 0;
mu_address_get_count (to, &count);
for (; i <= count; i++)
{
const char *email;
if ((status = mu_address_sget_email (to, i, &email)) != 0)
goto OPEN_STATE_CLEANUP;
if (!email)
{
/* the address wasn't fully qualified, choke (for now) */
status = EINVAL;
MAILER_DEBUG1 (mailer, MU_DEBUG_TRACE,
"envelope to (%s) not fully qualifed\n",
email);
goto OPEN_STATE_CLEANUP;
}
argvec[argc++] = email;
}
}
assert (argvec[argc] == NULL);
if (pipe (tunnel) == 0)
{
sendmail->fd = tunnel[1];
sendmail->pid = vfork ();
if (sendmail->pid == 0) /* Child. */
{
SCLOSE (STDIN_FILENO, tunnel);
SCLOSE (STDOUT_FILENO, tunnel);
SCLOSE (STDERR_FILENO, tunnel);
close (tunnel[1]);
dup2 (tunnel[0], STDIN_FILENO);
execv (sendmail->path, (char**) argvec);
exit (errno);
}
else if (sendmail->pid == -1)
{
status = errno;
MAILER_DEBUG1 (mailer, MU_DEBUG_TRACE,
"vfork() failed: %s\n", strerror (status));
}
}
else
{
status = errno;
MAILER_DEBUG1 (mailer, MU_DEBUG_TRACE,
"pipe() failed: %s\n", strerror (status));
}
OPEN_STATE_CLEANUP:
MAILER_DEBUG0 (mailer, MU_DEBUG_TRACE, "exec argv:");
for (argc = 0; argvec && argvec[argc]; argc++)
{
MAILER_DEBUG1 (mailer, MU_DEBUG_TRACE, " %s", argvec[argc]);
}
MAILER_DEBUG0 (mailer, MU_DEBUG_TRACE, "\n");
free (argvec);
close (tunnel[0]);
if (status != 0)
{
close (sendmail->fd);
break;
}
sendmail->state = SENDMAIL_SEND;
}
case SENDMAIL_SEND:
{
mu_stream_t stream = NULL;
char buffer[512];
size_t len = 0;
int rc;
size_t offset = 0;
mu_header_t hdr;
mu_body_t body;
int found_nl = 0;
int exit_status;
mu_message_get_header (msg, &hdr);
mu_header_get_stream (hdr, &stream);
MAILER_DEBUG0 (mailer, MU_DEBUG_TRACE, "Sending headers...\n");
while ((status = mu_stream_readline (stream, buffer, sizeof (buffer),
offset, &len)) == 0
&& len != 0)
{
if (strncasecmp (buffer, MU_HEADER_FCC,
sizeof (MU_HEADER_FCC) - 1))
{
MAILER_DEBUG1 (mailer, MU_DEBUG_PROT,
"Header: %s", buffer);
if (write (sendmail->fd, buffer, len) == -1)
{
status = errno;
MAILER_DEBUG1 (mailer, MU_DEBUG_TRACE,
"write() failed: %s\n", strerror (status));
break;
}
}
found_nl = (len == 1 && buffer[0] == '\n');
offset += len;
sendmail->offset += len;
}
if (!found_nl)
{
if (write (sendmail->fd, "\n", 1) == -1)
{
status = errno;
MAILER_DEBUG1 (mailer, MU_DEBUG_TRACE,
"write() failed: %s\n", strerror (status));
}
}
mu_message_get_body (msg, &body);
mu_body_get_stream (body, &stream);
MAILER_DEBUG0 (mailer, MU_DEBUG_TRACE, "Sending body...\n");
offset = 0;
while ((status = mu_stream_read (stream, buffer, sizeof (buffer),
offset, &len)) == 0
&& len != 0)
{
if (write (sendmail->fd, buffer, len) == -1)
{
status = errno;
MAILER_DEBUG1 (mailer, MU_DEBUG_TRACE,
"write() failed: %s\n", strerror (status));
break;
}
offset += len;
sendmail->offset += len;
}
if (status == EAGAIN)
return status;
close (sendmail->fd);
rc = waitpid (sendmail->pid, &exit_status, 0);
if (rc < 0)
{
if (errno == ECHILD)
status = 0;
else
{
status = errno;
MAILER_DEBUG2 (mailer, MU_DEBUG_TRACE,
"waitpid(%d) failed: %s\n",
sendmail->pid, strerror (status));
}
}
else if (WIFEXITED (exit_status))
{
exit_status = WEXITSTATUS (exit_status);
MAILER_DEBUG2 (mailer, MU_DEBUG_TRACE,
"%s exited with: %d\n",
sendmail->path, exit_status);
status = (exit_status == 0) ? 0 : MU_ERR_PROCESS_EXITED;
}
else if (WIFSIGNALED (exit_status))
status = MU_ERR_PROCESS_SIGNALED;
else
status = MU_ERR_PROCESS_UNKNOWN_FAILURE;
/* Shouldn't this notification only happen on success? */
mu_observable_notify (mailer->observable, MU_EVT_MAILER_MESSAGE_SENT);
}
default:
break;
}
sendmail->state = (status == 0) ? SENDMAIL_OPEN : SENDMAIL_CLOSED;
return status;
}
#else
#include <stdio.h>
#include <registrar0.h>
mu_record_t mu_sendmail_record = NULL;
#endif
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2004, 2005,
2006, 2007 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301 USA */
/** @file smtp.c
@brief an SMTP mailer
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef ENABLE_SMTP
#include <errno.h>
#include <ctype.h>
#include <netdb.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <mailutils/address.h>
#include <mailutils/debug.h>
#include <mailutils/errno.h>
#include <mailutils/header.h>
#include <mailutils/body.h>
#include <mailutils/message.h>
#include <mailutils/mutil.h>
#include <mailutils/observer.h>
#include <mailutils/property.h>
#include <mailutils/stream.h>
#include <mailutils/url.h>
#include <mailutils/tls.h>
#include <mailer0.h>
#include <registrar0.h>
static struct _mu_record _smtp_record = {
MU_SMTP_PRIO,
MU_SMTP_SCHEME,
_url_smtp_init, /* url init. */
NULL, /* Mailbox init. */
&_mailer_smtp_init, /* Mailer init. */
NULL, /* Folder init. */
NULL, /* No need for a back pointer. */
NULL, /* _is_scheme method. */
NULL, /* _get_url method. */
NULL, /* _get_mailbox method. */
NULL, /* _get_mailer method. */
NULL /* _get_folder method. */
};
/* We export : url parsing and the initialisation of
the mailbox, via the register entry/record. */
mu_record_t mu_smtp_record = &_smtp_record;
struct _smtp
{
mu_mailer_t mailer;
char *mailhost;
char *localhost;
/* IO buffering. */
char *buffer; /* Must be freed. */
size_t buflen;
char *ptr;
char *nl;
off_t s_offset;
enum smtp_state
{
SMTP_NO_STATE, SMTP_OPEN, SMTP_GREETINGS, SMTP_EHLO, SMTP_EHLO_ACK,
SMTP_HELO, SMTP_HELO_ACK, SMTP_QUIT, SMTP_QUIT_ACK, SMTP_ENV_FROM,
SMTP_ENV_RCPT, SMTP_MAIL_FROM, SMTP_MAIL_FROM_ACK, SMTP_RCPT_TO,
SMTP_RCPT_TO_ACK, SMTP_DATA, SMTP_DATA_ACK, SMTP_SEND, SMTP_SEND_ACK,
SMTP_SEND_DOT, SMTP_STARTTLS, SMTP_STARTTLS_ACK
}
state;
int extended;
unsigned long capa; /* Server capabilities */
const char *mail_from;
mu_address_t rcpt_to; /* Destroy this if not the same as argto below. */
mu_address_t rcpt_bcc;
size_t rcpt_to_count;
size_t rcpt_bcc_count;
size_t rcpt_index;
size_t rcpt_count;
int bccing;
mu_message_t msg; /* Destroy this if not same argmsg. */
off_t offset;
/* The mu_mailer_send_message() args. */
mu_message_t argmsg;
mu_address_t argfrom;
mu_address_t argto;
};
typedef struct _smtp *smtp_t;
/* ESMTP capabilities */
#define CAPA_STARTTLS 0x00000001
#define CAPA_8BITMIME 0x00000002
static void smtp_destroy (mu_mailer_t);
static int smtp_open (mu_mailer_t, int);
static int smtp_close (mu_mailer_t);
static int smtp_send_message (mu_mailer_t, mu_message_t, mu_address_t, mu_address_t);
static int smtp_writeline (smtp_t smtp, const char *format, ...);
static int smtp_readline (smtp_t);
static int smtp_read_ack (smtp_t);
static int smtp_parse_ehlo_ack (smtp_t);
static int smtp_write (smtp_t);
static int smtp_starttls (smtp_t);
static int _smtp_set_rcpt (smtp_t, mu_message_t, mu_address_t);
/* Useful little macros, since these are very repetitive. */
static void
CLEAR_STATE (smtp_t smtp)
{
smtp->ptr = NULL;
smtp->nl = NULL;
smtp->s_offset = 0;
smtp->state = SMTP_NO_STATE;
smtp->extended = 0;
if (smtp->mail_from)
smtp->mail_from = NULL;
if (smtp->rcpt_to != smtp->argto)
mu_address_destroy (&smtp->rcpt_to);
smtp->rcpt_to = NULL;
mu_address_destroy (&smtp->rcpt_bcc);
smtp->rcpt_to_count = 0;
smtp->rcpt_bcc_count = 0;
smtp->rcpt_index = 0;
smtp->rcpt_count = 0;
smtp->bccing = 0;
if (smtp->msg != smtp->argmsg)
mu_message_destroy (&smtp->msg, NULL);
smtp->msg = NULL;
smtp->offset = 0;
smtp->argmsg = NULL;
smtp->argfrom = NULL;
smtp->argto = NULL;
}
/* If we are resuming, we should be resuming the SAME operation
as that which is ongoing. Check this. */
static int
smtp_check_send_resumption (smtp_t smtp,
mu_message_t msg, mu_address_t from, mu_address_t to)
{
if (smtp->state == SMTP_NO_STATE)
return 0;
/* FIXME: state should be one of the "send" states if its not
"no state" */
if (msg != smtp->argmsg)
return MU_ERR_BAD_RESUMPTION;
if (from != smtp->argfrom)
return MU_ERR_BAD_RESUMPTION;
if (to != smtp->argto)
return MU_ERR_BAD_RESUMPTION;
return 0;
}
#define CHECK_SEND_RESUME(smtp, msg, from, to) \
do { \
if((status = smtp_check_send_resumption(smtp, msg, from, to)) != 0) \
return status; \
} while (0)
/* Clear the state and close the stream. */
#define CHECK_ERROR_CLOSE(mailer, smtp, status) \
do \
{ \
if (status != 0) \
{ \
mu_stream_close (mailer->stream); \
CLEAR_STATE (smtp); \
return status; \
} \
} \
while (0)
/* Clear the state. */
#define CHECK_ERROR(smtp, status) \
do \
{ \
if (status != 0) \
{ \
CLEAR_STATE (smtp); \
return status; \
} \
} \
while (0)
/* Clear the state for non recoverable error. */
#define CHECK_EAGAIN(smtp, status) \
do \
{ \
if (status != 0) \
{ \
if (status != EAGAIN && status != EINPROGRESS && status != EINTR) \
{ \
CLEAR_STATE (smtp); \
} \
return status; \
} \
} \
while (0)
int
_mailer_smtp_init (mu_mailer_t mailer)
{
smtp_t smtp;
/* Allocate memory specific to smtp mailer. */
smtp = mailer->data = calloc (1, sizeof (*smtp));
if (mailer->data == NULL)
return ENOMEM;
smtp->mailer = mailer; /* Back pointer. */
smtp->state = SMTP_NO_STATE;
mailer->_destroy = smtp_destroy;
mailer->_open = smtp_open;
mailer->_close = smtp_close;
mailer->_send_message = smtp_send_message;
/* Set our properties. */
{
mu_property_t property = NULL;
mu_mailer_get_property (mailer, &property);
mu_property_set_value (property, "TYPE", "SMTP", 1);
}
return 0;
}
static void
smtp_destroy (mu_mailer_t mailer)
{
smtp_t smtp = mailer->data;
CLEAR_STATE (smtp);
/* Not our responsability to close. */
if (smtp->mailhost)
free (smtp->mailhost);
if (smtp->localhost)
free (smtp->localhost);
if (smtp->buffer)
free (smtp->buffer);
free (smtp);
mailer->data = NULL;
}
/** Open an SMTP mailer.
An SMTP mailer must be opened before any messages can be sent.
@param mailer the mailer created by smtp_create()
@param flags the mailer flags
*/
static int
smtp_open (mu_mailer_t mailer, int flags)
{
smtp_t smtp = mailer->data;
int status;
long port;
/* Sanity checks. */
if (!smtp)
return EINVAL;
mailer->flags = flags;
if ((status = mu_url_get_port (mailer->url, &port)) != 0)
return status;
switch (smtp->state)
{
case SMTP_NO_STATE:
if (smtp->mailhost)
{
free (smtp->mailhost);
smtp->mailhost = NULL;
}
/* Fetch the mailer server name and the port in the mu_url_t. */
if ((status = mu_url_aget_host (mailer->url, &smtp->mailhost)) != 0)
return status;
if (smtp->localhost)
{
free (smtp->localhost);
smtp->localhost = NULL;
}
/* Fetch our local host name. */
status = mu_get_host_name (&smtp->localhost);
if (status != 0)
{
/* gethostname failed, abort. */
free (smtp->mailhost);
smtp->mailhost = NULL;
return status;
}
/* allocate a working io buffer. */
if (smtp->buffer == NULL)
{
smtp->buflen = 512; /* Initial guess. */
smtp->buffer = malloc (smtp->buflen + 1);
if (smtp->buffer == NULL)
{
CHECK_ERROR (smtp, ENOMEM);
}
smtp->ptr = smtp->buffer;
}
/* Create a TCP stack if one is not given. */
if (mailer->stream == NULL)
{
status =
mu_tcp_stream_create (&mailer->stream, smtp->mailhost, port,
mailer->flags);
CHECK_ERROR (smtp, status);
mu_stream_setbufsiz (mailer->stream, BUFSIZ);
}
CHECK_ERROR (smtp, status);
smtp->state = SMTP_OPEN;
case SMTP_OPEN:
MAILER_DEBUG2 (mailer, MU_DEBUG_PROT, "smtp_open (host: %s port: %ld)\n",
smtp->mailhost, port);
status = mu_stream_open (mailer->stream);
CHECK_EAGAIN (smtp, status);
smtp->state = SMTP_GREETINGS;
case SMTP_GREETINGS:
/* Swallow the greetings. */
status = smtp_read_ack (smtp);
CHECK_EAGAIN (smtp, status);
if (smtp->buffer[0] != '2')
{
mu_stream_close (mailer->stream);
return EACCES;
}
status = smtp_writeline (smtp, "EHLO %s\r\n", smtp->localhost);
CHECK_ERROR (smtp, status);
smtp->state = SMTP_EHLO;
case SMTP_EHLO:
/* We first try Extended SMTP. */
status = smtp_write (smtp);
CHECK_EAGAIN (smtp, status);
smtp->state = SMTP_EHLO_ACK;
case SMTP_EHLO_ACK:
status = smtp_parse_ehlo_ack (smtp);
CHECK_EAGAIN (smtp, status);
if (smtp->buffer[0] != '2')
{
smtp->extended = 0;
status = smtp_writeline (smtp, "HELO %s\r\n", smtp->localhost);
CHECK_ERROR (smtp, status);
smtp->state = SMTP_HELO;
}
else
{
smtp->extended = 1;
if (smtp->capa & CAPA_STARTTLS)
smtp->state = SMTP_STARTTLS;
else
break;
}
case SMTP_STARTTLS:
case SMTP_STARTTLS_ACK:
smtp_starttls (smtp);
break;
case SMTP_HELO:
if (!smtp->extended) /* FIXME: this will always be false! */
{
status = smtp_write (smtp);
CHECK_EAGAIN (smtp, status);
}
smtp->state = SMTP_HELO_ACK;
case SMTP_HELO_ACK:
if (!smtp->extended)
{
status = smtp_read_ack (smtp);
CHECK_EAGAIN (smtp, status);
if (smtp->buffer[0] != '2')
{
mu_stream_close (mailer->stream);
CLEAR_STATE (smtp);
return EACCES;
}
}
default:
break;
}
CLEAR_STATE (smtp);
return 0;
}
static int
smtp_close (mu_mailer_t mailer)
{
smtp_t smtp = mailer->data;
int status;
switch (smtp->state)
{
case SMTP_NO_STATE:
status = smtp_writeline (smtp, "QUIT\r\n");
CHECK_ERROR (smtp, status);
smtp->state = SMTP_QUIT;
case SMTP_QUIT:
status = smtp_write (smtp);
CHECK_EAGAIN (smtp, status);
smtp->state = SMTP_QUIT_ACK;
case SMTP_QUIT_ACK:
status = smtp_read_ack (smtp);
CHECK_EAGAIN (smtp, status);
default:
break;
}
return mu_stream_close (mailer->stream);
}
/*
Client side STARTTLS support.
*/
static int
smtp_reader (void *iodata)
{
int status = 0;
smtp_t iop = iodata;
status = smtp_read_ack (iop);
CHECK_EAGAIN (iop, status);
return status;
}
static int
smtp_writer (void *iodata, char *buf)
{
smtp_t iop = iodata;
int status;
if (strncasecmp (buf, "EHLO", 4) == 0)
status = smtp_writeline (iop, "%s %s\r\n", buf, iop->localhost);
else
status = smtp_writeline (iop, "%s\r\n", buf);
CHECK_ERROR (iop, status);
status = smtp_write (iop);
CHECK_EAGAIN (iop, status);
return status;
}
static void
smtp_stream_ctl (void *iodata, mu_stream_t *pold, mu_stream_t new)
{
smtp_t iop = iodata;
if (pold)
*pold = iop->mailer->stream;
if (new)
iop->mailer->stream = new;
}
static int
smtp_starttls (smtp_t smtp)
{
#ifdef WITH_TLS
int status;
mu_mailer_t mailer = smtp->mailer;
char *keywords[] = { "STARTTLS", "EHLO", NULL };
if (!mu_tls_enable || !(smtp->capa & CAPA_STARTTLS))
return -1;
smtp->capa = 0;
status = mu_tls_begin (smtp, smtp_reader, smtp_writer,
smtp_stream_ctl, keywords);
MAILER_DEBUG1 (mailer, MU_DEBUG_PROT, "TLS negotiation %s\n",
status == 0 ? "succeeded" : "failed");
return status;
#else
return -1;
#endif /* WITH_TLS */
}
static int
message_set_header_value (mu_message_t msg, const char *field, const char *value)
{
int status = 0;
mu_header_t hdr = NULL;
if ((status = mu_message_get_header (msg, &hdr)))
return status;
if ((status = mu_header_set_value (hdr, field, value, 1)))
return status;
return status;
}
static int
message_has_bcc (mu_message_t msg)
{
int status;
mu_header_t header = NULL;
size_t bccsz = 0;
if ((status = mu_message_get_header (msg, &header)))
return status;
status = mu_header_get_value (header, MU_HEADER_BCC, NULL, 0, &bccsz);
/* MU_ERR_NOENT, or there was a Bcc: field. */
return status == MU_ERR_NOENT ? 0 : 1;
}
/*
The smtp mailer doesn't deal with mail like:
To: public@com, pub2@com
Bcc: hidden@there, two@there
It just sends the message to all the addresses, making the
"blind" cc not particularly blind.
The correct algorithm is
- open smtp connection
- look as msg, figure out addrto&cc, and addrbcc
- deliver to the to & cc addresses:
- if there are bcc addrs, remove the bcc field
- send the message to to & cc addrs:
mail from: me
rcpt to: public@com
rcpt to: pub2@com
data
...
- deliver to the bcc addrs:
for a in (bccaddrs)
do
- add header field to msg, bcc: $a
- send the msg:
mail from: me
rcpt to: $a
data
...
done
- quit smtp connection
*/
static int
smtp_send_message (mu_mailer_t mailer, mu_message_t argmsg, mu_address_t argfrom,
mu_address_t argto)
{
smtp_t smtp = NULL;
int status;
if (mailer == NULL)
return EINVAL;
smtp = mailer->data;
if (!smtp)
return EINVAL;
CHECK_SEND_RESUME (smtp, argmsg, argfrom, argto);
switch (smtp->state)
{
case SMTP_NO_STATE:
if (argmsg == NULL || argfrom == NULL)
return EINVAL;
smtp->argmsg = smtp->msg = argmsg;
smtp->argfrom = argfrom;
smtp->argto = argto;
status = mu_address_sget_email (smtp->argfrom, 1, &smtp->mail_from);
CHECK_ERROR (smtp, status);
status = _smtp_set_rcpt (smtp, smtp->argmsg, smtp->argto);
CHECK_ERROR (smtp, status);
/* Clear the Bcc: field if we found one. */
if (message_has_bcc (smtp->argmsg))
{
smtp->msg = NULL;
status = mu_message_create_copy (&smtp->msg, smtp->argmsg);
CHECK_ERROR (smtp, status);
status = message_set_header_value (smtp->msg, MU_HEADER_BCC, NULL);
CHECK_ERROR (smtp, status);
}
/* Begin bccing if there are not To: recipients. */
if (smtp->rcpt_to_count == 0)
smtp->bccing = 1;
smtp->rcpt_index = 1;
smtp->state = SMTP_ENV_FROM;
case SMTP_ENV_FROM:
ENV_FROM:
{
status = smtp_writeline (smtp, "MAIL FROM: <%s>\r\n", smtp->mail_from);
CHECK_ERROR (smtp, status);
smtp->state = SMTP_MAIL_FROM;
}
/* We use a goto, since we may have multiple messages,
we come back here and doit all over again ... Not pretty. */
case SMTP_MAIL_FROM:
status = smtp_write (smtp);
CHECK_EAGAIN (smtp, status);
smtp->state = SMTP_MAIL_FROM_ACK;
case SMTP_MAIL_FROM_ACK:
status = smtp_read_ack (smtp);
CHECK_EAGAIN (smtp, status);
if (smtp->buffer[0] != '2')
{
mu_stream_close (mailer->stream);
CLEAR_STATE (smtp);
return EACCES;
}
/* We use a goto, since we may have multiple recipients,
we come back here and do it all over again ... Not pretty. */
case SMTP_ENV_RCPT:
ENV_RCPT:
{
mu_address_t addr = smtp->rcpt_to;
const char *to = NULL;
if (smtp->bccing)
addr = smtp->rcpt_bcc;
status = mu_address_sget_email (addr, smtp->rcpt_index, &to);
CHECK_ERROR (smtp, status);
/* Add the Bcc: field back in for recipient. */
if (smtp->bccing)
{
status = message_set_header_value (smtp->msg, MU_HEADER_BCC, to);
CHECK_ERROR (smtp, status);
}
status = smtp_writeline (smtp, "RCPT TO: <%s>\r\n", to);
CHECK_ERROR (smtp, status);
smtp->state = SMTP_RCPT_TO;
smtp->rcpt_index++;
}
case SMTP_RCPT_TO:
status = smtp_write (smtp);
CHECK_EAGAIN (smtp, status);
smtp->state = SMTP_RCPT_TO_ACK;
case SMTP_RCPT_TO_ACK:
status = smtp_read_ack (smtp);
CHECK_EAGAIN (smtp, status);
if (smtp->buffer[0] != '2')
{
mu_stream_close (mailer->stream);
CLEAR_STATE (smtp);
return MU_ERR_SMTP_RCPT_FAILED;
}
/* Redo the receipt sequence for every To: and Cc: recipient. */
if (!smtp->bccing && smtp->rcpt_index <= smtp->rcpt_to_count)
goto ENV_RCPT;
/* We are done with the rcpt. */
status = smtp_writeline (smtp, "DATA\r\n");
CHECK_ERROR (smtp, status);
smtp->state = SMTP_DATA;
case SMTP_DATA:
status = smtp_write (smtp);
CHECK_EAGAIN (smtp, status);
smtp->state = SMTP_DATA_ACK;
case SMTP_DATA_ACK:
status = smtp_read_ack (smtp);
CHECK_EAGAIN (smtp, status);
if (smtp->buffer[0] != '3')
{
mu_stream_close (mailer->stream);
CLEAR_STATE (smtp);
return EACCES;
}
smtp->offset = 0;
smtp->state = SMTP_SEND;
if ((smtp->mailer->flags & MAILER_FLAG_DEBUG_DATA) == 0)
MAILER_DEBUG0 (smtp->mailer, MU_DEBUG_PROT, "> (data...)\n");
case SMTP_SEND:
{
mu_stream_t stream;
size_t n = 0;
char data[256] = "";
mu_header_t hdr;
mu_body_t body;
int found_nl;
/* We may be here after an EAGAIN so check if we have something
in the buffer and flush it. */
status = smtp_write (smtp);
CHECK_EAGAIN (smtp, status);
mu_message_get_header (smtp->msg, &hdr);
mu_header_get_stream (hdr, &stream);
while ((status = mu_stream_readline (stream, data, sizeof (data),
smtp->offset, &n)) == 0 && n > 0)
{
int nl;
found_nl = (n == 1 && data[0] == '\n');
if ((nl = (data[n - 1] == '\n')))
data[n - 1] = '\0';
if (data[0] == '.')
{
status = smtp_writeline (smtp, ".%s", data);
CHECK_ERROR (smtp, status);
}
else if (strncasecmp (data, MU_HEADER_FCC,
sizeof (MU_HEADER_FCC) - 1))
{
status = smtp_writeline (smtp, "%s", data);
CHECK_ERROR (smtp, status);
status = smtp_write (smtp);
CHECK_EAGAIN (smtp, status);
}
else
nl = 0;
if (nl)
{
status = smtp_writeline (smtp, "\r\n");
CHECK_ERROR (smtp, status);
status = smtp_write (smtp);
CHECK_EAGAIN (smtp, status);
}
smtp->offset += n;
}
if (!found_nl)
{
status = smtp_writeline (smtp, "\r\n");
CHECK_ERROR (smtp, status);
status = smtp_write (smtp);
CHECK_EAGAIN (smtp, status);
}
mu_message_get_body (smtp->msg, &body);
mu_body_get_stream (body, &stream);
smtp->offset = 0;
while ((status = mu_stream_readline (stream, data, sizeof (data) - 1,
smtp->offset, &n)) == 0 && n > 0)
{
if (data[n - 1] == '\n')
data[n - 1] = '\0';
if (data[0] == '.')
status = smtp_writeline (smtp, ".%s\r\n", data);
else
status = smtp_writeline (smtp, "%s\r\n", data);
CHECK_ERROR (smtp, status);
status = smtp_write (smtp);
CHECK_EAGAIN (smtp, status);
smtp->offset += n;
}
smtp->offset = 0;
status = smtp_writeline (smtp, ".\r\n");
CHECK_ERROR (smtp, status);
smtp->state = SMTP_SEND_DOT;
}
case SMTP_SEND_DOT:
status = smtp_write (smtp);
CHECK_EAGAIN (smtp, status);
smtp->state = SMTP_SEND_ACK;
case SMTP_SEND_ACK:
status = smtp_read_ack (smtp);
CHECK_EAGAIN (smtp, status);
if (smtp->buffer[0] != '2')
{
mu_stream_close (mailer->stream);
CLEAR_STATE (smtp);
return EACCES;
}
/* Decide whether we need to loop again, to deliver to Bcc:
recipients. */
if (!smtp->bccing)
{
smtp->bccing = 1;
smtp->rcpt_index = 1;
}
if (smtp->rcpt_index <= smtp->rcpt_bcc_count)
goto ENV_FROM;
mu_observable_notify (mailer->observable, MU_EVT_MAILER_MESSAGE_SENT);
default:
break;
}
CLEAR_STATE (smtp);
return 0;
}
int
smtp_address_add (mu_address_t *paddr, const char *value)
{
mu_address_t addr = NULL;
int status;
status = mu_address_create (&addr, value);
if (status)
return status;
status = mu_address_union (paddr, addr);
mu_address_destroy (&addr);
return status;
}
static int
_smtp_property_is_set (smtp_t smtp, const char *name)
{
mu_property_t property = NULL;
mu_mailer_get_property (smtp->mailer, &property);
return mu_property_is_set (property, name);
}
static int
_smtp_set_rcpt (smtp_t smtp, mu_message_t msg, mu_address_t to)
{
int status = 0;
mu_header_t header = NULL;
char *value;
/* Get RCPT_TO from TO, or the message. */
if (to)
{
/* Use the specified mu_address_t. */
if ((status = mu_mailer_check_to (to)) != 0)
{
MAILER_DEBUG0 (smtp->mailer, MU_DEBUG_ERROR,
"mu_mailer_send_message(): explicit to not valid\n");
return status;
}
smtp->rcpt_to = to;
mu_address_get_count (smtp->rcpt_to, &smtp->rcpt_to_count);
if (status)
return status;
}
if (!to || _smtp_property_is_set (smtp, "READ_RECIPIENTS"))
{
if ((status = mu_message_get_header (msg, &header)))
return status;
status = mu_header_aget_value (header, MU_HEADER_TO, &value);
if (status == 0)
{
smtp_address_add (&smtp->rcpt_to, value);
free (value);
}
else if (status != MU_ERR_NOENT)
goto end;
status = mu_header_aget_value (header, MU_HEADER_CC, &value);
if (status == 0)
{
smtp_address_add (&smtp->rcpt_to, value);
free (value);
}
else if (status != MU_ERR_NOENT)
goto end;
status = mu_header_aget_value (header, MU_HEADER_BCC, &value);
if (status == 0)
{
smtp_address_add (&smtp->rcpt_bcc, value);
free (value);
}
else if (status != MU_ERR_NOENT)
goto end;
/* If to or bcc is present, the must be OK. */
if (smtp->rcpt_to && (status = mu_mailer_check_to (smtp->rcpt_to)))
goto end;
if (smtp->rcpt_bcc && (status = mu_mailer_check_to (smtp->rcpt_bcc)))
goto end;
}
end:
if (status)
{
mu_address_destroy (&smtp->rcpt_to);
mu_address_destroy (&smtp->rcpt_bcc);
}
else
{
if (smtp->rcpt_to)
mu_address_get_count (smtp->rcpt_to, &smtp->rcpt_to_count);
if (smtp->rcpt_bcc)
mu_address_get_count (smtp->rcpt_bcc, &smtp->rcpt_bcc_count);
if (smtp->rcpt_to_count + smtp->rcpt_bcc_count == 0)
status = MU_ERR_MAILER_NO_RCPT_TO;
}
return status;
}
/* C99 says that a conforming implementations of snprintf ()
should return the number of char that would have been call
but many GNU/Linux && BSD implementations return -1 on error.
Worse QNX/Neutrino actually does not put the terminal
null char. So let's try to cope. */
static int
smtp_writeline (smtp_t smtp, const char *format, ...)
{
int len;
va_list ap;
int done = 1;
va_start (ap, format);
do
{
len = vsnprintf (smtp->buffer, smtp->buflen - 1, format, ap);
if (len < 0 || (len >= (int) smtp->buflen)
|| !memchr (smtp->buffer, '\0', len + 1))
{
char *buffer = NULL;
size_t buflen = smtp->buflen * 2;
buffer = realloc (smtp->buffer, buflen);
if (smtp->buffer == NULL)
return ENOMEM;
smtp->buffer = buffer;
smtp->buflen = buflen;
done = 0;
}
else
done = 1;
}
while (!done);
va_end (ap);
smtp->ptr = smtp->buffer + len;
while (len > 0 && isspace (smtp->buffer[len - 1]))
len--;
if ((smtp->state != SMTP_SEND && smtp->state != SMTP_SEND_DOT)
|| smtp->mailer->flags & MAILER_FLAG_DEBUG_DATA)
{
MAILER_DEBUG2 (smtp->mailer, MU_DEBUG_PROT, "> %.*s\n", len,
smtp->buffer);
}
return 0;
}
static int
smtp_write (smtp_t smtp)
{
int status = 0;
size_t len;
if (smtp->ptr > smtp->buffer)
{
len = smtp->ptr - smtp->buffer;
status = mu_stream_write (smtp->mailer->stream, smtp->buffer, len,
0, &len);
if (status == 0)
{
memmove (smtp->buffer, smtp->buffer + len, len);
smtp->ptr -= len;
}
}
else
{
smtp->ptr = smtp->buffer;
len = 0;
}
return status;
}
static int
smtp_read_ack (smtp_t smtp)
{
int status;
int multi;
do
{
multi = 0;
status = smtp_readline (smtp);
if ((smtp->ptr - smtp->buffer) > 4 && smtp->buffer[3] == '-')
multi = 1;
if (status == 0)
smtp->ptr = smtp->buffer;
}
while (multi && status == 0);
if (status == 0)
smtp->ptr = smtp->buffer;
return status;
}
static int
smtp_parse_ehlo_ack (smtp_t smtp)
{
int status;
int multi;
do
{
multi = 0;
status = smtp_readline (smtp);
if ((smtp->ptr - smtp->buffer) > 4 && smtp->buffer[3] == '-')
multi = 1;
if (status == 0) {
smtp->ptr = smtp->buffer;
if (!strncasecmp (smtp->buffer, "250-STARTTLS", 12))
smtp->capa |= CAPA_STARTTLS;
}
}
while (multi && status == 0);
if (status == 0)
smtp->ptr = smtp->buffer;
return status;
}
/* Read a complete line form the pop server. Transform CRLF to LF,
put a null in the buffer when done. */
static int
smtp_readline (smtp_t smtp)
{
size_t n = 0;
size_t total = smtp->ptr - smtp->buffer;
int status;
/* Must get a full line before bailing out. */
do
{
status = mu_stream_readline (smtp->mailer->stream, smtp->buffer + total,
smtp->buflen - total, smtp->s_offset, &n);
if (status != 0)
return status;
/* Server went away, consider this like an error. */
if (n == 0)
return EIO;
total += n;
smtp->s_offset += n;
smtp->nl = memchr (smtp->buffer, '\n', total);
if (smtp->nl == NULL) /* Do we have a full line. */
{
/* Allocate a bigger buffer ? */
if (total >= smtp->buflen - 1)
{
smtp->buflen *= 2;
smtp->buffer = realloc (smtp->buffer, smtp->buflen + 1);
if (smtp->buffer == NULL)
return ENOMEM;
}
}
smtp->ptr = smtp->buffer + total;
}
while (smtp->nl == NULL);
/* \r\n --> \n\0 */
if (smtp->nl > smtp->buffer)
{
*(smtp->nl - 1) = '\n';
*(smtp->nl) = '\0';
smtp->ptr = smtp->nl;
}
MAILER_DEBUG1 (smtp->mailer, MU_DEBUG_PROT, "< %s", smtp->buffer);
return 0;
}
#else
#include <stdio.h>
#include <registrar0.h>
mu_record_t mu_smtp_record = NULL;
#endif