Coverage Report

Created: 2023-06-07 06:15

/src/neomutt/send/smtp.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Send email to an SMTP server
4
 *
5
 * @authors
6
 * Copyright (C) 2002 Michael R. Elkins <me@mutt.org>
7
 * Copyright (C) 2005-2009 Brendan Cully <brendan@kublai.com>
8
 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
9
 *
10
 * @copyright
11
 * This program is free software: you can redistribute it and/or modify it under
12
 * the terms of the GNU General Public License as published by the Free Software
13
 * Foundation, either version 2 of the License, or (at your option) any later
14
 * version.
15
 *
16
 * This program is distributed in the hope that it will be useful, but WITHOUT
17
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19
 * details.
20
 *
21
 * You should have received a copy of the GNU General Public License along with
22
 * this program.  If not, see <http://www.gnu.org/licenses/>.
23
 */
24
25
/**
26
 * @page send_smtp Send email to an SMTP server
27
 *
28
 * Send email to an SMTP server
29
 */
30
31
/* This file contains code for direct SMTP delivery of email messages. */
32
33
#include "config.h"
34
#include <arpa/inet.h>
35
#include <netdb.h>
36
#include <stdbool.h>
37
#include <stdint.h>
38
#include <stdio.h>
39
#include <string.h>
40
#include <unistd.h>
41
#include "mutt/lib.h"
42
#include "address/lib.h"
43
#include "config/lib.h"
44
#include "email/lib.h"
45
#include "core/lib.h"
46
#include "conn/lib.h"
47
#include "smtp.h"
48
#include "lib.h"
49
#include "progress/lib.h"
50
#include "question/lib.h"
51
#include "globals.h" // IWYU pragma: keep
52
#include "mutt_account.h"
53
#include "mutt_socket.h"
54
#ifdef USE_SASL_GNU
55
#include <gsasl.h>
56
#endif
57
#ifdef USE_SASL_CYRUS
58
#include <sasl/sasl.h>
59
#include <sasl/saslutil.h>
60
#endif
61
62
0
#define smtp_success(x) ((x) / 100 == 2)
63
#define SMTP_READY 334
64
0
#define SMTP_CONTINUE 354
65
66
0
#define SMTP_ERR_READ -2
67
0
#define SMTP_ERR_WRITE -3
68
0
#define SMTP_ERR_CODE -4
69
70
0
#define SMTP_PORT 25
71
0
#define SMTPS_PORT 465
72
73
0
#define SMTP_AUTH_SUCCESS 0
74
0
#define SMTP_AUTH_UNAVAIL 1
75
0
#define SMTP_AUTH_FAIL -1
76
77
// clang-format off
78
/**
79
 * typedef SmtpCapFlags - SMTP server capabilities
80
 */
81
typedef uint8_t SmtpCapFlags;          ///< Flags, e.g. #SMTP_CAP_STARTTLS
82
0
#define SMTP_CAP_NO_FLAGS           0  ///< No flags are set
83
0
#define SMTP_CAP_STARTTLS     (1 << 0) ///< Server supports STARTTLS command
84
0
#define SMTP_CAP_AUTH         (1 << 1) ///< Server supports AUTH command
85
0
#define SMTP_CAP_DSN          (1 << 2) ///< Server supports Delivery Status Notification
86
0
#define SMTP_CAP_EIGHTBITMIME (1 << 3) ///< Server supports 8-bit MIME content
87
0
#define SMTP_CAP_SMTPUTF8     (1 << 4) ///< Server accepts UTF-8 strings
88
#define SMTP_CAP_ALL         ((1 << 5) - 1)
89
// clang-format on
90
91
/**
92
 * struct SmtpAccountData - Server connection data
93
 */
94
struct SmtpAccountData
95
{
96
  const char *auth_mechs;    ///< Allowed authorisation mechanisms
97
  SmtpCapFlags capabilities; ///< Server capabilities
98
  struct Connection *conn;   ///< Server Connection
99
  struct ConfigSubset *sub;  ///< Config scope
100
  const char *fqdn;          ///< Fully-qualified domain name
101
};
102
103
/**
104
 * struct SmtpAuth - SMTP authentication multiplexor
105
 */
106
struct SmtpAuth
107
{
108
  /**
109
   * authenticate - Authenticate an SMTP connection
110
   * @param adata  Smtp Account data
111
   * @param method Use this named method, or any available method if NULL
112
   * @retval num Result, e.g. #SMTP_AUTH_SUCCESS
113
   */
114
  int (*authenticate)(struct SmtpAccountData *adata, const char *method);
115
116
  const char *method; ///< Name of authentication method supported, NULL means variable.
117
      ///< If this is not null, authenticate may ignore the second parameter.
118
};
119
120
/**
121
 * valid_smtp_code - Is the is a valid SMTP return code?
122
 * @param[in]  buf String to check
123
 * @param[in]  buflen Length of string
124
 * @param[out] n   Numeric value of code
125
 * @retval true Valid number
126
 */
127
static bool valid_smtp_code(char *buf, size_t buflen, int *n)
128
0
{
129
0
  return (mutt_str_atoi(buf, n) - buf) <= 3;
130
0
}
131
132
/**
133
 * smtp_get_resp - Read a command response from the SMTP server
134
 * @param adata SMTP Account data
135
 * @retval  0 Success (2xx code) or continue (354 code)
136
 * @retval -1 Write error, or any other response code
137
 */
138
static int smtp_get_resp(struct SmtpAccountData *adata)
139
0
{
140
0
  int n;
141
0
  char buf[1024] = { 0 };
142
143
0
  do
144
0
  {
145
0
    n = mutt_socket_readln(buf, sizeof(buf), adata->conn);
146
0
    if (n < 4)
147
0
    {
148
      /* read error, or no response code */
149
0
      return SMTP_ERR_READ;
150
0
    }
151
0
    const char *s = buf + 4; /* Skip the response code and the space/dash */
152
0
    size_t plen;
153
154
0
    if (mutt_istr_startswith(s, "8BITMIME"))
155
0
    {
156
0
      adata->capabilities |= SMTP_CAP_EIGHTBITMIME;
157
0
    }
158
0
    else if ((plen = mutt_istr_startswith(s, "AUTH ")))
159
0
    {
160
0
      adata->capabilities |= SMTP_CAP_AUTH;
161
0
      FREE(&adata->auth_mechs);
162
0
      adata->auth_mechs = mutt_str_dup(s + plen);
163
0
    }
164
0
    else if (mutt_istr_startswith(s, "DSN"))
165
0
    {
166
0
      adata->capabilities |= SMTP_CAP_DSN;
167
0
    }
168
0
    else if (mutt_istr_startswith(s, "STARTTLS"))
169
0
    {
170
0
      adata->capabilities |= SMTP_CAP_STARTTLS;
171
0
    }
172
0
    else if (mutt_istr_startswith(s, "SMTPUTF8"))
173
0
    {
174
0
      adata->capabilities |= SMTP_CAP_SMTPUTF8;
175
0
    }
176
177
0
    if (!valid_smtp_code(buf, n, &n))
178
0
      return SMTP_ERR_CODE;
179
180
0
  } while (buf[3] == '-');
181
182
0
  if (smtp_success(n) || (n == SMTP_CONTINUE))
183
0
    return 0;
184
185
0
  mutt_error(_("SMTP session failed: %s"), buf);
186
0
  return -1;
187
0
}
188
189
/**
190
 * smtp_rcpt_to - Set the recipient to an Address
191
 * @param adata SMTP Account data
192
 * @param al    AddressList to use
193
 * @retval  0 Success
194
 * @retval <0 Error, e.g. #SMTP_ERR_WRITE
195
 */
196
static int smtp_rcpt_to(struct SmtpAccountData *adata, const struct AddressList *al)
197
0
{
198
0
  if (!al)
199
0
    return 0;
200
201
0
  const char *const c_dsn_notify = cs_subset_string(adata->sub, "dsn_notify");
202
203
0
  struct Address *a = NULL;
204
0
  TAILQ_FOREACH(a, al, entries)
205
0
  {
206
    /* weed out group mailboxes, since those are for display only */
207
0
    if (!a->mailbox || a->group)
208
0
    {
209
0
      continue;
210
0
    }
211
0
    char buf[1024] = { 0 };
212
0
    if ((adata->capabilities & SMTP_CAP_DSN) && c_dsn_notify)
213
0
    {
214
0
      snprintf(buf, sizeof(buf), "RCPT TO:<%s> NOTIFY=%s\r\n",
215
0
               buf_string(a->mailbox), c_dsn_notify);
216
0
    }
217
0
    else
218
0
    {
219
0
      snprintf(buf, sizeof(buf), "RCPT TO:<%s>\r\n", buf_string(a->mailbox));
220
0
    }
221
0
    if (mutt_socket_send(adata->conn, buf) == -1)
222
0
      return SMTP_ERR_WRITE;
223
0
    int rc = smtp_get_resp(adata);
224
0
    if (rc != 0)
225
0
      return rc;
226
0
  }
227
228
0
  return 0;
229
0
}
230
231
/**
232
 * smtp_data - Send data to an SMTP server
233
 * @param adata   SMTP Account data
234
 * @param msgfile Filename containing data
235
 * @retval  0 Success
236
 * @retval <0 Error, e.g. #SMTP_ERR_WRITE
237
 */
238
static int smtp_data(struct SmtpAccountData *adata, const char *msgfile)
239
0
{
240
0
  char buf[1024] = { 0 };
241
0
  struct Progress *progress = NULL;
242
0
  int rc = SMTP_ERR_WRITE;
243
0
  int term = 0;
244
0
  size_t buflen = 0;
245
246
0
  FILE *fp = fopen(msgfile, "r");
247
0
  if (!fp)
248
0
  {
249
0
    mutt_error(_("SMTP session failed: unable to open %s"), msgfile);
250
0
    return -1;
251
0
  }
252
0
  const long size = mutt_file_get_size_fp(fp);
253
0
  if (size == 0)
254
0
  {
255
0
    mutt_file_fclose(&fp);
256
0
    return -1;
257
0
  }
258
0
  unlink(msgfile);
259
0
  progress = progress_new(_("Sending message..."), MUTT_PROGRESS_NET, size);
260
261
0
  snprintf(buf, sizeof(buf), "DATA\r\n");
262
0
  if (mutt_socket_send(adata->conn, buf) == -1)
263
0
  {
264
0
    mutt_file_fclose(&fp);
265
0
    goto done;
266
0
  }
267
0
  rc = smtp_get_resp(adata);
268
0
  if (rc != 0)
269
0
  {
270
0
    mutt_file_fclose(&fp);
271
0
    goto done;
272
0
  }
273
274
0
  rc = SMTP_ERR_WRITE;
275
0
  while (fgets(buf, sizeof(buf) - 1, fp))
276
0
  {
277
0
    buflen = mutt_str_len(buf);
278
0
    term = buflen && buf[buflen - 1] == '\n';
279
0
    if (term && ((buflen == 1) || (buf[buflen - 2] != '\r')))
280
0
      snprintf(buf + buflen - 1, sizeof(buf) - buflen + 1, "\r\n");
281
0
    if (buf[0] == '.')
282
0
    {
283
0
      if (mutt_socket_send_d(adata->conn, ".", MUTT_SOCK_LOG_FULL) == -1)
284
0
      {
285
0
        mutt_file_fclose(&fp);
286
0
        goto done;
287
0
      }
288
0
    }
289
0
    if (mutt_socket_send_d(adata->conn, buf, MUTT_SOCK_LOG_FULL) == -1)
290
0
    {
291
0
      mutt_file_fclose(&fp);
292
0
      goto done;
293
0
    }
294
0
    progress_update(progress, MAX(0, ftell(fp)), -1);
295
0
  }
296
0
  if (!term && buflen &&
297
0
      (mutt_socket_send_d(adata->conn, "\r\n", MUTT_SOCK_LOG_FULL) == -1))
298
0
  {
299
0
    mutt_file_fclose(&fp);
300
0
    goto done;
301
0
  }
302
0
  mutt_file_fclose(&fp);
303
304
  /* terminate the message body */
305
0
  if (mutt_socket_send(adata->conn, ".\r\n") == -1)
306
0
    goto done;
307
308
0
  rc = smtp_get_resp(adata);
309
310
0
done:
311
0
  progress_free(&progress);
312
0
  return rc;
313
0
}
314
315
/**
316
 * smtp_get_field - Get connection login credentials - Implements ConnAccount::get_field()
317
 */
318
static const char *smtp_get_field(enum ConnAccountField field, void *gf_data)
319
0
{
320
0
  struct SmtpAccountData *adata = gf_data;
321
0
  if (!adata)
322
0
    return NULL;
323
324
0
  switch (field)
325
0
  {
326
0
    case MUTT_CA_LOGIN:
327
0
    case MUTT_CA_USER:
328
0
    {
329
0
      const char *const c_smtp_user = cs_subset_string(adata->sub, "smtp_user");
330
0
      return c_smtp_user;
331
0
    }
332
0
    case MUTT_CA_PASS:
333
0
    {
334
0
      const char *const c_smtp_pass = cs_subset_string(adata->sub, "smtp_pass");
335
0
      return c_smtp_pass;
336
0
    }
337
0
    case MUTT_CA_OAUTH_CMD:
338
0
    {
339
0
      const char *const c_smtp_oauth_refresh_command = cs_subset_string(adata->sub, "smtp_oauth_refresh_command");
340
0
      return c_smtp_oauth_refresh_command;
341
0
    }
342
0
    case MUTT_CA_HOST:
343
0
    default:
344
0
      return NULL;
345
0
  }
346
0
}
347
348
/**
349
 * smtp_fill_account - Create ConnAccount object from SMTP Url
350
 * @param adata SMTP Account data
351
 * @param cac ConnAccount to populate
352
 * @retval  0 Success
353
 * @retval -1 Error
354
 */
355
static int smtp_fill_account(struct SmtpAccountData *adata, struct ConnAccount *cac)
356
0
{
357
0
  cac->flags = 0;
358
0
  cac->port = 0;
359
0
  cac->type = MUTT_ACCT_TYPE_SMTP;
360
0
  cac->service = "smtp";
361
0
  cac->get_field = smtp_get_field;
362
0
  cac->gf_data = adata;
363
364
0
  const char *const c_smtp_url = cs_subset_string(adata->sub, "smtp_url");
365
366
0
  struct Url *url = url_parse(c_smtp_url);
367
0
  if (!url || ((url->scheme != U_SMTP) && (url->scheme != U_SMTPS)) ||
368
0
      !url->host || (mutt_account_fromurl(cac, url) < 0))
369
0
  {
370
0
    url_free(&url);
371
0
    mutt_error(_("Invalid SMTP URL: %s"), c_smtp_url);
372
0
    return -1;
373
0
  }
374
375
0
  if (url->scheme == U_SMTPS)
376
0
    cac->flags |= MUTT_ACCT_SSL;
377
378
0
  if (cac->port == 0)
379
0
  {
380
0
    if (cac->flags & MUTT_ACCT_SSL)
381
0
    {
382
0
      cac->port = SMTPS_PORT;
383
0
    }
384
0
    else
385
0
    {
386
0
      static unsigned short SmtpPort = 0;
387
0
      if (SmtpPort == 0)
388
0
      {
389
0
        struct servent *service = getservbyname("smtp", "tcp");
390
0
        if (service)
391
0
          SmtpPort = ntohs(service->s_port);
392
0
        else
393
0
          SmtpPort = SMTP_PORT;
394
0
        mutt_debug(LL_DEBUG3, "Using default SMTP port %d\n", SmtpPort);
395
0
      }
396
0
      cac->port = SmtpPort;
397
0
    }
398
0
  }
399
400
0
  url_free(&url);
401
0
  return 0;
402
0
}
403
404
/**
405
 * smtp_helo - Say hello to an SMTP Server
406
 * @param adata SMTP Account data
407
 * @param esmtp If true, use ESMTP
408
 * @retval  0 Success
409
 * @retval <0 Error, e.g. #SMTP_ERR_WRITE
410
 */
411
static int smtp_helo(struct SmtpAccountData *adata, bool esmtp)
412
0
{
413
0
  adata->capabilities = SMTP_CAP_NO_FLAGS;
414
415
0
  if (!esmtp)
416
0
  {
417
    /* if TLS or AUTH are requested, use EHLO */
418
0
    if (adata->conn->account.flags & MUTT_ACCT_USER)
419
0
      esmtp = true;
420
#ifdef USE_SSL
421
    const bool c_ssl_force_tls = cs_subset_bool(adata->sub, "ssl_force_tls");
422
    const enum QuadOption c_ssl_starttls = cs_subset_quad(adata->sub, "ssl_starttls");
423
424
    if (c_ssl_force_tls || (c_ssl_starttls != MUTT_NO))
425
      esmtp = true;
426
#endif
427
0
  }
428
429
0
  char buf[1024] = { 0 };
430
0
  snprintf(buf, sizeof(buf), "%s %s\r\n", esmtp ? "EHLO" : "HELO", adata->fqdn);
431
  /* XXX there should probably be a wrapper in mutt_socket.c that
432
   * repeatedly calls adata->conn->write until all data is sent.  This
433
   * currently doesn't check for a short write.  */
434
0
  if (mutt_socket_send(adata->conn, buf) == -1)
435
0
    return SMTP_ERR_WRITE;
436
0
  return smtp_get_resp(adata);
437
0
}
438
439
#ifdef USE_SASL_GNU
440
/**
441
 * smtp_code - Extract an SMTP return code from a string
442
 * @param[in]  str String to parse
443
 * @param[in]  len Length of string
444
 * @param[out] n   SMTP return code result
445
 * @retval true Success
446
 *
447
 * Note: the 'len' parameter is actually the number of bytes, as
448
 * returned by mutt_socket_readln().  If all callers are converted to
449
 * mutt_socket_buffer_readln() we can pass in the actual len, or
450
 * perhaps the buffer itself.
451
 */
452
static int smtp_code(const char *str, size_t len, int *n)
453
{
454
  char code[4];
455
456
  if (len < 4)
457
    return false;
458
  code[0] = str[0];
459
  code[1] = str[1];
460
  code[2] = str[2];
461
  code[3] = 0;
462
463
  const char *end = mutt_str_atoi(code, n);
464
  if (!end || (*end != '\0'))
465
    return false;
466
  return true;
467
}
468
469
/**
470
 * smtp_get_auth_response - Get the SMTP authorisation response
471
 * @param[in]  conn         Connection to a server
472
 * @param[in]  input_buf    Temp buffer for strings returned by the server
473
 * @param[out] smtp_rc      SMTP return code result
474
 * @param[out] response_buf Text after the SMTP response code
475
 * @retval  0 Success
476
 * @retval -1 Error
477
 *
478
 * Did the SMTP authorisation succeed?
479
 */
480
static int smtp_get_auth_response(struct Connection *conn, struct Buffer *input_buf,
481
                                  int *smtp_rc, struct Buffer *response_buf)
482
{
483
  buf_reset(response_buf);
484
  do
485
  {
486
    if (mutt_socket_buffer_readln(input_buf, conn) < 0)
487
      return -1;
488
    if (!smtp_code(buf_string(input_buf), buf_len(input_buf) + 1 /* number of bytes */, smtp_rc))
489
    {
490
      return -1;
491
    }
492
493
    if (*smtp_rc != SMTP_READY)
494
      break;
495
496
    const char *smtp_response = buf_string(input_buf) + 3;
497
    if (*smtp_response)
498
    {
499
      smtp_response++;
500
      buf_addstr(response_buf, smtp_response);
501
    }
502
  } while (buf_string(input_buf)[3] == '-');
503
504
  return 0;
505
}
506
507
/**
508
 * smtp_auth_gsasl - Authenticate using SASL
509
 * @param adata    SMTP Account data
510
 * @param mechlist List of mechanisms to use
511
 * @retval  0 Success
512
 * @retval <0 Error, e.g. #SMTP_AUTH_FAIL
513
 */
514
static int smtp_auth_gsasl(struct SmtpAccountData *adata, const char *mechlist)
515
{
516
  Gsasl_session *gsasl_session = NULL;
517
  struct Buffer *input_buf = NULL, *output_buf = NULL, *smtp_response_buf = NULL;
518
  int rc = SMTP_AUTH_FAIL, gsasl_rc = GSASL_OK, smtp_rc;
519
520
  const char *chosen_mech = mutt_gsasl_get_mech(mechlist, adata->auth_mechs);
521
  if (!chosen_mech)
522
  {
523
    mutt_debug(LL_DEBUG2, "returned no usable mech\n");
524
    return SMTP_AUTH_UNAVAIL;
525
  }
526
527
  mutt_debug(LL_DEBUG2, "using mech %s\n", chosen_mech);
528
529
  if (mutt_gsasl_client_new(adata->conn, chosen_mech, &gsasl_session) < 0)
530
  {
531
    mutt_debug(LL_DEBUG1, "Error allocating GSASL connection.\n");
532
    return SMTP_AUTH_UNAVAIL;
533
  }
534
535
  if (!OptNoCurses)
536
    mutt_message(_("Authenticating (%s)..."), chosen_mech);
537
538
  input_buf = buf_pool_get();
539
  output_buf = buf_pool_get();
540
  smtp_response_buf = buf_pool_get();
541
542
  buf_printf(output_buf, "AUTH %s", chosen_mech);
543
544
  /* Work around broken SMTP servers. See Debian #1010658.
545
   * The msmtp source also forces IR for PLAIN because the author
546
   * encountered difficulties with a server requiring it. */
547
  if (mutt_str_equal(chosen_mech, "PLAIN"))
548
  {
549
    char *gsasl_step_output = NULL;
550
    gsasl_rc = gsasl_step64(gsasl_session, "", &gsasl_step_output);
551
    if (gsasl_rc != GSASL_NEEDS_MORE && gsasl_rc != GSASL_OK)
552
    {
553
      mutt_debug(LL_DEBUG1, "gsasl_step64() failed (%d): %s\n", gsasl_rc,
554
                 gsasl_strerror(gsasl_rc));
555
      goto fail;
556
    }
557
558
    buf_addch(output_buf, ' ');
559
    buf_addstr(output_buf, gsasl_step_output);
560
    gsasl_free(gsasl_step_output);
561
  }
562
563
  buf_addstr(output_buf, "\r\n");
564
565
  do
566
  {
567
    if (mutt_socket_send(adata->conn, buf_string(output_buf)) < 0)
568
      goto fail;
569
570
    if (smtp_get_auth_response(adata->conn, input_buf, &smtp_rc, smtp_response_buf) < 0)
571
      goto fail;
572
573
    if (smtp_rc != SMTP_READY)
574
      break;
575
576
    char *gsasl_step_output = NULL;
577
    gsasl_rc = gsasl_step64(gsasl_session, buf_string(smtp_response_buf), &gsasl_step_output);
578
    if ((gsasl_rc == GSASL_NEEDS_MORE) || (gsasl_rc == GSASL_OK))
579
    {
580
      buf_strcpy(output_buf, gsasl_step_output);
581
      buf_addstr(output_buf, "\r\n");
582
      gsasl_free(gsasl_step_output);
583
    }
584
    else
585
    {
586
      mutt_debug(LL_DEBUG1, "gsasl_step64() failed (%d): %s\n", gsasl_rc,
587
                 gsasl_strerror(gsasl_rc));
588
    }
589
  } while ((gsasl_rc == GSASL_NEEDS_MORE) || (gsasl_rc == GSASL_OK));
590
591
  if (smtp_rc == SMTP_READY)
592
  {
593
    mutt_socket_send(adata->conn, "*\r\n");
594
    goto fail;
595
  }
596
597
  if (smtp_success(smtp_rc) && (gsasl_rc == GSASL_OK))
598
    rc = SMTP_AUTH_SUCCESS;
599
600
fail:
601
  buf_pool_release(&input_buf);
602
  buf_pool_release(&output_buf);
603
  buf_pool_release(&smtp_response_buf);
604
  mutt_gsasl_client_finish(&gsasl_session);
605
606
  if (rc == SMTP_AUTH_FAIL)
607
    mutt_debug(LL_DEBUG2, "%s failed\n", chosen_mech);
608
609
  return rc;
610
}
611
#endif
612
613
#ifdef USE_SASL_CYRUS
614
/**
615
 * smtp_auth_sasl - Authenticate using SASL
616
 * @param adata    SMTP Account data
617
 * @param mechlist List of mechanisms to use
618
 * @retval  0 Success
619
 * @retval <0 Error, e.g. #SMTP_AUTH_FAIL
620
 */
621
static int smtp_auth_sasl(struct SmtpAccountData *adata, const char *mechlist)
622
{
623
  sasl_conn_t *saslconn = NULL;
624
  sasl_interact_t *interaction = NULL;
625
  const char *mech = NULL;
626
  const char *data = NULL;
627
  unsigned int len;
628
  char *buf = NULL;
629
  size_t bufsize = 0;
630
  int rc, saslrc;
631
632
  if (mutt_sasl_client_new(adata->conn, &saslconn) < 0)
633
    return SMTP_AUTH_FAIL;
634
635
  do
636
  {
637
    rc = sasl_client_start(saslconn, mechlist, &interaction, &data, &len, &mech);
638
    if (rc == SASL_INTERACT)
639
      mutt_sasl_interact(interaction);
640
  } while (rc == SASL_INTERACT);
641
642
  if ((rc != SASL_OK) && (rc != SASL_CONTINUE))
643
  {
644
    mutt_debug(LL_DEBUG2, "%s unavailable\n", NONULL(mech));
645
    sasl_dispose(&saslconn);
646
    return SMTP_AUTH_UNAVAIL;
647
  }
648
649
  if (!OptNoCurses)
650
  {
651
    // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
652
    mutt_message(_("Authenticating (%s)..."), mech);
653
  }
654
655
  bufsize = MAX((len * 2), 1024);
656
  buf = mutt_mem_malloc(bufsize);
657
658
  snprintf(buf, bufsize, "AUTH %s", mech);
659
  if (len)
660
  {
661
    mutt_str_cat(buf, bufsize, " ");
662
    if (sasl_encode64(data, len, buf + mutt_str_len(buf),
663
                      bufsize - mutt_str_len(buf), &len) != SASL_OK)
664
    {
665
      mutt_debug(LL_DEBUG1, "#1 error base64-encoding client response\n");
666
      goto fail;
667
    }
668
  }
669
  mutt_str_cat(buf, bufsize, "\r\n");
670
671
  do
672
  {
673
    if (mutt_socket_send(adata->conn, buf) < 0)
674
      goto fail;
675
    rc = mutt_socket_readln_d(buf, bufsize, adata->conn, MUTT_SOCK_LOG_FULL);
676
    if (rc < 0)
677
      goto fail;
678
    if (!valid_smtp_code(buf, rc, &rc))
679
      goto fail;
680
681
    if (rc != SMTP_READY)
682
      break;
683
684
    if (sasl_decode64(buf + 4, strlen(buf + 4), buf, bufsize - 1, &len) != SASL_OK)
685
    {
686
      mutt_debug(LL_DEBUG1, "error base64-decoding server response\n");
687
      goto fail;
688
    }
689
690
    do
691
    {
692
      saslrc = sasl_client_step(saslconn, buf, len, &interaction, &data, &len);
693
      if (saslrc == SASL_INTERACT)
694
        mutt_sasl_interact(interaction);
695
    } while (saslrc == SASL_INTERACT);
696
697
    if (len)
698
    {
699
      if ((len * 2) > bufsize)
700
      {
701
        bufsize = len * 2;
702
        mutt_mem_realloc(&buf, bufsize);
703
      }
704
      if (sasl_encode64(data, len, buf, bufsize, &len) != SASL_OK)
705
      {
706
        mutt_debug(LL_DEBUG1, "#2 error base64-encoding client response\n");
707
        goto fail;
708
      }
709
    }
710
    mutt_str_copy(buf + len, "\r\n", bufsize - len);
711
  } while (rc == SMTP_READY && saslrc != SASL_FAIL);
712
713
  if (smtp_success(rc))
714
  {
715
    mutt_sasl_setup_conn(adata->conn, saslconn);
716
    FREE(&buf);
717
    return SMTP_AUTH_SUCCESS;
718
  }
719
720
fail:
721
  sasl_dispose(&saslconn);
722
  FREE(&buf);
723
  return SMTP_AUTH_FAIL;
724
}
725
#endif
726
727
/**
728
 * smtp_auth_oauth_xoauth2 - Authenticate an SMTP connection using OAUTHBEARER/XOAUTH2
729
 * @param adata   SMTP Account data
730
 * @param method  Authentication method
731
 * @param xoauth2 Use XOAUTH2 token (if true), OAUTHBEARER token otherwise
732
 * @retval num Result, e.g. #SMTP_AUTH_SUCCESS
733
 */
734
static int smtp_auth_oauth_xoauth2(struct SmtpAccountData *adata, const char *method, bool xoauth2)
735
0
{
736
  /* If they did not explicitly request or configure oauth then fail quietly */
737
0
  const char *const c_smtp_oauth_refresh_command = cs_subset_string(NeoMutt->sub, "smtp_oauth_refresh_command");
738
0
  if (!method && !c_smtp_oauth_refresh_command)
739
0
    return SMTP_AUTH_UNAVAIL;
740
741
0
  const char *authtype = xoauth2 ? "XOAUTH2" : "OAUTHBEARER";
742
743
  // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
744
0
  mutt_message(_("Authenticating (%s)..."), authtype);
745
746
  /* We get the access token from the smtp_oauth_refresh_command */
747
0
  char *oauthbearer = mutt_account_getoauthbearer(&adata->conn->account, xoauth2);
748
0
  if (!oauthbearer)
749
0
    return SMTP_AUTH_FAIL;
750
751
0
  size_t ilen = strlen(oauthbearer) + 30;
752
0
  char *ibuf = mutt_mem_malloc(ilen);
753
0
  snprintf(ibuf, ilen, "AUTH %s %s\r\n", authtype, oauthbearer);
754
755
0
  int rc = mutt_socket_send(adata->conn, ibuf);
756
0
  FREE(&oauthbearer);
757
0
  FREE(&ibuf);
758
759
0
  if (rc == -1)
760
0
    return SMTP_AUTH_FAIL;
761
0
  if (smtp_get_resp(adata) != 0)
762
0
    return SMTP_AUTH_FAIL;
763
764
0
  return SMTP_AUTH_SUCCESS;
765
0
}
766
767
/**
768
 * smtp_auth_oauth - Authenticate an SMTP connection using OAUTHBEARER
769
 * @param adata   SMTP Account data
770
 * @param method  Authentication method (not used)
771
 * @retval num Result, e.g. #SMTP_AUTH_SUCCESS
772
 */
773
static int smtp_auth_oauth(struct SmtpAccountData *adata, const char *method)
774
0
{
775
0
  return smtp_auth_oauth_xoauth2(adata, method, false);
776
0
}
777
778
/**
779
 * smtp_auth_xoauth2 - Authenticate an SMTP connection using XOAUTH2
780
 * @param adata   SMTP Account data
781
 * @param method  Authentication method (not used)
782
 * @retval num Result, e.g. #SMTP_AUTH_SUCCESS
783
 */
784
static int smtp_auth_xoauth2(struct SmtpAccountData *adata, const char *method)
785
0
{
786
0
  return smtp_auth_oauth_xoauth2(adata, method, true);
787
0
}
788
789
/**
790
 * smtp_auth_plain - Authenticate using plain text
791
 * @param adata SMTP Account data
792
 * @param method     Authentication method (not used)
793
 * @retval  0 Success
794
 * @retval <0 Error, e.g. #SMTP_AUTH_FAIL
795
 *
796
 * @note method is "PLAIN"
797
 */
798
static int smtp_auth_plain(struct SmtpAccountData *adata, const char *method)
799
0
{
800
0
  char buf[1024] = { 0 };
801
802
  /* Get username and password. Bail out of any can't be retrieved. */
803
0
  if ((mutt_account_getuser(&adata->conn->account) < 0) ||
804
0
      (mutt_account_getpass(&adata->conn->account) < 0))
805
0
  {
806
0
    goto error;
807
0
  }
808
809
  /* Build the initial client response. */
810
0
  size_t len = mutt_sasl_plain_msg(buf, sizeof(buf), "AUTH PLAIN",
811
0
                                   adata->conn->account.user,
812
0
                                   adata->conn->account.user,
813
0
                                   adata->conn->account.pass);
814
815
  /* Terminate as per SMTP protocol. Bail out if there's no room left. */
816
0
  if (snprintf(buf + len, sizeof(buf) - len, "\r\n") != 2)
817
0
  {
818
0
    goto error;
819
0
  }
820
821
  /* Send request, receive response (with a check for OK code). */
822
0
  if ((mutt_socket_send(adata->conn, buf) < 0) || smtp_get_resp(adata))
823
0
  {
824
0
    goto error;
825
0
  }
826
827
  /* If we got here, auth was successful. */
828
0
  return 0;
829
830
0
error:
831
  // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
832
0
  mutt_error(_("%s authentication failed"), "SASL");
833
0
  return -1;
834
0
}
835
836
/**
837
 * smtp_auth_login - Authenticate using plain text
838
 * @param adata  SMTP Account data
839
 * @param method Authentication method (not used)
840
 * @retval  0 Success
841
 * @retval <0 Error, e.g. #SMTP_AUTH_FAIL
842
 *
843
 * @note method is "LOGIN"
844
 */
845
static int smtp_auth_login(struct SmtpAccountData *adata, const char *method)
846
0
{
847
0
  char b64[1024] = { 0 };
848
0
  char buf[1026] = { 0 };
849
850
  /* Get username and password. Bail out of any can't be retrieved. */
851
0
  if ((mutt_account_getuser(&adata->conn->account) < 0) ||
852
0
      (mutt_account_getpass(&adata->conn->account) < 0))
853
0
  {
854
0
    goto error;
855
0
  }
856
857
  /* Send the AUTH LOGIN request. */
858
0
  if (mutt_socket_send(adata->conn, "AUTH LOGIN\r\n") < 0)
859
0
  {
860
0
    goto error;
861
0
  }
862
863
  /* Read the 334 VXNlcm5hbWU6 challenge ("Username:" base64-encoded) */
864
0
  mutt_socket_readln_d(buf, sizeof(buf), adata->conn, MUTT_SOCK_LOG_FULL);
865
0
  if (!mutt_str_equal(buf, "334 VXNlcm5hbWU6"))
866
0
  {
867
0
    goto error;
868
0
  }
869
870
  /* Send the username */
871
0
  size_t len = snprintf(buf, sizeof(buf), "%s", adata->conn->account.user);
872
0
  mutt_b64_encode(buf, len, b64, sizeof(b64));
873
0
  snprintf(buf, sizeof(buf), "%s\r\n", b64);
874
0
  if (mutt_socket_send(adata->conn, buf) < 0)
875
0
  {
876
0
    goto error;
877
0
  }
878
879
  /* Read the 334 UGFzc3dvcmQ6 challenge ("Password:" base64-encoded) */
880
0
  mutt_socket_readln_d(buf, sizeof(buf), adata->conn, MUTT_SOCK_LOG_FULL);
881
0
  if (!mutt_str_equal(buf, "334 UGFzc3dvcmQ6"))
882
0
  {
883
0
    goto error;
884
0
  }
885
886
  /* Send the password */
887
0
  len = snprintf(buf, sizeof(buf), "%s", adata->conn->account.pass);
888
0
  mutt_b64_encode(buf, len, b64, sizeof(b64));
889
0
  snprintf(buf, sizeof(buf), "%s\r\n", b64);
890
0
  if (mutt_socket_send(adata->conn, buf) < 0)
891
0
  {
892
0
    goto error;
893
0
  }
894
895
  /* Check the final response */
896
0
  if (smtp_get_resp(adata) < 0)
897
0
  {
898
0
    goto error;
899
0
  }
900
901
  /* If we got here, auth was successful. */
902
0
  return 0;
903
904
0
error:
905
  // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
906
0
  mutt_error(_("%s authentication failed"), "LOGIN");
907
0
  return -1;
908
0
}
909
910
/**
911
 * SmtpAuthenticators - Accepted authentication methods
912
 */
913
static const struct SmtpAuth SmtpAuthenticators[] = {
914
  // clang-format off
915
  { smtp_auth_oauth, "oauthbearer" },
916
  { smtp_auth_xoauth2, "xoauth2" },
917
  { smtp_auth_plain, "plain" },
918
  { smtp_auth_login, "login" },
919
#ifdef USE_SASL_CYRUS
920
  { smtp_auth_sasl, NULL },
921
#endif
922
#ifdef USE_SASL_GNU
923
  { smtp_auth_gsasl, NULL },
924
#endif
925
  // clang-format on
926
};
927
928
/**
929
 * smtp_auth_is_valid - Check if string is a valid smtp authentication method
930
 * @param authenticator Authenticator string to check
931
 * @retval true Argument is a valid auth method
932
 *
933
 * Validate whether an input string is an accepted smtp authentication method as
934
 * defined by #SmtpAuthenticators.
935
 */
936
bool smtp_auth_is_valid(const char *authenticator)
937
0
{
938
0
  for (size_t i = 0; i < mutt_array_size(SmtpAuthenticators); i++)
939
0
  {
940
0
    const struct SmtpAuth *auth = &SmtpAuthenticators[i];
941
0
    if (auth->method && mutt_istr_equal(auth->method, authenticator))
942
0
      return true;
943
0
  }
944
945
0
  return false;
946
0
}
947
948
/**
949
 * smtp_authenticate - Authenticate to an SMTP server
950
 * @param adata SMTP Account data
951
 * @retval  0 Success
952
 * @retval <0 Error, e.g. #SMTP_AUTH_FAIL
953
 */
954
static int smtp_authenticate(struct SmtpAccountData *adata)
955
0
{
956
0
  int r = SMTP_AUTH_UNAVAIL;
957
958
0
  const struct Slist *c_smtp_authenticators = cs_subset_slist(adata->sub, "smtp_authenticators");
959
0
  if (c_smtp_authenticators && (c_smtp_authenticators->count > 0))
960
0
  {
961
0
    mutt_debug(LL_DEBUG2, "Trying user-defined smtp_authenticators\n");
962
963
    /* Try user-specified list of authentication methods */
964
0
    struct ListNode *np = NULL;
965
0
    STAILQ_FOREACH(np, &c_smtp_authenticators->head, entries)
966
0
    {
967
0
      mutt_debug(LL_DEBUG2, "Trying method %s\n", np->data);
968
969
0
      for (size_t i = 0; i < mutt_array_size(SmtpAuthenticators); i++)
970
0
      {
971
0
        const struct SmtpAuth *auth = &SmtpAuthenticators[i];
972
0
        if (!auth->method || mutt_istr_equal(auth->method, np->data))
973
0
        {
974
0
          r = auth->authenticate(adata, np->data);
975
0
          if (r == SMTP_AUTH_SUCCESS)
976
0
            return r;
977
0
        }
978
0
      }
979
0
    }
980
0
  }
981
0
  else
982
0
  {
983
    /* Fall back to default: any authenticator */
984
#if defined(USE_SASL_CYRUS)
985
    mutt_debug(LL_DEBUG2, "Falling back to smtp_auth_sasl, if using sasl.\n");
986
    r = smtp_auth_sasl(adata, adata->auth_mechs);
987
#elif defined(USE_SASL_GNU)
988
    mutt_debug(LL_DEBUG2, "Falling back to smtp_auth_gsasl, if using gsasl.\n");
989
    r = smtp_auth_gsasl(adata, adata->auth_mechs);
990
#else
991
0
    mutt_debug(LL_DEBUG2, "Falling back to using any authenticator available.\n");
992
    /* Try all available authentication methods */
993
0
    for (size_t i = 0; i < mutt_array_size(SmtpAuthenticators); i++)
994
0
    {
995
0
      const struct SmtpAuth *auth = &SmtpAuthenticators[i];
996
0
      mutt_debug(LL_DEBUG2, "Trying method %s\n", auth->method ? auth->method : "<variable>");
997
0
      r = auth->authenticate(adata, auth->method);
998
0
      if (r == SMTP_AUTH_SUCCESS)
999
0
        return r;
1000
0
    }
1001
0
#endif
1002
0
  }
1003
1004
0
  if (r != SMTP_AUTH_SUCCESS)
1005
0
    mutt_account_unsetpass(&adata->conn->account);
1006
1007
0
  if (r == SMTP_AUTH_FAIL)
1008
0
  {
1009
    // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
1010
0
    mutt_error(_("%s authentication failed"), "SASL");
1011
0
  }
1012
0
  else if (r == SMTP_AUTH_UNAVAIL)
1013
0
  {
1014
0
    mutt_error(_("No authenticators available"));
1015
0
  }
1016
1017
0
  return (r == SMTP_AUTH_SUCCESS) ? 0 : -1;
1018
0
}
1019
1020
/**
1021
 * smtp_open - Open an SMTP Connection
1022
 * @param adata SMTP Account data
1023
 * @param esmtp If true, use ESMTP
1024
 * @retval  0 Success
1025
 * @retval -1 Error
1026
 */
1027
static int smtp_open(struct SmtpAccountData *adata, bool esmtp)
1028
0
{
1029
0
  int rc;
1030
1031
0
  if (mutt_socket_open(adata->conn))
1032
0
    return -1;
1033
1034
0
  const bool force_auth = cs_subset_string(adata->sub, "smtp_user");
1035
0
  esmtp |= force_auth;
1036
1037
  /* get greeting string */
1038
0
  rc = smtp_get_resp(adata);
1039
0
  if (rc != 0)
1040
0
    return rc;
1041
1042
0
  rc = smtp_helo(adata, esmtp);
1043
0
  if (rc != 0)
1044
0
    return rc;
1045
1046
#ifdef USE_SSL
1047
  const bool c_ssl_force_tls = cs_subset_bool(adata->sub, "ssl_force_tls");
1048
  const enum QuadOption c_ssl_starttls = cs_subset_quad(adata->sub, "ssl_starttls");
1049
  enum QuadOption ans = MUTT_NO;
1050
  if (adata->conn->ssf != 0)
1051
    ans = MUTT_NO;
1052
  else if (c_ssl_force_tls)
1053
    ans = MUTT_YES;
1054
  else if ((adata->capabilities & SMTP_CAP_STARTTLS) &&
1055
           ((ans = query_quadoption(c_ssl_starttls, _("Secure connection with TLS?"))) == MUTT_ABORT))
1056
  {
1057
    return -1;
1058
  }
1059
1060
  if (ans == MUTT_YES)
1061
  {
1062
    if (mutt_socket_send(adata->conn, "STARTTLS\r\n") < 0)
1063
      return SMTP_ERR_WRITE;
1064
    rc = smtp_get_resp(adata);
1065
    // Clear any data after the STARTTLS acknowledgement
1066
    mutt_socket_empty(adata->conn);
1067
    if (rc != 0)
1068
      return rc;
1069
1070
    if (mutt_ssl_starttls(adata->conn))
1071
    {
1072
      mutt_error(_("Could not negotiate TLS connection"));
1073
      return -1;
1074
    }
1075
1076
    /* re-EHLO to get authentication mechanisms */
1077
    rc = smtp_helo(adata, esmtp);
1078
    if (rc != 0)
1079
      return rc;
1080
  }
1081
#endif
1082
1083
0
  if (force_auth || adata->conn->account.flags & MUTT_ACCT_USER)
1084
0
  {
1085
0
    if (!(adata->capabilities & SMTP_CAP_AUTH))
1086
0
    {
1087
0
      mutt_error(_("SMTP server does not support authentication"));
1088
0
      return -1;
1089
0
    }
1090
1091
0
    return smtp_authenticate(adata);
1092
0
  }
1093
1094
0
  return 0;
1095
0
}
1096
1097
/**
1098
 * mutt_smtp_send - Send a message using SMTP
1099
 * @param from     From Address
1100
 * @param to       To Address
1101
 * @param cc       Cc Address
1102
 * @param bcc      Bcc Address
1103
 * @param msgfile  Message to send to the server
1104
 * @param eightbit If true, try for an 8-bit friendly connection
1105
 * @param sub      Config Subset
1106
 * @retval  0 Success
1107
 * @retval -1 Error
1108
 */
1109
int mutt_smtp_send(const struct AddressList *from, const struct AddressList *to,
1110
                   const struct AddressList *cc, const struct AddressList *bcc,
1111
                   const char *msgfile, bool eightbit, struct ConfigSubset *sub)
1112
0
{
1113
0
  struct SmtpAccountData adata = { 0 };
1114
0
  struct ConnAccount cac = { { 0 } };
1115
0
  const char *envfrom = NULL;
1116
0
  char buf[1024] = { 0 };
1117
0
  int rc = -1;
1118
1119
0
  adata.sub = sub;
1120
0
  adata.fqdn = mutt_fqdn(false, adata.sub);
1121
0
  if (!adata.fqdn)
1122
0
    adata.fqdn = NONULL(ShortHostname);
1123
1124
0
  const struct Address *c_envelope_from_address = cs_subset_address(adata.sub, "envelope_from_address");
1125
1126
  /* it might be better to synthesize an envelope from from user and host
1127
   * but this condition is most likely arrived at accidentally */
1128
0
  if (c_envelope_from_address)
1129
0
  {
1130
0
    envfrom = buf_string(c_envelope_from_address->mailbox);
1131
0
  }
1132
0
  else if (from && !TAILQ_EMPTY(from))
1133
0
  {
1134
0
    envfrom = buf_string(TAILQ_FIRST(from)->mailbox);
1135
0
  }
1136
0
  else
1137
0
  {
1138
0
    mutt_error(_("No from address given"));
1139
0
    return -1;
1140
0
  }
1141
1142
0
  if (smtp_fill_account(&adata, &cac) < 0)
1143
0
    return rc;
1144
1145
0
  adata.conn = mutt_conn_find(&cac);
1146
0
  if (!adata.conn)
1147
0
    return -1;
1148
1149
0
  const char *const c_dsn_return = cs_subset_string(adata.sub, "dsn_return");
1150
1151
0
  do
1152
0
  {
1153
    /* send our greeting */
1154
0
    rc = smtp_open(&adata, eightbit);
1155
0
    if (rc != 0)
1156
0
      break;
1157
0
    FREE(&adata.auth_mechs);
1158
1159
    /* send the sender's address */
1160
0
    int len = snprintf(buf, sizeof(buf), "MAIL FROM:<%s>", envfrom);
1161
0
    if (eightbit && (adata.capabilities & SMTP_CAP_EIGHTBITMIME))
1162
0
    {
1163
0
      mutt_strn_cat(buf, sizeof(buf), " BODY=8BITMIME", 15);
1164
0
      len += 14;
1165
0
    }
1166
0
    if (c_dsn_return && (adata.capabilities & SMTP_CAP_DSN))
1167
0
      len += snprintf(buf + len, sizeof(buf) - len, " RET=%s", c_dsn_return);
1168
0
    if ((adata.capabilities & SMTP_CAP_SMTPUTF8) &&
1169
0
        (mutt_addr_uses_unicode(envfrom) || mutt_addrlist_uses_unicode(to) ||
1170
0
         mutt_addrlist_uses_unicode(cc) || mutt_addrlist_uses_unicode(bcc)))
1171
0
    {
1172
0
      snprintf(buf + len, sizeof(buf) - len, " SMTPUTF8");
1173
0
    }
1174
0
    mutt_strn_cat(buf, sizeof(buf), "\r\n", 3);
1175
0
    if (mutt_socket_send(adata.conn, buf) == -1)
1176
0
    {
1177
0
      rc = SMTP_ERR_WRITE;
1178
0
      break;
1179
0
    }
1180
0
    rc = smtp_get_resp(&adata);
1181
0
    if (rc != 0)
1182
0
      break;
1183
1184
    /* send the recipient list */
1185
0
    if ((rc = smtp_rcpt_to(&adata, to)) || (rc = smtp_rcpt_to(&adata, cc)) ||
1186
0
        (rc = smtp_rcpt_to(&adata, bcc)))
1187
0
    {
1188
0
      break;
1189
0
    }
1190
1191
    /* send the message data */
1192
0
    rc = smtp_data(&adata, msgfile);
1193
0
    if (rc != 0)
1194
0
      break;
1195
1196
0
    mutt_socket_send(adata.conn, "QUIT\r\n");
1197
1198
0
    rc = 0;
1199
0
  } while (false);
1200
1201
0
  mutt_socket_close(adata.conn);
1202
0
  FREE(&adata.conn);
1203
1204
0
  if (rc == SMTP_ERR_READ)
1205
0
    mutt_error(_("SMTP session failed: read error"));
1206
0
  else if (rc == SMTP_ERR_WRITE)
1207
0
    mutt_error(_("SMTP session failed: write error"));
1208
0
  else if (rc == SMTP_ERR_CODE)
1209
0
    mutt_error(_("Invalid server response"));
1210
1211
0
  return rc;
1212
0
}