/* mutt - text oriented MIME mail user agent
 * Copyright (C) 2002 Michael R. Elkins <me@mutt.org>
 * Copyright (C) 2005-2009 Brendan Cully <brendan@kublai.com>
 *
 *     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 2 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, write to the Free Software
 *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
 */

/* This file contains code for direct SMTP delivery of email messages. */

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

#include "mutt.h"
#include "mutt_curses.h"
#include "mutt_socket.h"
#ifdef USE_SSL
# include "mutt_ssl.h"
#endif
#ifdef USE_SASL_CYRUS
#include "mutt_sasl.h"

#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#endif
#ifdef USE_SASL_GNU
#include "mutt_sasl_gnu.h"
#include <gsasl.h>
#endif

#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/stat.h>

#define smtp_success(x) ((x)/100 == 2)
#define smtp_ready 334
#define smtp_continue 354

#define smtp_err_read -2
#define smtp_err_write -3
#define smtp_err_code -4

#define SMTP_PORT 25
#define SMTPS_PORT 465

#define SMTP_AUTH_SUCCESS 0
#define SMTP_AUTH_UNAVAIL 1
#define SMTP_AUTH_FAIL    -1

enum {
  STARTTLS,
  AUTH,
  DSN,
  EIGHTBITMIME,
  SMTPUTF8,

  CAPMAX
};

static int smtp_auth(CONNECTION *conn);
static int smtp_auth_oauth(CONNECTION *conn, int xoauth2);
#ifdef USE_SASL_CYRUS
static int smtp_auth_sasl(CONNECTION *conn, const char *mechanisms);
#endif
#ifdef USE_SASL_GNU
static int smtp_auth_gsasl(CONNECTION *conn, const char *method);
#endif

static int smtp_fill_account(ACCOUNT *account);
static int smtp_open(CONNECTION *conn);

static int Esmtp = 0;
static char *AuthMechs = NULL;
static unsigned char Capabilities[(CAPMAX + 7)/ 8];

/* Note: the 'len' parameter is actually the number of bytes, as
 * returned by mutt_socket_readln().  If all callers are converted to
 * mutt_socket_buffer_readln() we can pass in the actual len, or
 * perhaps the buffer itself.
 */
static int smtp_code(const char *buf, size_t len, int *n)
{
  char code[4];

  if (len < 4)
    return -1;
  code[0] = buf[0];
  code[1] = buf[1];
  code[2] = buf[2];
  code[3] = 0;
  if (mutt_atoi(code, n, 0) < 0)
    return -1;
  return 0;
}

/* Reads a command response from the SMTP server.
 * Returns:
 * 0    on success (2xx code) or continue (354 code)
 * -1   write error, or any other response code
 */
static int
smtp_get_resp(CONNECTION * conn)
{
  int n;
  char buf[1024];
  char *smtp_response;

  do
  {
    n = mutt_socket_readln(buf, sizeof(buf), conn);
    if (n < 4)
    {
      /* read error, or no response code */
      return smtp_err_read;
    }

    smtp_response = buf + 3;
    if (*smtp_response)
    {
      smtp_response++;

      if (!ascii_strncasecmp("8BITMIME", smtp_response, 8))
        mutt_bit_set(Capabilities, EIGHTBITMIME);
      else if (!ascii_strncasecmp("AUTH ", smtp_response, 5))
      {
        mutt_bit_set(Capabilities, AUTH);
        FREE(&AuthMechs);
        AuthMechs = safe_strdup(smtp_response + 5);
      }
      else if (!ascii_strncasecmp("DSN", smtp_response, 3))
        mutt_bit_set(Capabilities, DSN);
      else if (!ascii_strncasecmp("STARTTLS", smtp_response, 8))
        mutt_bit_set(Capabilities, STARTTLS);
      else if (!ascii_strncasecmp("SMTPUTF8", smtp_response, 8))
        mutt_bit_set(Capabilities, SMTPUTF8);
    }

    if (smtp_code(buf, n, &n) < 0)
      return smtp_err_code;

  } while (buf[3] == '-');

  if (smtp_success(n) || n == smtp_continue)
    return 0;

  mutt_error(_("SMTP session failed: %s"), buf);
  return -1;
}

static int
smtp_get_auth_response(CONNECTION *conn, BUFFER *input_buf, int *smtp_rc,
                       BUFFER *response_buf)
{
  const char *smtp_response;

  mutt_buffer_clear(response_buf);
  do
  {
    if (mutt_socket_buffer_readln(input_buf, conn) < 0)
      return -1;
    if (smtp_code(mutt_b2s(input_buf),
                  mutt_buffer_len(input_buf) + 1, /* number of bytes */
                  smtp_rc) < 0)
      return -1;

    if (*smtp_rc != smtp_ready)
      break;

    smtp_response = mutt_b2s(input_buf) + 3;
    if (*smtp_response)
    {
      smtp_response++;
      mutt_buffer_addstr(response_buf, smtp_response);
    }
  } while (mutt_b2s(input_buf)[3] == '-');

  return 0;
}

static int
smtp_rcpt_to(CONNECTION * conn, const ADDRESS * a)
{
  char buf[1024];
  int r;

  while (a)
  {
    /* weed out group mailboxes, since those are for display only */
    if (!a->mailbox || a->group)
    {
      a = a->next;
      continue;
    }
    if (mutt_bit_isset(Capabilities, DSN) && DsnNotify)
      snprintf(buf, sizeof(buf), "RCPT TO:<%s> NOTIFY=%s\r\n",
               a->mailbox, DsnNotify);
    else
      snprintf(buf, sizeof(buf), "RCPT TO:<%s>\r\n", a->mailbox);
    if (mutt_socket_write(conn, buf) == -1)
      return smtp_err_write;
    if ((r = smtp_get_resp(conn)))
    {
      mutt_sleep(2);
      mutt_error(_("SMTP session failed: cannot add recipient <%s>"),
                 a->mailbox);
      return r;
    }
    a = a->next;
  }

  return 0;
}

static int
smtp_data(CONNECTION * conn, const char *msgfile)
{
  char buf[1024];
  FILE *fp = 0;
  progress_t progress;
  struct stat st;
  int r, term = 0;
  size_t buflen = 0;

  fp = fopen(msgfile, "r");
  if (!fp)
  {
    mutt_error(_("SMTP session failed: unable to open %s"), msgfile);
    return -1;
  }
  stat(msgfile, &st);
  unlink(msgfile);
  mutt_progress_init(&progress, _("Sending message..."), MUTT_PROGRESS_SIZE,
                     NetInc, st.st_size);

  snprintf(buf, sizeof(buf), "DATA\r\n");
  if (mutt_socket_write(conn, buf) == -1)
  {
    safe_fclose(&fp);
    return smtp_err_write;
  }
  if ((r = smtp_get_resp(conn)))
  {
    safe_fclose(&fp);
    return r;
  }

  while (fgets(buf, sizeof(buf) - 1, fp))
  {
    buflen = mutt_strlen(buf);
    term = buflen && buf[buflen-1] == '\n';
    if (term && (buflen == 1 || buf[buflen - 2] != '\r'))
      snprintf(buf + buflen - 1, sizeof(buf) - buflen + 1, "\r\n");
    if (buf[0] == '.')
    {
      if (mutt_socket_write_d(conn, ".", -1, MUTT_SOCK_LOG_FULL) == -1)
      {
        safe_fclose(&fp);
        return smtp_err_write;
      }
    }
    if (mutt_socket_write_d(conn, buf, -1, MUTT_SOCK_LOG_FULL) == -1)
    {
      safe_fclose(&fp);
      return smtp_err_write;
    }
    mutt_progress_update(&progress, ftell(fp), -1);
  }
  if (!term && buflen &&
      mutt_socket_write_d(conn, "\r\n", -1, MUTT_SOCK_LOG_FULL) == -1)
  {
    safe_fclose(&fp);
    return smtp_err_write;
  }
  safe_fclose(&fp);

  /* terminate the message body */
  if (mutt_socket_write(conn, ".\r\n") == -1)
    return smtp_err_write;

  if ((r = smtp_get_resp(conn)))
    return r;

  return 0;
}


/* Returns 1 if a contains at least one 8-bit character, 0 if none do.
 */
static int address_uses_unicode(const char *a)
{
  if (!a)
    return 0;

  while (*a)
  {
    if ((unsigned char) *a & (1<<7))
      return 1;
    a++;
  }

  return 0;
}


/* Returns 1 if any address in a contains at least one 8-bit
 * character, 0 if none do.
 */
static int addresses_use_unicode(const ADDRESS *a)
{
  while (a)
  {
    if (a->mailbox && !a->group && address_uses_unicode(a->mailbox))
      return 1;
    a = a->next;
  }
  return 0;
}


int
mutt_smtp_send(const ADDRESS *from, const ADDRESS *to, const ADDRESS *cc,
               const ADDRESS *bcc, const char *msgfile, int eightbit)
{
  CONNECTION *conn;
  ACCOUNT account;
  const char *envfrom;
  char buf[1024];
  int ret = -1;

  if (smtp_fill_account(&account) < 0)
    return ret;

  if (!(conn = mutt_conn_find(NULL, &account)))
    return -1;

  /* it might be better to synthesize an envelope from from user and host
   * but this condition is most likely arrived at accidentally */
  if (option(OPTENVFROM) && EnvFrom && EnvFrom->mailbox)
    envfrom = EnvFrom->mailbox;
  else if (from && from->mailbox)
    envfrom = from->mailbox;
  else
  {
    mutt_error(_("No from address given"));
    mutt_socket_close(conn);
    return -1;
  }

  Esmtp = eightbit;

  do
  {
    /* send our greeting */
    if (( ret = smtp_open(conn)))
      break;
    FREE(&AuthMechs);

    /* send the sender's address */
    ret = snprintf(buf, sizeof(buf), "MAIL FROM:<%s>", envfrom);
    if (eightbit && mutt_bit_isset(Capabilities, EIGHTBITMIME))
    {
      safe_strncat(buf, sizeof(buf), " BODY=8BITMIME", 15);
      ret += 14;
    }
    if (DsnReturn && mutt_bit_isset(Capabilities, DSN))
      ret += snprintf(buf + ret, sizeof(buf) - ret, " RET=%s", DsnReturn);
    if (mutt_bit_isset(Capabilities, SMTPUTF8) &&
        (address_uses_unicode(envfrom) ||
         addresses_use_unicode(to) ||
         addresses_use_unicode(cc) ||
         addresses_use_unicode(bcc)))
      ret += snprintf(buf + ret, sizeof(buf) - ret, " SMTPUTF8");
    safe_strncat(buf, sizeof(buf), "\r\n", 3);
    if (mutt_socket_write(conn, buf) == -1)
    {
      ret = smtp_err_write;
      break;
    }
    if ((ret = smtp_get_resp(conn)))
      break;

    /* send the recipient list */
    if ((ret = smtp_rcpt_to(conn, to)) || (ret = smtp_rcpt_to(conn, cc))
        || (ret = smtp_rcpt_to(conn, bcc)))
      break;

    /* send the message data */
    if ((ret = smtp_data(conn, msgfile)))
      break;

    mutt_socket_write(conn, "QUIT\r\n");

    ret = 0;
  }
  while (0);

  if (conn)
    mutt_socket_close(conn);

  if (ret == smtp_err_read)
    mutt_error(_("SMTP session failed: read error"));
  else if (ret == smtp_err_write)
    mutt_error(_("SMTP session failed: write error"));
  else if (ret == smtp_err_code)
    mutt_error(_("Invalid server response"));

  return ret;
}

static int smtp_fill_account(ACCOUNT *account)
{
  static unsigned short SmtpPort = 0;

  struct servent *service;
  ciss_url_t url;
  char *urlstr;

  account->flags = 0;
  account->port = 0;
  account->type = MUTT_ACCT_TYPE_SMTP;

  urlstr = safe_strdup(SmtpUrl);
  url_parse_ciss(&url, urlstr);
  if ((url.scheme != U_SMTP && url.scheme != U_SMTPS)
      || mutt_account_fromurl(account, &url) < 0)
  {
    FREE(&urlstr);
    mutt_error(_("Invalid SMTP URL: %s"), SmtpUrl);
    mutt_sleep(1);
    return -1;
  }
  FREE(&urlstr);

  if (url.scheme == U_SMTPS)
    account->flags |= MUTT_ACCT_SSL;

  if (!account->port)
  {
    if (account->flags & MUTT_ACCT_SSL)
      account->port = SMTPS_PORT;
    else
    {
      if (!SmtpPort)
      {
        service = getservbyname("smtp", "tcp");
        if (service)
          SmtpPort = ntohs(service->s_port);
        else
          SmtpPort = SMTP_PORT;
        muttdbg(3, "Using default SMTP port %d", SmtpPort);
      }
      account->port = SmtpPort;
    }
  }

  return 0;
}

static int smtp_helo(CONNECTION *conn)
{
  char buf[LONG_STRING];
  const char *fqdn;

  memset(Capabilities, 0, sizeof(Capabilities));

  if (!Esmtp)
  {
    /* if TLS or AUTH are requested, use EHLO */
    if (conn->account.flags & MUTT_ACCT_USER)
      Esmtp = 1;
#ifdef USE_SSL
    if (option(OPTSSLFORCETLS) || quadoption(OPT_SSLSTARTTLS) != MUTT_NO)
      Esmtp = 1;
#endif
  }

  if (!(fqdn = mutt_fqdn(0)))
    fqdn = NONULL(Hostname);

  snprintf(buf, sizeof(buf), "%s %s\r\n", Esmtp ? "EHLO" : "HELO", fqdn);
  /* XXX there should probably be a wrapper in mutt_socket.c that
   * repeatedly calls conn->write until all data is sent.  This
   * currently doesn't check for a short write.
   */
  if (mutt_socket_write(conn, buf) == -1)
    return smtp_err_write;
  return smtp_get_resp(conn);
}

static int smtp_open(CONNECTION *conn)
{
  int rc;

  if (mutt_socket_open(conn))
    return -1;

  /* get greeting string */
  if ((rc = smtp_get_resp(conn)))
    return rc;

  if ((rc = smtp_helo(conn)))
    return rc;

#ifdef USE_SSL
  if (conn->ssf)
    rc = MUTT_NO;
  else if (option(OPTSSLFORCETLS))
    rc = MUTT_YES;
  else if (mutt_bit_isset(Capabilities, STARTTLS) &&
           (rc = query_quadoption(OPT_SSLSTARTTLS,
                                  _("Secure connection with TLS?"))) == -1)
    return rc;

  if (rc == MUTT_YES)
  {
    if (mutt_socket_write(conn, "STARTTLS\r\n") < 0)
      return smtp_err_write;
    if ((rc = smtp_get_resp(conn)))
      return rc;

    if (mutt_ssl_starttls(conn))
    {
      mutt_error(_("Could not negotiate TLS connection"));
      mutt_sleep(1);
      return -1;
    }

    /* re-EHLO to get authentication mechanisms */
    if ((rc = smtp_helo(conn)))
      return rc;
  }
#endif

  /* In some cases, the SMTP server will advertise AUTH even though
   * it's not required.  Check if the username is explicitly in the
   * URL to decide whether to call smtp_auth().
   *
   * For client certificates, we assume the server won't advertise
   * AUTH if they are pre-authenticated, because we also need to
   * handle the post-TLS authentication (AUTH EXTERNAL) case.
   */
  if (mutt_bit_isset(Capabilities, AUTH) &&
      (conn->account.flags & MUTT_ACCT_USER
#ifdef USE_SSL
       || SslClientCert
#endif
        ))
  {
    return smtp_auth(conn);
  }

  return 0;
}

static int smtp_auth(CONNECTION *conn)
{
  int r = SMTP_AUTH_UNAVAIL;

  if (SmtpAuthenticators)
  {
    char *methods = safe_strdup(SmtpAuthenticators);
    char *method;
    char *delim;

    for (method = methods; method; method = delim)
    {
      delim = strchr(method, ':');
      if (delim)
        *delim++ = '\0';
      if (! method[0])
        continue;

      muttdbg(2, "smtp_authenticate: Trying method %s", method);

      if (!ascii_strcasecmp(method, "oauthbearer"))
      {
        r = smtp_auth_oauth(conn, 0);
      }
      else if (!ascii_strcasecmp(method, "xoauth2"))
      {
        r = smtp_auth_oauth(conn, 1);
      }
      else
      {
#if defined(USE_SASL_CYRUS)
        r = smtp_auth_sasl(conn, method);
#elif defined(USE_SASL_GNU)
        r = smtp_auth_gsasl(conn, method);
#else
        mutt_error(_("SMTP authentication method %s requires SASL"), method);
        mutt_sleep(1);
        continue;
#endif
      }
      if (r == SMTP_AUTH_FAIL && delim)
      {
        mutt_error(_("%s authentication failed, trying next method"), method);
        mutt_sleep(1);
      }
      else if (r != SMTP_AUTH_UNAVAIL)
        break;
    }

    FREE(&methods);
  }
  else
  {
#if defined(USE_SASL_CYRUS)
    r = smtp_auth_sasl(conn, AuthMechs);
#elif defined(USE_SASL_GNU)
    r = smtp_auth_gsasl(conn, NULL);
#else
    mutt_error(_("SMTP authentication requires SASL"));
    mutt_sleep(1);
    r = SMTP_AUTH_UNAVAIL;
#endif
  }

  if (r != SMTP_AUTH_SUCCESS)
    mutt_account_unsetpass(&conn->account);

  if (r == SMTP_AUTH_FAIL)
  {
    mutt_error(_("SASL authentication failed"));
    mutt_sleep(1);
  }
  else if (r == SMTP_AUTH_UNAVAIL)
  {
    mutt_error(_("No authenticators available"));
    mutt_sleep(1);
  }

  return r == SMTP_AUTH_SUCCESS ? 0 : -1;
}

#ifdef USE_SASL_CYRUS
static int smtp_auth_sasl(CONNECTION *conn, const char *mechlist)
{
  sasl_conn_t *saslconn;
  sasl_interact_t *interaction = NULL;
  const char *mech;
  const char *data = NULL;
  unsigned int data_len;
  BUFFER *temp_buf = NULL, *output_buf = NULL, *smtp_response_buf = NULL;
  int rc = SMTP_AUTH_FAIL, sasl_rc, smtp_rc;

  if (mutt_sasl_client_new(conn, &saslconn) < 0)
    return SMTP_AUTH_FAIL;

  do
  {
    sasl_rc = sasl_client_start(saslconn, mechlist, &interaction, &data,
                                &data_len, &mech);
    if (sasl_rc == SASL_INTERACT)
      mutt_sasl_interact(interaction);
  }
  while (sasl_rc == SASL_INTERACT);

  if (sasl_rc != SASL_OK && sasl_rc != SASL_CONTINUE)
  {
    muttdbg(2, "%s unavailable", NONULL(mechlist));
    sasl_dispose(&saslconn);
    return SMTP_AUTH_UNAVAIL;
  }

  if (!option(OPTNOCURSES))
    mutt_message(_("Authenticating (%s)..."), mech);

  temp_buf = mutt_buffer_pool_get();
  output_buf = mutt_buffer_pool_get();
  smtp_response_buf = mutt_buffer_pool_get();

  mutt_buffer_printf(output_buf, "AUTH %s", mech);
  if (data_len)
  {
    mutt_buffer_addch(output_buf, ' ');
    mutt_buffer_to_base64(temp_buf, (const unsigned char *)data, data_len);
    mutt_buffer_addstr(output_buf, mutt_b2s(temp_buf));
  }
  mutt_buffer_addstr(output_buf, "\r\n");

  do
  {
    if (mutt_socket_write(conn, mutt_b2s(output_buf)) < 0)
      goto fail;

    if (smtp_get_auth_response(conn, temp_buf, &smtp_rc, smtp_response_buf) < 0)
      goto fail;

    if (smtp_rc != smtp_ready)
      break;

    if (mutt_buffer_from_base64(temp_buf, mutt_b2s(smtp_response_buf)) < 0)
    {
      muttdbg(1, "error base64-decoding server response.");
      goto fail;
    }

    do
    {
      sasl_rc = sasl_client_step(saslconn, mutt_b2s(temp_buf),
                                 mutt_buffer_len(temp_buf),
                                 &interaction, &data, &data_len);
      if (sasl_rc == SASL_INTERACT)
        mutt_sasl_interact(interaction);
    }
    while (sasl_rc == SASL_INTERACT);

    if (data_len)
      mutt_buffer_to_base64(output_buf, (const unsigned char *)data, data_len);
    else
      mutt_buffer_clear(output_buf);
    mutt_buffer_addstr(output_buf, "\r\n");
  }
  while (sasl_rc != SASL_FAIL);

  if (smtp_success(smtp_rc))
  {
    mutt_sasl_setup_conn(conn, saslconn);
    rc = SMTP_AUTH_SUCCESS;
  }
  else
  {
    if (smtp_rc == smtp_ready)
      mutt_socket_write(conn, "*\r\n");
    sasl_dispose(&saslconn);
  }

fail:
  mutt_buffer_pool_release(&temp_buf);
  mutt_buffer_pool_release(&output_buf);
  mutt_buffer_pool_release(&smtp_response_buf);
  return rc;
}
#endif /* USE_SASL_CYRUS */

#ifdef USE_SASL_GNU
static int smtp_auth_gsasl(CONNECTION *conn, const char *method)
{
  Gsasl_session *gsasl_session = NULL;
  const char *chosen_mech;
  BUFFER *input_buf = NULL, *output_buf = NULL, *smtp_response_buf = NULL;
  char *gsasl_step_output = NULL;
  int rc = SMTP_AUTH_FAIL, gsasl_rc = GSASL_OK, smtp_rc, first_response = 1;

  chosen_mech = mutt_gsasl_get_mech(method, AuthMechs);
  if (!chosen_mech)
  {
    muttdbg(2, "mutt_gsasl_get_mech() returned no usable mech");
    return SMTP_AUTH_UNAVAIL;
  }

  muttdbg(2, "using mech %s", chosen_mech);

  if (mutt_gsasl_client_new(conn, chosen_mech, &gsasl_session) < 0)
  {
    muttdbg(1, "Error allocating GSASL connection.");
    return SMTP_AUTH_UNAVAIL;
  }

  if (!option(OPTNOCURSES))
    mutt_message(_("Authenticating (%s)..."), chosen_mech);

  input_buf = mutt_buffer_pool_get();
  output_buf = mutt_buffer_pool_get();
  smtp_response_buf = mutt_buffer_pool_get();

  mutt_buffer_printf(output_buf, "AUTH %s", chosen_mech);

  /* Work around broken SMTP servers. See Debian #1010658.
   * The msmtp source also forces IR for PLAIN because the author
   * encountered difficulties with a server requiring it.
   */
  if (!mutt_strcmp(chosen_mech, "PLAIN"))
  {
    first_response = 0;
    gsasl_rc = gsasl_step64(gsasl_session, "", &gsasl_step_output);
    if (gsasl_rc != GSASL_NEEDS_MORE && gsasl_rc != GSASL_OK)
    {
      muttdbg(1, "gsasl_step64() failed (%d): %s", gsasl_rc,
              gsasl_strerror(gsasl_rc));
      goto fail;
    }

    mutt_buffer_addch(output_buf, ' ');
    mutt_buffer_addstr(output_buf, gsasl_step_output);
    gsasl_free(gsasl_step_output);
  }

  mutt_buffer_addstr(output_buf, "\r\n");

  do
  {
    if (mutt_socket_write(conn, mutt_b2s(output_buf)) < 0)
      goto fail;

    if (smtp_get_auth_response(conn, input_buf, &smtp_rc, smtp_response_buf) < 0)
      goto fail;

    if (smtp_rc != smtp_ready)
      break;

    /* Another workaround for broken SMTP servers.  Instead of an
     * empty challenge, some MS servers return a meaningless
     * non-BASE64 encoded response in the initial reply, e.g. "334
     * GSSAPI supported".
     */
    if (first_response)
    {
      first_response = 0;
      /* Use input_buf as a temp buffer. We've already processed the input */
      if (mutt_buffer_from_base64(input_buf, mutt_b2s(smtp_response_buf)) < 0)
        mutt_buffer_clear(smtp_response_buf);
    }

    gsasl_rc = gsasl_step64(gsasl_session, mutt_b2s(smtp_response_buf),
                            &gsasl_step_output);
    if (gsasl_rc == GSASL_NEEDS_MORE || gsasl_rc == GSASL_OK)
    {
      mutt_buffer_strcpy(output_buf, gsasl_step_output);
      mutt_buffer_addstr(output_buf, "\r\n");
      gsasl_free(gsasl_step_output);
    }
    else
    {
      muttdbg(1, "gsasl_step64() failed (%d): %s", gsasl_rc,
              gsasl_strerror(gsasl_rc));
    }
  }
  while (gsasl_rc == GSASL_NEEDS_MORE || gsasl_rc == GSASL_OK);

  if (smtp_rc == smtp_ready)
  {
    mutt_socket_write(conn, "*\r\n");
    goto fail;
  }

  if (smtp_success(smtp_rc) && (gsasl_rc == GSASL_OK))
    rc = SMTP_AUTH_SUCCESS;

fail:
  mutt_buffer_pool_release(&input_buf);
  mutt_buffer_pool_release(&output_buf);
  mutt_buffer_pool_release(&smtp_response_buf);
  mutt_gsasl_client_finish(&gsasl_session);

  if (rc == SMTP_AUTH_FAIL)
    muttdbg(2, "%s failed", chosen_mech);

  return rc;
}
#endif


/* smtp_auth_oauth: AUTH=OAUTHBEARER support. See RFC 7628 */
static int smtp_auth_oauth(CONNECTION *conn, int xoauth2)
{
  int rc = SMTP_AUTH_FAIL, smtp_rc;
  BUFFER *bearertoken = NULL, *authline = NULL;
  BUFFER *input_buf = NULL, *smtp_response_buf = NULL;
  const char *authtype;

  authtype = xoauth2 ? "XOAUTH2" : "OAUTHBEARER";

  /* L10N:
     %s is the authentication type, such as XOAUTH2 or OAUTHBEARER
  */
  mutt_message(_("Authenticating (%s)..."), authtype);;

  bearertoken = mutt_buffer_pool_get();
  authline = mutt_buffer_pool_get();
  input_buf = mutt_buffer_pool_get();
  smtp_response_buf = mutt_buffer_pool_get();

  /* We get the access token from the smtp_oauth_refresh_command */
  if (mutt_account_getoauthbearer(&conn->account, bearertoken, xoauth2))
    goto cleanup;

  if (xoauth2)
  {
    mutt_buffer_printf(authline, "AUTH %s\r\n", authtype);
    if (mutt_socket_write(conn, mutt_b2s(authline)) == -1)
      goto cleanup;
    if (smtp_get_auth_response(conn, input_buf, &smtp_rc, smtp_response_buf) < 0)
      goto saslcleanup;
    if (smtp_rc != smtp_ready)
      goto saslcleanup;

    mutt_buffer_printf(authline, "%s\r\n", mutt_b2s(bearertoken));
  }
  else
  {
    mutt_buffer_printf(authline, "AUTH %s %s\r\n", authtype, mutt_b2s(bearertoken));
  }

  if (mutt_socket_write(conn, mutt_b2s(authline)) == -1)
    goto cleanup;
  if (smtp_get_auth_response(conn, input_buf, &smtp_rc, smtp_response_buf) < 0)
    goto saslcleanup;
  if (!smtp_success(smtp_rc))
    goto saslcleanup;

  rc = SMTP_AUTH_SUCCESS;

saslcleanup:
  if (rc != SMTP_AUTH_SUCCESS)
  {
    /* The error response was in SASL continuation, so continue the SASL
     * to cause a failure and exit SASL input.  See RFC 7628 3.2.3
     * "AQ==" is Base64 encoded ^A (0x01) .
     */
    mutt_socket_write(conn, "AQ==\r\n");
    smtp_get_resp(conn);
  }

cleanup:
  mutt_buffer_pool_release(&bearertoken);
  mutt_buffer_pool_release(&authline);
  mutt_buffer_pool_release(&input_buf);
  mutt_buffer_pool_release(&smtp_response_buf);
  return rc;
}
