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 | } |