/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2004, 2005, 2006, 2007, 2009, 2010
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
*/
/* FIXME: Bufferization stuff is spurious. Remove it */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef ENABLE_SMTP
#include <errno.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/mime.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 <mailutils/md5.h>
#include <mailutils/io.h>
#include <mailutils/secret.h>
#include <mailutils/cctype.h>
#include <mailutils/cstr.h>
#include <mailutils/sys/mailer.h>
#include <mailutils/sys/url.h>
#include <mailutils/sys/registrar.h>
static int _mailer_smtp_init (mu_mailer_t);
static int
_url_smtp_init (mu_url_t url)
{
/* host isn't optional */
if (!url->host)
return EINVAL;
/* accept url->user, pass, and auth
for the ESMTP authentication */
/* all other fields must be NULL */
if (url->path || url->qargc)
return EINVAL;
if (url->port == 0)
url->port = MU_SMTP_PORT;
return 0;
}
static struct _mu_record _smtp_record = {
MU_SMTP_PRIO,
MU_SMTP_SCHEME,
_url_smtp_init, /* url init. */
_mu_mailer_mailbox_init, /* Mailbox init. */
_mailer_smtp_init, /* Mailer init. */
_mu_mailer_folder_init, /* 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;
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, SMTP_AUTH, SMTP_AUTH_ACK,
}
state;
int extended;
unsigned long capa; /* Server capabilities */
size_t max_size; /* Maximum message size the server is willing
to accept */
unsigned long auth_mechs; /* Available ESMTP AUTH mechanisms */
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. */
/* 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
#define CAPA_SIZE 0x00000004
#define CAPA_AUTH 0x00000008
/* ESMTP AUTH mechanisms */
#define AUTH_LOGIN 0x00000001
#define AUTH_PLAIN 0x00000002
#define AUTH_CRAM_MD5 0x00000004
#define AUTH_DIGEST_MD5 0x00000008
#define AUTH_GSSAPI 0x00000010
#define AUTH_EXTERNAL 0x00000020
struct auth_mech_record
{
unsigned long id;
char *name;
};
static struct auth_mech_record auth_mech_list[] = {
{AUTH_LOGIN, "login"},
{AUTH_PLAIN, "plain"},
{AUTH_CRAM_MD5, "cram-md5"},
{AUTH_DIGEST_MD5, "digest-md5"},
{AUTH_GSSAPI, "gssapi"},
{AUTH_EXTERNAL, "external"},
{0, NULL},
};
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_auth (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 = smtp->buffer;
smtp->nl = NULL;
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->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)
static 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 po
|