/*
 * smtp.c
 *
 * This file is part of msmtp, an SMTP client.
 *
 * Copyright (C) 2000, 2003, 2004, 2005, 2006, 2007
 * Martin Lambers <marlam@marlam.de>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program 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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

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

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <stdarg.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <errno.h>

#ifdef HAVE_LIBGSASL
# include <gsasl.h>
#else
# include "base64.h"
# include "hmac.h"
#endif

#include "gettext.h"
#include "xalloc.h"
#include "xvasprintf.h"

#include "list.h"
#include "net.h"
#include "smtp.h"
#include "stream.h"
#ifdef HAVE_TLS
#include "tls.h"
#endif /* HAVE_TLS */


/* This defines the maximum number of lines in a multiline server reply.
 * This limit exists to prevent an extremely long reply from eating
 * all the memory.
 * The longest server reply we have to expect is the repsonse to the EHLO
 * command. We should have enough lines for every SMTP extension known today
 * plus more lines for future extensions.
 */
#define SMTP_MAXLINES 50

/* This defines the length of the *input* buffer for SMTP messages.
 * According to RFC 2821, SMTP commands and SMTP messages may contain
 * at most 512 characters including CRLF, thus the *minimum* size is
 * 512 characters.
 */
#define SMTP_BUFSIZE 1024

/* This defines the length of the *output* buffer for SMTP commands, without
 * CRLF. According to RFC 2821, SMTP commands may contain at most 512
 * characters including CRLF, thus the maximum size should be 510 characters.
 * But this is not sufficient for some authentication mechanisms, notably
 * GSSAPI, so this value must be higher.
 */
#define SMTP_MAXCMDLEN 1022

/* The maximum length of the SMTP command pipeline (the maximum number of
 * commands that are sent at once, before starting to read the replies). This
 * number should be large enough to keep the benefits of pipelining (saved round
 * trips), and small enough to avoid problems (exceeding the TCP window size).
 * A value of 1 disables pipelining.
 */
#define SMTP_PIPELINE_LIMIT 100

/* This is the buffer length for copying the mail to the SMTP server.
 * According to RFC 2822, a line in a mail can contain at most 998 
 * characters + CRLF. Plus one character for '\0' = 1001 characters.
 * All lines should fit in a buffer of this size.
 * However, this length is not a limit; smtp_send_mail() will accept 
 * arbitrary long lines.
 */
#define MAIL_BUFSIZE 1024


/*
 * smtp_new()
 *
 * see smtp.h
 */

smtp_server_t smtp_new(FILE *debug, int protocol)
{
    smtp_server_t srv;

    srv.fd = -1;
    net_readbuf_init(&(srv.readbuf));
#ifdef HAVE_TLS
    tls_clear(&srv.tls);
#endif /* HAVE_TLS */
    srv.protocol = protocol;
    srv.cap.flags = 0;
    srv.cap.size = 0;
    srv.debug = debug;
    return srv;
}


/*
 * smtp_connect()
 *
 * see smtp.h
 */

int smtp_connect(smtp_server_t *srv, const char *host, int port, int timeout,
	char **server_canonical_name, char **server_address, 
	char **errstr)
{
    return net_open_socket(host, port, timeout, &srv->fd, 
	    server_canonical_name, server_address, errstr);
}


/*
 * smtp_get_msg()
 * 
 * This function gets a message from the SMTP server 'srv'.
 * In case of success, 'msg' will contain a pointer to a newly created list, 
 * and each member of this list will contain one line of the message as an 
 * allocated string. The list will contain at least one line. Each line will 
 * be at least 4 characters long: The three digit status code plus a ' ' or '-'.
 * Each line will be at most SMTP_BUFSIZE characters long, including '\0'.
 * The return code will be EOK.
 * In case of failure, 'msg' will be NULL, and one of the following error codes
 * will be returned: SMTP_EIO, SMTP_EPROTO
 */

int smtp_get_msg(smtp_server_t *srv, list_t **msg, char **errstr)
{
    list_t *l;
    list_t *lp;
    char line[SMTP_BUFSIZE];
    int counter;
    size_t len;

    *msg = NULL;
    l = list_new();
    lp = l;

    counter = 0;
    do
    {
#ifdef HAVE_TLS
	if (tls_is_active(&srv->tls))
	{
	    if (tls_gets(&srv->tls, line, SMTP_BUFSIZE, &len, errstr) 
		    != TLS_EOK)
	    {
		list_xfree(l, free);
		return SMTP_EIO;
	    }
	}
	else
	{
#endif /* HAVE_TLS */
	    if (net_gets(srv->fd, &(srv->readbuf), line, SMTP_BUFSIZE, &len, 
			errstr) != NET_EOK)
	    {
		list_xfree(l, free);
		return SMTP_EIO;
	    }
#ifdef HAVE_TLS
	}
#endif /* HAVE_TLS */
	if (len < 4 
		|| !(isdigit((unsigned char)line[0]) 
		    && isdigit((unsigned char)line[1]) 
		    && isdigit((unsigned char)line[2]) 
		    && (line[3] == ' ' || line[3] == '-'))
		|| line[len - 1] != '\n')
	{
	    list_xfree(l, free);
	    /* The string is not necessarily a reply (it may be the initial OK
	     * message), but this is the term used in the RFCs.
	     * An empty reply is a special case of an invalid reply - this
	     * differentiation may help the user. */
	    if (counter == 0 && len == 0)
	    {
		*errstr = xasprintf(_("the server sent an empty reply"));
	    }
	    else
	    {
		*errstr = xasprintf(_("the server sent an invalid reply"));
	    }
	    return SMTP_EPROTO;
	}
	if (srv->debug)
	{
	    fputs("<-- ", srv->debug);
	    fwrite(line, sizeof(char), len, srv->debug);
	}
	/* kill CRLF */
	line[--len] = '\0';
	if (line[len - 1] == '\r')
	{
	    line[--len] = '\0';
	}
	list_insert(lp, xstrdup(line));
	counter++;
	lp = lp->next;
    }
    while (line[3] == '-' && counter <= SMTP_MAXLINES);

    if (counter > SMTP_MAXLINES)
    {
	list_xfree(l, free);
	*errstr = xasprintf(_("Rejecting server reply that is longer "
		    "than %d lines. Increase SMTP_MAXLINES."), SMTP_MAXLINES);
	return SMTP_EPROTO;
    }

    *msg = l;
    return SMTP_EOK;
}


/*
 * smtp_msg_status()
 *
 * see smtp.h
 */

int smtp_msg_status(list_t *msg)
{
    /* we know that *msg is valid; there's no need to check for errors */
    return atoi(msg->next->data);
}


/*
 * smtp_put()
 *
 * This function writes 'len' characters from 's' to the SMTP server 'srv'.
 * Used error codes: SMTP_EIO
 */

int smtp_put(smtp_server_t *srv, const char *s, size_t len, char **errstr)
{
    int e = 0;

#ifdef HAVE_TLS
    if (tls_is_active(&srv->tls))
    {
	e = (tls_puts(&srv->tls, s, len, errstr) != TLS_EOK);
    }
    else
    {
#endif /* HAVE_TLS */
	e = (net_puts(srv->fd, s, len, errstr) != NET_EOK);
#ifdef HAVE_TLS
    }
#endif /* HAVE_TLS */
    if (e)
    {
	return SMTP_EIO;
    }
    if (srv->debug)
    {
	fputs("--> ", srv->debug);
	fwrite(s, sizeof(char), len, srv->debug);
    }
    
    return SMTP_EOK;
}


/*
 * smtp_send_cmd()
 *
 * This function writes a string to the SMTP server 'srv'. The string may not 
 * be longer than SMTP_MAXCMDLEN characters (see above). TCP CRLF ('\r\n') will
 * be appended to the string. Use this function to send SMTP commands (not mail
 * data) to the SMTP server.
 * Used error codes: SMTP_EIO, SMTP_EINVAL
 */

/* make gcc print format warnings for this function */
#ifdef __GNUC__
int smtp_send_cmd(smtp_server_t *srv, char **errstr, const char *format, ...) 
    __attribute__ ((format (printf, 3, 4)));
#endif

int smtp_send_cmd(smtp_server_t *srv, char **errstr, const char *format, ...)
{
    char line[SMTP_MAXCMDLEN + 3];
    int count;
    va_list args;

    va_start(args, format);
    count = vsnprintf(line, SMTP_MAXCMDLEN + 1, format, args);	
    va_end(args);
    if (count >= SMTP_MAXCMDLEN + 1)
    {
	*errstr = xasprintf(_("Cannot send command because it is "
		"longer than %d characters. Increase SMTP_MAXCMDLEN."), 
		SMTP_MAXCMDLEN);
	return SMTP_EINVAL;
    }
    line[count++] = '\r';
    line[count++] = '\n';
    line[count] = '\0';
    return smtp_put(srv, line, (size_t)count, errstr);
}


/*
 * smtp_get_greeting()
 *
 * see smtp.h
 */

int smtp_get_greeting(smtp_server_t *srv, list_t **errmsg, char **buf, 
	char **errstr)
{
    int e;
    list_t *msg;
    
    *errmsg = NULL;
    if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
    {
	return e;
    }
    if (smtp_msg_status(msg) != 220)
    {
	*errmsg = msg;
	*errstr = xasprintf(_("cannot get initial OK message from server"));
	return SMTP_EPROTO;
    }
    if (buf)
    {
	*buf = xmalloc(
		(strlen((char *)msg->next->data + 4) + 1) * sizeof(char));
	strcpy(*buf, (char *)(msg->next->data) + 4);
    }
    list_xfree(msg, free);
    
    return SMTP_EOK;
}


/*
 * smtp_init()
 *
 * see smtp.h
 */

int smtp_init(smtp_server_t *srv, const char *ehlo_domain, list_t **errmsg, 
	char **errstr)
{
    int e;
    list_t *ehlo_response;
    list_t *lp;
    char *s;
    char *p;
    size_t len;
    int i;
    
    srv->cap.flags = 0;

    *errmsg = NULL;
    if (srv->protocol == SMTP_PROTO_SMTP)
    {
	if ((e = smtp_send_cmd(srv, errstr, "EHLO %s", ehlo_domain)) 
		!= SMTP_EOK)
	{
	    return e;
	}
	if ((e = smtp_get_msg(srv, &ehlo_response, errstr)) != SMTP_EOK)
	{
	    return e;
	}
	if (smtp_msg_status(ehlo_response) != 250)
	{
	    /* fall back to HELO, for very old SMTP servers */
	    list_xfree(ehlo_response, free);
	    if ((e = smtp_send_cmd(srv, errstr, "HELO %s", ehlo_domain)) 
		    != SMTP_EOK)
	    {
		return e;
	    }
	    if ((e = smtp_get_msg(srv, &ehlo_response, errstr)) != SMTP_EOK)
	    {
		return e;
	    }
	    if (smtp_msg_status(ehlo_response) != 250)
	    {
		*errmsg = ehlo_response;
		*errstr = xasprintf(_("SMTP server does not accept "
			    "EHLO or HELO commands"));
		return SMTP_EPROTO;
	    }
	    list_xfree(ehlo_response, free);
	    /* srv->cap.flags is 0 */
	    return SMTP_EOK;
	}
    }
    else /* protocol is LMTP */
    {
	if ((e = smtp_send_cmd(srv, errstr, "LHLO %s", ehlo_domain)) 
		!= SMTP_EOK)
	{
	    return e;
	}
	if ((e = smtp_get_msg(srv, &ehlo_response, errstr)) != SMTP_EOK)
	{
	    return e;
	}
	if (smtp_msg_status(ehlo_response) != 250)
	{
	    *errmsg = ehlo_response;
	    *errstr = xasprintf(_("command %s failed"), "LHLO");
	    return SMTP_EPROTO;
	}
    }

    lp = ehlo_response;
    while (!list_is_empty(lp))
    {
	lp = lp->next;
	s = lp->data;
	len = strlen(s);
	/* we know that len is >= 4 */
	/* make line uppercase */
    	for (i = 4; (size_t)i < len; i++)
	{
	    s[i] = toupper((unsigned char)s[i]);
	}
	/* search capabilities */
	if (strncmp(s + 4, "STARTTLS", 8) == 0)
	{
    	    srv->cap.flags |= SMTP_CAP_STARTTLS;
	}
	else if (strncmp(s + 4, "DSN", 3) == 0)
	{
	    srv->cap.flags |= SMTP_CAP_DSN;
	}
	else if (strncmp(s + 4, "PIPELINING", 10) == 0)
	{
	    srv->cap.flags |= SMTP_CAP_PIPELINING;
	}
	else if (strncmp(s + 4, "SIZE", 4) == 0)
	{
	    /* If there's no number after the SIZE keyword, the server does not
	     * tell us about a maximum message size. Treat that as if the SIZE 
	     * keyword was not seen. Also treat invalid numbers the same way.
	     * The value 0 means there is no maximum.
	     * See RFC 1653. */
	    errno = 0;
	    srv->cap.size = strtol(s + 8, &p, 10);
	    if (!(*(s + 8) == '\0' || *p != '\0' || srv->cap.size < 0
		    || (srv->cap.size == LONG_MAX && errno == ERANGE)))
	    {
		srv->cap.flags |= SMTP_CAP_SIZE;
	    }
	}
	/* Accept "AUTH " as well as "AUTH=". There are still some broken
	 * servers that use "AUTH=". */
     	else if (strncmp(s + 4, "AUTH", 4) == 0 
		&& (*(s + 8) == ' ' || *(s + 8) == '='))
	{
    	    srv->cap.flags |= SMTP_CAP_AUTH;
	    if (strstr(s + 9, "PLAIN"))
	    {
    		srv->cap.flags |= SMTP_CAP_AUTH_PLAIN;
	    }
	    if (strstr(s + 9, "CRAM-MD5"))
	    {
		srv->cap.flags |= SMTP_CAP_AUTH_CRAM_MD5;
	    }
	    if (strstr(s + 9, "DIGEST-MD5"))
	    {
		srv->cap.flags |= SMTP_CAP_AUTH_DIGEST_MD5;
	    }
	    if (strstr(s + 9, "GSSAPI"))
	    {
		srv->cap.flags |= SMTP_CAP_AUTH_GSSAPI;
	    }
	    if (strstr(s + 9, "EXTERNAL"))
	    {
		srv->cap.flags |= SMTP_CAP_AUTH_EXTERNAL;
	    }
	    if (strstr(s + 9, "LOGIN"))
	    {
		srv->cap.flags |= SMTP_CAP_AUTH_LOGIN;
	    }
	    if (strstr(s + 9, "NTLM"))
	    {
		srv->cap.flags |= SMTP_CAP_AUTH_NTLM;
	    }
	}
	else if (strncmp(s + 4, "ETRN", 4) == 0)
	{
	    srv->cap.flags |= SMTP_CAP_ETRN;
	}
    }

    list_xfree(ehlo_response, free);
    return SMTP_EOK;
}


/*
 * smtp_tls_init()
 *
 * see smtp.h
 */

#ifdef HAVE_TLS
int smtp_tls_init(smtp_server_t *srv, const char *tls_key_file, 
	const char *tls_ca_file, const char *tls_trust_file, 
	int force_sslv3, char **errstr)
{
    return tls_init(&srv->tls, tls_key_file, tls_ca_file, tls_trust_file, 
	    force_sslv3, errstr);
}
#endif /* HAVE_TLS */


/*
 * smtp_tls_starttls()
 *
 * see smtp.h
 */

#ifdef HAVE_TLS
int smtp_tls_starttls(smtp_server_t *srv, list_t **error_msg, char **errstr)
{
    int e;
    list_t *msg;

    *error_msg = NULL;
    if ((e = smtp_send_cmd(srv, errstr, "STARTTLS")) != SMTP_EOK)
    {
	return e;
    }
    if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
    {
	return e;
    }
    if (smtp_msg_status(msg) != 220)
    {
	*error_msg = msg;
	*errstr = xasprintf(_("command %s failed"), "STARTTLS");
	return SMTP_EPROTO;
    }
    list_xfree(msg, free);
    return SMTP_EOK;
}
#endif /* HAVE_TLS */


/*
 * smtp_tls()
 *
 * see smtp.h
 */

#ifdef HAVE_TLS
int smtp_tls(smtp_server_t *srv, const char *hostname, int tls_nocertcheck,
	tls_cert_info_t *tci, char **errstr)
{
    return tls_start(&srv->tls, srv->fd, hostname, tls_nocertcheck, tci, 
	    errstr);
}
#endif /* HAVE_TLS */


/*
 * smtp_auth_plain()
 * 
 * Do SMTP authentication via AUTH PLAIN.
 * The SMTP server must support SMTP_CAP_AUTH_PLAIN
 * Used error codes: SMTP_EIO, SMTP_EPROTO, SMTP_EAUTHFAIL, SMTP_EINVAL
 */

#ifndef HAVE_LIBGSASL
int smtp_auth_plain(smtp_server_t *srv, const char *user, const char *password,
	list_t **error_msg, char **errstr)
{
    char *s;
    char *b64;
    size_t u_len;
    size_t p_len;
    size_t b64_len;
    list_t *msg;
    int e;
    int status;

    *error_msg = NULL;
    u_len = strlen(user);
    p_len = strlen(password);
    s = xmalloc((u_len + p_len + 3) * sizeof(char));
    s[0] = '\0';
    strcpy(s + 1, user);
    strcpy(s + u_len + 2, password);
    b64_len = BASE64_LENGTH(u_len + p_len + 2);
    b64 = xmalloc(b64_len + 1);
    base64_encode(s, u_len + p_len + 2, b64, b64_len + 1);
    free(s);
    
    if ((e = smtp_send_cmd(srv, errstr, "AUTH PLAIN %s", b64)) != SMTP_EOK)
    {
	free(b64);
	return e;
    }
    free(b64);
    if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
    {
	return e;
    }
    if ((status = smtp_msg_status(msg)) != 235)
    {
	*error_msg = msg;
	if (status == 504)
	{
	    *errstr = xasprintf(_("command %s failed"), "AUTH PLAIN");
	    return SMTP_EPROTO;
	}
	else
	{
	    *errstr = xasprintf(_("authentication failed (method %s)"), 
		    "PLAIN");
	    return SMTP_EAUTHFAIL;
	}
    }
    list_xfree(msg, free);

    return SMTP_EOK;
}
#endif /* !HAVE_LIBGSASL */


/*
 * smtp_auth_login()
 * 
 * Do SMTP authentication via AUTH LOGIN.
 * The SMTP server must support SMTP_CAP_AUTH_LOGIN
 * Used error codes: SMTP_EIO, SMTP_EPROTO, SMTP_EAUTHFAIL, SMTP_EINVAL
 */

#ifndef HAVE_LIBGSASL
int smtp_auth_login(smtp_server_t *srv, const char *user, const char *password,
	list_t **error_msg, char **errstr)
{
    int e;
    list_t *msg;
    char *b64;
    size_t b64_len;
    size_t u_len;
    size_t p_len;
    
    *error_msg = NULL;
    if ((e = smtp_send_cmd(srv, errstr, "AUTH LOGIN")) != SMTP_EOK)
    {
	return e;
    }
    if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
    {
	return e;
    }
    if (smtp_msg_status(msg) != 334)
    {
	*error_msg = msg;
	*errstr = xasprintf(_("command %s failed"), "AUTH LOGIN");
	return SMTP_EPROTO;
    }
    list_xfree(msg, free);
    u_len = strlen(user);
    b64_len = BASE64_LENGTH(u_len);
    b64 = xmalloc(b64_len + 1);
    base64_encode(user, u_len, b64, b64_len + 1);
    if ((e = smtp_send_cmd(srv, errstr, "%s", b64)) != SMTP_EOK)
    {
	free(b64);
	return e;
    }
    free(b64);
    if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
    {
	return e;
    }
    if (smtp_msg_status(msg) != 334)
    {
	*error_msg = msg;
	*errstr = xasprintf(_("authentication failed (method %s)"), "LOGIN");
	return SMTP_EAUTHFAIL;
    }
    list_xfree(msg, free);
    p_len = strlen(password);
    b64_len = BASE64_LENGTH(p_len);
    b64 = xmalloc(b64_len + 1);
    base64_encode(password, p_len, b64, b64_len + 1);
    if ((e = smtp_send_cmd(srv, errstr, "%s", b64)) != SMTP_EOK)
    {
	free(b64);
	return e;
    }
    free(b64);
    if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
    {
	return e;
    }
    if (smtp_msg_status(msg) != 235)
    {
	*error_msg = msg;
	*errstr = xasprintf(_("authentication failed (method %s)"), "LOGIN");
	return SMTP_EAUTHFAIL;
    }
    list_xfree(msg, free);

    return SMTP_EOK;
}
#endif /* !HAVE_LIBGSASL */


/*
 * smtp_auth_cram_md5()
 *
 * Do SMTP authentication via AUTH CRAM-MD5.
 * The SMTP server must support SMTP_CAP_AUTH_CRAM_MD5
 * Used error codes: SMTP_EIO, SMTP_EPROTO, SMTP_EAUTHFAIL, SMTP_EINVAL
 */

#ifndef HAVE_LIBGSASL
int smtp_auth_cram_md5(smtp_server_t *srv, const char *user, 
	const char *password,
	list_t **error_msg, char **errstr)
{
    unsigned char digest[16];
    char hex[] = "0123456789abcdef";
    char *challenge;
    size_t challenge_len;
    char *b64;
    size_t b64_len;
    char *buf;
    char *p;
    size_t len;
    int i;
    list_t *msg;
    int e;
    
    *error_msg = NULL;
    if ((e = smtp_send_cmd(srv, errstr, "AUTH CRAM-MD5")) != SMTP_EOK)
    {
	return e;
    }
    if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
    {
	return e;
    }
    if (smtp_msg_status(msg) != 334)
    {
	*error_msg = msg;
	*errstr = xasprintf(_("command %s failed"), "AUTH CRAM-MD5");
	return SMTP_EPROTO;
    }
    /* we know the line is at least 4 characters long */
    challenge = (char *)(msg->next->data) + 4;
    challenge_len = strlen(challenge);
    len = 3 * (challenge_len / 4) + 2;
    b64 = xmalloc(len);
    if (!base64_decode(challenge, challenge_len, b64, &len))
    {
	list_xfree(msg, free);
	*errstr = xasprintf(_("authentication method CRAM-MD5: "
		    "server sent invalid challenge"));
	return SMTP_EPROTO;
    }
    list_xfree(msg, free);
    hmac_md5(password, strlen(password), b64, len, digest);
    free(b64);
    
    /* construct username + ' ' + digest_in_hex */
    len = strlen(user);
    buf = xmalloc((len + 1 + 32 + 1) * sizeof(char));
    strcpy(buf, user);
    p = buf + len;
    *p++ = ' ';
    for (i = 0; i < 16; i++)
    {
	p[2 * i] = hex[(digest[i] & 0xf0) >> 4];
	p[2 * i + 1] = hex[digest[i] & 0x0f];
    }
    p[32] = '\0';
    
    b64_len = BASE64_LENGTH(len + 33);
    b64 = xmalloc(b64_len + 1);
    base64_encode(buf, len + 33, b64, b64_len + 1);
    free(buf);
    if ((e = smtp_send_cmd(srv, errstr, "%s", b64)) != SMTP_EOK)
    {
	free(b64);
	return e;
    }
    free(b64);
    if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
    {
	return e;
    }
    if (smtp_msg_status(msg) != 235)
    {
	*error_msg = msg;
	*errstr = xasprintf(_("authentication failed (method %s)"), "CRAM-MD5");
	return SMTP_EAUTHFAIL;
    }
    list_xfree(msg, free);

    return SMTP_EOK;
}
#endif /* !HAVE_LIBGSASL */


/*
 * smtp_auth_external()
 * 
 * Do SMTP authentication via AUTH EXTERNAL.
 * This means the actual authentication is done via TLS; we just send the user
 * name to ther server.
 * The SMTP server must support SMTP_CAP_AUTH_EXTERNAL
 * Used error codes: SMTP_EIO, SMTP_EPROTO, SMTP_EAUTHFAIL, SMTP_EINVAL
 */

#ifndef HAVE_LIBGSASL
int smtp_auth_external(smtp_server_t *srv, const char *user,
	list_t **error_msg, char **errstr)
{
    size_t u_len;
    size_t b64_len;
    char *b64;
    list_t *msg;
    int e;

    *error_msg = NULL;
    if ((e = smtp_send_cmd(srv, errstr, "AUTH EXTERNAL")) != SMTP_EOK)
    {
	return e;
    }
    if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
    {
	return e;
    }
    if (smtp_msg_status(msg) != 334)
    {
	*error_msg = msg;
	*errstr = xasprintf(_("command %s failed"), "AUTH EXTERNAL");
	return SMTP_EPROTO;
    }
    list_xfree(msg, free);
    u_len = strlen(user);
    b64_len = BASE64_LENGTH(u_len);
    b64 = xmalloc(b64_len + 1);
    base64_encode(user, u_len, b64, b64_len + 1);
    if ((e = smtp_send_cmd(srv, errstr, "%s", b64)) != SMTP_EOK)
    {
	free(b64);
	return e;
    }
    free(b64);
    if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
    {
	return e;
    }
    if (smtp_msg_status(msg) != 235)
    {
	*error_msg = msg;
	*errstr = xasprintf(_("authentication failed (method %s)"), "EXTERNAL");
	return SMTP_EAUTHFAIL;
    }
    list_xfree(msg, free);

    return SMTP_EOK;
}
#endif /* !HAVE_LIBGSASL */


/*
 * smtp_server_supports_authmech()
 *
 * see smtp.h
 */

int smtp_server_supports_authmech(smtp_server_t *srv, const char *mech)
{
    return (((srv->cap.flags & SMTP_CAP_AUTH_PLAIN)
		&& strcmp(mech, "PLAIN") == 0)
	    || ((srv->cap.flags & SMTP_CAP_AUTH_CRAM_MD5)
		&& strcmp(mech, "CRAM-MD5") == 0)
	    || ((srv->cap.flags & SMTP_CAP_AUTH_DIGEST_MD5)
		&& strcmp(mech, "DIGEST-MD5") == 0)
	    || ((srv->cap.flags & SMTP_CAP_AUTH_EXTERNAL)
		&& strcmp(mech, "EXTERNAL") == 0)
	    || ((srv->cap.flags & SMTP_CAP_AUTH_GSSAPI)
		&& strcmp(mech, "GSSAPI") == 0)
	    || ((srv->cap.flags & SMTP_CAP_AUTH_LOGIN)
		&& strcmp(mech, "LOGIN") == 0)
	    || ((srv->cap.flags & SMTP_CAP_AUTH_NTLM)
		&& strcmp(mech, "NTLM") == 0));
}


/*
 * smtp_client_supports_authmech()
 *
 * see smtp.h
 */

int smtp_client_supports_authmech(const char *mech)
{
#ifdef HAVE_LIBGSASL

    int supported = 0;
    Gsasl *ctx;
    
    if (gsasl_init(&ctx) != GSASL_OK)
    {
	return 0;
    }
    supported = gsasl_client_support_p(ctx, mech);
    gsasl_done(ctx);
    return supported;
    
#else /* not HAVE_LIBGSASL */
    
    return (strcmp(mech, "CRAM-MD5") == 0
	    || strcmp(mech, "PLAIN") == 0
	    || strcmp(mech, "EXTERNAL") == 0
	    || strcmp(mech, "LOGIN") == 0);
    
#endif /* not HAVE_LIBGSASL */
}


/*
 * smtp_auth()
 *
 * see smtp.h
 */

int smtp_auth(smtp_server_t *srv,
	const char *hostname,
	const char *user, 
	const char *password,
#ifdef HAVE_LIBGSASL
	const char *ntlmdomain,
#else
	const char *ntlmdomain UNUSED,
#endif
	const char *auth_mech,
	char *(*password_callback)(const char *hostname, const char *user),
	list_t **error_msg,
	char **errstr)
{
#ifdef HAVE_LIBGSASL
    int e;
    list_t *msg;
    Gsasl *ctx;
    Gsasl_session *sctx;
    char *input;
    char inbuf[SMTP_BUFSIZE];
    char *outbuf;
    int error_code;
    int auth_plain_special;
    char *callback_password = NULL;


    *error_msg = NULL;
    if (strcmp(auth_mech, "") != 0 
	    && !smtp_server_supports_authmech(srv, auth_mech))
    {
	*errstr = xasprintf(
		_("the server does not support authentication method %s"), 
		auth_mech);
	return SMTP_EUNAVAIL;
    }
    if ((error_code = gsasl_init(&ctx)) != GSASL_OK)
    {
	*errstr = xasprintf(_("GNU SASL: %s"), gsasl_strerror(error_code));
	return SMTP_ELIBFAILED;
    }
    if (strcmp(auth_mech, "") != 0 && !gsasl_client_support_p(ctx, auth_mech))
    {
	gsasl_done(ctx);
	*errstr = xasprintf(
		_("GNU SASL: authentication method %s not supported"), 
		auth_mech);
	return SMTP_ELIBFAILED;
    }
    if (strcmp(auth_mech, "") == 0)
    {
	/* Choose "best" authentication mechanism. */
	/* TODO: use gsasl_client_suggest_mechanism()? */
	if (gsasl_client_support_p(ctx, "GSSAPI") 
		&& (srv->cap.flags & SMTP_CAP_AUTH_GSSAPI))
	{
	    auth_mech = "GSSAPI";
	}
	else if (gsasl_client_support_p(ctx, "DIGEST-MD5") 
		&& (srv->cap.flags & SMTP_CAP_AUTH_DIGEST_MD5))
	{
	    auth_mech = "DIGEST-MD5";
	}
	else if (gsasl_client_support_p(ctx, "CRAM-MD5") 
		&& (srv->cap.flags & SMTP_CAP_AUTH_CRAM_MD5))
	{
	    auth_mech = "CRAM-MD5";
	}
#ifdef HAVE_TLS
	else if (tls_is_active(&srv->tls))
	{
	    if (gsasl_client_support_p(ctx, "PLAIN") 
		    && (srv->cap.flags & SMTP_CAP_AUTH_PLAIN))
	    {
		auth_mech = "PLAIN";
	    }
	    else if (gsasl_client_support_p(ctx, "LOGIN") 
		    && (srv->cap.flags & SMTP_CAP_AUTH_LOGIN))
	    {
		auth_mech = "LOGIN";
	    }
	    else if (gsasl_client_support_p(ctx, "NTLM") 
	    	    && (srv->cap.flags & SMTP_CAP_AUTH_NTLM))
	    {
		auth_mech = "NTLM";
	    }
	}
#endif /* HAVE_TLS */
    }
    if (strcmp(auth_mech, "") == 0)
    {
	gsasl_done(ctx);
#ifdef HAVE_TLS
	if (!tls_is_active(&srv->tls))
	{
#endif /* HAVE_TLS */
	    *errstr = xasprintf(_("cannot use a secure authentication method"));
#ifdef HAVE_TLS
	}
	else
	{
	    *errstr = xasprintf(
		    _("cannot find a usable authentication method"));
     	}
#endif /* not HAVE_TLS */
	return SMTP_EUNAVAIL;
    }
    
    /* Check availability of required authentication data */
    if (strcmp(auth_mech, "EXTERNAL") != 0)
    {
	/* GSSAPI, DIGEST-MD5, CRAM-MD5, PLAIN, LOGIN, NTLM all need a user 
	 * name */
	if (!user)
	{
	    gsasl_done(ctx);
	    *errstr = xasprintf(_("authentication method %s needs a user name"),
		    auth_mech);
	    return SMTP_EUNAVAIL;
	}
	/* DIGEST-MD5, CRAM-MD5, PLAIN, LOGIN, NTLM all need a password */
	if (strcmp(auth_mech, "GSSAPI") != 0 && !password)
	{
	    if (!password_callback 
		    || !(callback_password = password_callback(hostname, user)))
	    {
		gsasl_done(ctx);
		*errstr = xasprintf(
			_("authentication method %s needs a password"),
			auth_mech);
		return SMTP_EUNAVAIL;
	    }
	    password = callback_password;
	}
    }

    if ((error_code = gsasl_client_start(ctx, auth_mech, &sctx)) != GSASL_OK)
    {
	gsasl_done(ctx);
	*errstr = xasprintf(_("GNU SASL: %s"), gsasl_strerror(error_code));
	return SMTP_ELIBFAILED;
    }

    /* Set the authentication properties */
    if (user)
    {
	gsasl_property_set(sctx, GSASL_AUTHID, user);
	/* GSASL_AUTHZID must not be set for DIGEST-MD5, because otherwise
    	 * authentication may fail (tested with postfix). Set it only for 
	 * EXTERNAL. */
	if (strcmp(auth_mech, "EXTERNAL") == 0)
	{
	    gsasl_property_set(sctx, GSASL_AUTHZID, user);
	}
    }
    if (password)
    {
	gsasl_property_set(sctx, GSASL_PASSWORD, password);
    }
    free(callback_password);
    /* For DIGEST-MD5 and GSSAPI */
    gsasl_property_set(sctx, GSASL_SERVICE, "smtp");
    if (hostname)
    {
	gsasl_property_set(sctx, GSASL_HOSTNAME, hostname);
    }
    /* For NTLM. Postfix does not care, MS IIS needs an arbitrary non-empty
     * string. */
    if (ntlmdomain)
    {
	gsasl_property_set(sctx, GSASL_REALM, ntlmdomain);
    }

    /* Bigg authentication loop */
    input = NULL;
    do
    {
	error_code = gsasl_step64(sctx, input, &outbuf);
	if (error_code != GSASL_OK && error_code != GSASL_NEEDS_MORE)
	{
	    gsasl_finish(sctx);
	    gsasl_done(ctx);
	    *errstr = xasprintf(_("GNU SASL: %s"), gsasl_strerror(error_code));
	    return SMTP_ELIBFAILED;
	}
	if (!input)
	{
	    if (strcmp(auth_mech, "PLAIN") == 0 && outbuf[0])
	    {
		/* AUTH PLAIN needs special treatment because it needs to send
		 * the authentication data together with the AUTH PLAIN command.
		 * At least smtp.web.de requires this, and I happen to use this
		 * server :) */
		auth_plain_special = 1;
		if ((e = smtp_send_cmd(srv, errstr, 
				"AUTH PLAIN %s", outbuf)) != SMTP_EOK)
		{
		    gsasl_finish(sctx);
		    gsasl_done(ctx);
		    free(outbuf);
		    return e;
		}
	    }
	    else
	    {	    
		auth_plain_special = 0;
		if ((e = smtp_send_cmd(srv, errstr, 
				"AUTH %s", auth_mech)) != SMTP_EOK)
		{
		    gsasl_finish(sctx);
		    gsasl_done(ctx);
		    free(outbuf);
		    return e;
		}
	    }
	    if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
	    {
		gsasl_finish(sctx);
		gsasl_done(ctx);
	    	free(outbuf);
		return e;
	    }
	    if (smtp_msg_status(msg) != 334 && smtp_msg_status(msg) != 235)
	    {
		*error_msg = msg;
		gsasl_finish(sctx);
		gsasl_done(ctx);
		free(outbuf);
		*errstr = xasprintf(_("authentication failed (method %s)"), 
			auth_mech);
		return SMTP_EAUTHFAIL;
	    }	    
	    /* msg->next->data cannot be longer than SMTP_BUFSIZE-1 */
	    strcpy(inbuf, msg->next->data);
	    list_xfree(msg, free);
	    input = inbuf + 4;
	    if (auth_plain_special)
	    {
		free(outbuf);
		continue;
	    }
	}
	/* For all mechanisms except GSSAPI, testing for (outbuf[0]) works.
	 * GSSAPI needs an additional step with empty output. */
	if (outbuf[0] 
		|| (GSASL_NEEDS_MORE && (strcmp(auth_mech, "GSSAPI") == 0)))
	{
	    if ((e = smtp_send_cmd(srv, errstr, "%s", outbuf)) != SMTP_EOK)
    	    {
		gsasl_finish(sctx);
		gsasl_done(ctx);
		free(outbuf);
		return e;
	    }
	    if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
	    {
		gsasl_finish(sctx);
		gsasl_done(ctx);
		free(outbuf);
		return e;
	    }
	    if (smtp_msg_status(msg) != 334 && smtp_msg_status(msg) != 235)
	    {
		*error_msg = msg;
		gsasl_finish(sctx);
		gsasl_done(ctx);
		free(outbuf);
		*errstr = xasprintf(_("authentication failed (method %s)"), 
			auth_mech);
		return SMTP_EAUTHFAIL;
	    }	    
	    /* msg->next->data cannot be longer than SMTP_BUFSIZE-1 */
	    strcpy(inbuf, msg->next->data);
	    list_xfree(msg, free);
	    input = inbuf + 4;
	}
	free(outbuf);
    }
    while (error_code == GSASL_NEEDS_MORE);
    if (error_code != GSASL_OK)
    {
	gsasl_finish(sctx);
	gsasl_done(ctx);
	*errstr = xasprintf(_("authentication failed: %s (method %s)"), 
		gsasl_strerror(error_code), auth_mech);
	return SMTP_EAUTHFAIL;
    }
    gsasl_finish(sctx);
    gsasl_done(ctx);
    /* For DIGEST-MD5, we need to send an empty answer to the last 334 
     * response before we get 235. */
    if (strncmp(inbuf, "235 ", 4) != 0)
    {
	if ((e = smtp_send_cmd(srv, errstr, "")) != SMTP_EOK)
	{
	    return e;
	}
	if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
	{
	    return e;
	}
	if (smtp_msg_status(msg) != 235)
	{
	    *error_msg = msg;
	    *errstr = xasprintf(_("authentication failed (method %s)"),
		    auth_mech);
	    return SMTP_EAUTHFAIL;
	}
	list_xfree(msg, free);
    }
    return SMTP_EOK;

#else /* not HAVE_LIBGSASL */

    char *callback_password = NULL;
    int e;
    
    
    *error_msg = NULL;
    if (strcmp(auth_mech, "") != 0 
	    && !smtp_server_supports_authmech(srv, auth_mech))
    {
	*errstr = xasprintf( 
		_("the server does not support authentication method %s"), 
		auth_mech);
	return SMTP_EUNAVAIL;
    }
    if (strcmp(auth_mech, "") == 0)
    {
	/* Choose "best" authentication mechanism. */
	if (srv->cap.flags & SMTP_CAP_AUTH_CRAM_MD5)
	{
	    auth_mech = "CRAM-MD5";
	}
#ifdef HAVE_TLS
	else if (tls_is_active(&srv->tls))
	{
	    if (srv->cap.flags & SMTP_CAP_AUTH_PLAIN)
	    {
		auth_mech = "PLAIN";
	    }
	    else if (srv->cap.flags & SMTP_CAP_AUTH_LOGIN)
	    {
		auth_mech = "LOGIN";
	    }
	}
#endif /* HAVE_TLS */
    }
    if (strcmp(auth_mech, "") == 0)
    {
#ifdef HAVE_TLS
       	if (!tls_is_active(&srv->tls))
 	{
#endif /* HAVE_TLS */
	    *errstr = xasprintf(_("cannot use a secure authentication method"));
#ifdef HAVE_TLS
    	}
    	else
 	{
	    *errstr = xasprintf(
		    _("cannot find a usable authentication method"));
	}
#endif /* not HAVE_TLS */
	return SMTP_EUNAVAIL;
    }

    if (strcmp(auth_mech, "EXTERNAL") != 0)
    {
	/* CRAMD-MD5, PLAIN, LOGIN all need a user name and a password */
	if (!user)
	{
	    *errstr = xasprintf(_("authentication method %s needs a user name"),
		    auth_mech);
	    return SMTP_EUNAVAIL;
	}
	if (!password)
	{
	    if (!password_callback 
		    || !(callback_password = password_callback(hostname, user)))
	    {
		*errstr = xasprintf(
			_("authentication method %s needs a password"),
			auth_mech);
		return SMTP_EUNAVAIL;
	    }
	    password = callback_password;
	}
    }

    if (strcmp(auth_mech, "CRAM-MD5") == 0)
    {
	e = smtp_auth_cram_md5(srv, user, password, error_msg, errstr);
    }
    else if (strcmp(auth_mech, "PLAIN") == 0)
    {
	e = smtp_auth_plain(srv, user, password, error_msg, errstr);
    }
    else if (strcmp(auth_mech, "EXTERNAL") == 0)
    {
	e = smtp_auth_external(srv, user ? user : "", error_msg, errstr);
    }
    else if (strcmp(auth_mech, "LOGIN") == 0)
    {
	e = smtp_auth_login(srv, user, password, error_msg, errstr);
    }
    else
    {
	*errstr = xasprintf(_("authentication method %s not supported"),
		auth_mech);
	e = SMTP_ELIBFAILED;
    }
    free(callback_password);
    return e;

#endif /* not HAVE_LIBGSASL */
}


/*
 * smtp_send_envelope()
 * 
 * see smtp.h
 */

int smtp_send_envelope(smtp_server_t *srv,
	const char *envelope_from, 
	list_t *recipients,
	const char *dsn_notify,
	const char *dsn_return,
	list_t **error_msg,
	char **errstr)
{
    int e;
    list_t *msg;    
    int mailfrom_cmd_was_sent = 0;
    int mailfrom_reply_was_rcvd = 0;
    list_t *rcpt_send = recipients;
    list_t *rcpt_recv = recipients;
    int data_cmd_was_sent = 0;
    int data_reply_was_rcvd = 0;
    int pipeline_limit = 1;
    int piped_commands = 0;

    
    *error_msg = NULL;
    if (srv->cap.flags & SMTP_CAP_PIPELINING)
    {
	pipeline_limit = SMTP_PIPELINE_LIMIT;
    }
        
    /* Send the MAIL FROM, RCPT TO and DATA commands using pipelining. The 
     * number of pipelined commands will never be greater than pipeline_limit
     * to avoid problems with the TCP window size (exceeding it can lead to 
     * deadlocks). pipeline_limit == 1 disables pipelining. */
    while (!data_reply_was_rcvd)
    {
	while (!data_cmd_was_sent && piped_commands < pipeline_limit)
	{
	    /* send */
	    if (!mailfrom_cmd_was_sent)
	    {
		if (dsn_return)
		{
		    e = smtp_send_cmd(srv, errstr, "MAIL FROM:<%s> RET=%s", 
			    strcasecmp(envelope_from, "MAILER-DAEMON") == 0 
			    ? "" : envelope_from, dsn_return);
		}
		else
		{
		    e = smtp_send_cmd(srv, errstr, "MAIL FROM:<%s>", 
			    strcasecmp(envelope_from, "MAILER-DAEMON") == 0 
			    ? "" : envelope_from);
		}
		if (e != SMTP_EOK)
		{
		    return e;
		}		
		mailfrom_cmd_was_sent = 1;
	    }
	    else if (!list_is_empty(rcpt_send))
	    {
		rcpt_send = rcpt_send->next;
		if (dsn_notify)
		{
		    e = smtp_send_cmd(srv, errstr, "RCPT TO:<%s> NOTIFY=%s",
			    (char *)(rcpt_send->data), dsn_notify);
		}
		else
		{
		    e = smtp_send_cmd(srv, errstr, "RCPT TO:<%s>", 
			    (char *)(rcpt_send->data));
		}
		if (e != SMTP_EOK)
		{
		    return e;
		}		
	    }
	    else
	    {
		if ((e = smtp_send_cmd(srv, errstr, "DATA")) != SMTP_EOK)
		{
		    return e;
		}
		data_cmd_was_sent = 1;
	    }
	    piped_commands++;
	}
	while (piped_commands > 0)
	{
	    /* receive */
	    if (!mailfrom_reply_was_rcvd)
	    {
		if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
		{
		    return e;
		}
		if (smtp_msg_status(msg) != 250)
		{
		    *error_msg = msg;
		    *errstr = xasprintf(_("envelope from address %s not "
				"accepted by the server"), envelope_from);
		    return SMTP_EINVAL;
		}
		list_xfree(msg, free);
		mailfrom_reply_was_rcvd = 1;
	    }
	    else if (!list_is_empty(rcpt_recv))
	    {
		rcpt_recv = rcpt_recv->next;
		if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
		{
		    return e;
		}
		if (smtp_msg_status(msg) != 250)
		{
		    *error_msg = msg;
		    *errstr = xasprintf(_("recipient address %s not "
				"accepted by the server"),
			    (char *)(rcpt_recv->data));
		    return SMTP_EINVAL;
		}
		list_xfree(msg, free);		
	    }
	    else
	    {
		if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
		{
		    return e;
		}
		if (smtp_msg_status(msg) != 354)
		{
		    *error_msg = msg;
		    *errstr = xasprintf(
			    _("the server does not accept mail data"));
		    return SMTP_EUNAVAIL;
		}
		list_xfree(msg, free);
		data_reply_was_rcvd = 1;
	    }
	    piped_commands--;
	}
    }

    return SMTP_EOK;
}


/*
 * smtp_send_mail()
 *
 * see smtp.h
 */

int smtp_send_mail(smtp_server_t *srv, FILE *mailf, int keep_bcc, 
	long *mailsize, char **errstr)
{
    char bigbuffer[MAIL_BUFSIZE + 3];	/* buffer + leading dot + ending CRLF */
    char *buffer;
    size_t len;
    char *send_buf;
    size_t send_len;
    int in_header;
    int in_bcc;
    int line_starts;
    int line_continues;
    int e;
    
    bigbuffer[0] = '.';
    buffer = bigbuffer + 1;
    in_header = 1;
    in_bcc = 0;
    line_continues = 0;
    e = SMTP_EOK;
    for (;;)
    {
	if (stream_gets(mailf, buffer, MAIL_BUFSIZE, &len, errstr) 
		!= STREAM_EOK)
	{
	    return SMTP_EIO;
	}
	if (len == 0)
	{
	    break;
	}
	line_starts = !line_continues;
	if (len > 0 && buffer[len - 1] == '\n')
	{
	    /* first case: we have a line end */
	    buffer[--len] = '\0';
	    if (len > 0 && buffer[len - 1] == '\r')
	    {
		buffer[--len] = '\0';
	    }
	    line_continues = 0;
	}
	else if (len == MAIL_BUFSIZE - 1)
	{
	    /* second case: the line continues */
	    if (buffer[len - 1] == '\r')
	    {
		/* We have CRLF that is divided by the buffer boundary. Since CR
		 * may not appear alone in a mail according to RFC2822, we
		 * know that the next buffer will be "\n\0", so it's safe to
		 * just delete the CR. */
		buffer[--len] = '\0';
	    }
	    line_continues = 1;
	}
	else
	{
	    /* third case: this is the last line, and it lacks a newline 
	     * character */
	    line_continues = 0;
	}
	if (!keep_bcc)
	{
	    if (line_starts && in_header && buffer[0] == '\0')
	    {
		in_header = 0;
	    }
	    if (in_header)
	    {
		if (line_starts)
		{
		    if (!in_bcc)
		    {
			if (strncasecmp(buffer, "Bcc:", 4) == 0)
			{
			    in_bcc = 1;
			    /* remove Bcc header by ignoring this line */
			    continue;
			}
		    }
		    else
		    {
			/* continued header lines begin with "horizontal 
			 * whitespace" (RFC 2822, section 2.2.3) */
			if (buffer[0] == '\t' || buffer[0] == ' ')
			{
			    /* remove Bcc header by ignoring this line */
			    continue;
			}
			else
			{
			    in_bcc = 0;
			}
		    }
		}
		else
		{
		    if (in_bcc)
		    {
			/* remove Bcc header by ignoring this line */
		    	continue;
		    }
		}
	    }
	}
	send_buf = buffer;
	send_len = len;
	if (line_starts && buffer[0] == '.')
	{
	    /* Quote the leading dot with another dot */
	    send_buf = bigbuffer;
	    send_len = len + 1;
	}
	if (!line_continues)
	{
	    /* Append CRLF */
	    buffer[len] = '\r';
	    buffer[len + 1] = '\n';
	    buffer[len + 2] = '\0';
	    send_len += 2;
	}
	/* Update mailsize. Do not count a quote dot. Count CRLF as one
	 * character. */
	*mailsize += (long)len + (line_continues ? 0 : 1);
	if ((e = smtp_put(srv, send_buf, send_len, errstr)) != SMTP_EOK)
	{
	    return e;
	}
    }

    return SMTP_EOK;
}


/*
 * smtp_end_mail()
 *
 * see smtp.h
 */

int smtp_end_mail(smtp_server_t *srv, list_t **error_msg, char **errstr)
{
    int e;
    list_t *msg;
    
    *error_msg = NULL;
    if ((e = smtp_send_cmd(srv, errstr, ".")) != SMTP_EOK)
    {
	return e;
    }
    if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
    {
	return e;
    }
    if (smtp_msg_status(msg) != 250)
    {
	*error_msg = msg;
	*errstr = xasprintf(_("the server did not accept the mail"));
	return SMTP_EUNAVAIL;
    }
    list_xfree(msg, free);
    
    return SMTP_EOK;
}


/*
 * smtp_end_mail_lmtp()
 *
 * see smtp.h
 * 
 */

void _smtp_free_list_of_lists(void *l)
{
    list_xfree((list_t *)l, free);
}

int smtp_end_mail_lmtp(smtp_server_t *srv, 
	list_t *recipients, 
	list_t **errstrs,
	list_t **error_msgs,
	char **errstr)
{
    int e;
    list_t *msg;
    list_t *lp_recipients;
    list_t *lp_errstrs;
    list_t *lp_error_msgs;
    int all_recipients_accepted;
    char *tmp;
    

    if ((e = smtp_send_cmd(srv, errstr, ".")) != SMTP_EOK)
    {
	*errstrs = NULL;
	*error_msgs = NULL;
	return e;
    }
    
    *errstrs = list_new();
    *error_msgs = list_new();
    lp_errstrs = *errstrs;
    lp_error_msgs = *error_msgs;
    lp_recipients = recipients;
    all_recipients_accepted = 1;
    while (!list_is_empty(lp_recipients))
    {
	lp_recipients = lp_recipients->next;
	if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
	{
	    list_xfree(*errstrs, free);
	    *errstrs = NULL;
	    list_xfree(*error_msgs, _smtp_free_list_of_lists);
	    *error_msgs = NULL;
	    return e;
	}
	if (smtp_msg_status(msg) != 250)
	{
	    all_recipients_accepted = 0;
	    tmp = xasprintf(_("the server refuses to send the mail to %s"),
		    (char *)(lp_recipients->data));
	    list_insert(lp_errstrs, tmp);
	    list_insert(lp_error_msgs, msg);
	}
	else
	{
	    list_xfree(msg, free);
	    list_insert(lp_errstrs, NULL);
	    list_insert(lp_error_msgs, NULL);
	}
	lp_errstrs = lp_errstrs->next;
	lp_error_msgs = lp_error_msgs->next;
    }
    
    if (all_recipients_accepted)
    {
	/* we can use list_free() here since all list entries are just NULL */
	list_free(*errstrs);
	*errstrs = NULL;
	list_free(*error_msgs);
	*error_msgs = NULL;
	return SMTP_EOK;
    }
    else
    {
	return SMTP_EUNAVAIL;
    }
}


/*
 * smtp_etrn()
 *
 * see smtp.h
 */

int smtp_etrn(smtp_server_t *srv, const char *etrn_argument, 
	list_t **error_msg, char **errstr)
{
    int e;
    list_t *msg;
    
    *error_msg = NULL;
    if ((e = smtp_send_cmd(srv, errstr, "ETRN %s", etrn_argument)) != SMTP_EOK)
    {
	return e;
    }
    if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK)
    {
	return e;
    }
    switch (smtp_msg_status(msg))
    {
	case 250: /* OK, queuing for node <x> started */
	case 251: /* OK, no messages waiting for node <x> */
	case 252: /* OK, pending messages for node <x> started */
	case 253: /* OK, <n> pending messages for node <x> started */
	    break;

	case 458: /* Unable to queue messages for node <x> */
	case 459: /* Node <x> not allowed: <reason> */
	    *error_msg = msg;
	    *errstr = xasprintf(
		    _("the server is unable to fulfill the request"));
	    return SMTP_EUNAVAIL;
	    break;
	    
	case 500: /* Syntax Error */
	case 501: /* 501 Syntax Error in Parameters */
	    *error_msg = msg;
	    *errstr = xasprintf(
		    _("invalid argument for Remote Message Queue Starting"));
	    return SMTP_EINVAL;
	    break;
	    
	default:
	    *error_msg = msg;
	    *errstr = xasprintf(_("command %s failed"), "ETRN");
	    return SMTP_EPROTO;
	    break;
    }
    list_xfree(msg, free);

    return SMTP_EOK;
}


/*
 * smtp_quit()
 *
 * see smtp.h
 */

int smtp_quit(smtp_server_t *srv, char **errstr)
{
    int e;
    list_t *msg;

    if ((e = smtp_send_cmd(srv, errstr, "QUIT")) == SMTP_EOK)
    {
	e = smtp_get_msg(srv, &msg, errstr);
    }
    if (msg)
    {
	list_xfree(msg, free);
    }
    return e;
}


/*
 * smtp_close()
 *
 * see smtp.h
 */

void smtp_close(smtp_server_t *srv)
{
#ifdef HAVE_TLS
    if (tls_is_active(&srv->tls))
    {
	tls_close(&srv->tls);
    }
#endif /* HAVE_TLS */
    net_close_socket(srv->fd);
}


syntax highlighted by Code2HTML, v. 0.9.1