NeoMutt  2022-04-29-215-gc12b98
Teaching an old dog new tricks
DOXYGEN
auth.c
Go to the documentation of this file.
1
30#include "config.h"
31#include <stdbool.h>
32#include <stdio.h>
33#include <string.h>
34#include "private.h"
35#include "mutt/lib.h"
36#include "address/lib.h"
37#include "config/lib.h"
38#include "core/lib.h"
39#include "conn/lib.h"
40#include "adata.h"
41#ifdef USE_SASL_CYRUS
42#include <sasl/sasl.h>
43#include <sasl/saslutil.h>
44#endif
45
46#ifdef USE_SASL_CYRUS
50static enum PopAuthRes pop_auth_sasl(struct PopAccountData *adata, const char *method)
51{
52 sasl_conn_t *saslconn = NULL;
53 sasl_interact_t *interaction = NULL;
54 int rc;
55 char inbuf[1024] = { 0 };
56 const char *mech = NULL;
57 const char *pc = NULL;
58 unsigned int len = 0, olen = 0;
59
60 if (mutt_account_getpass(&adata->conn->account) || !adata->conn->account.pass[0])
61 return POP_A_FAILURE;
62
63 if (mutt_sasl_client_new(adata->conn, &saslconn) < 0)
64 {
65 mutt_debug(LL_DEBUG1, "Error allocating SASL connection\n");
66 return POP_A_FAILURE;
67 }
68
69 if (!method)
70 method = adata->auth_list.data;
71
72 while (true)
73 {
74 rc = sasl_client_start(saslconn, method, &interaction, &pc, &olen, &mech);
75 if (rc != SASL_INTERACT)
76 break;
77 mutt_sasl_interact(interaction);
78 }
79
80 if ((rc != SASL_OK) && (rc != SASL_CONTINUE))
81 {
82 mutt_debug(LL_DEBUG1, "Failure starting authentication exchange. No shared mechanisms?\n");
83
84 /* SASL doesn't support suggested mechanisms, so fall back */
85 sasl_dispose(&saslconn);
86 return POP_A_UNAVAIL;
87 }
88
89 /* About client_start: If sasl_client_start() returns data via pc/olen,
90 * the client is expected to send this first (after the AUTH string is sent).
91 * sasl_client_start() may in fact return SASL_OK in this case. */
92 unsigned int client_start = olen;
93
94 // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
95 mutt_message(_("Authenticating (%s)..."), "SASL");
96
97 size_t bufsize = MAX((olen * 2), 1024);
98 char *buf = mutt_mem_malloc(bufsize);
99
100 snprintf(buf, bufsize, "AUTH %s", mech);
101 olen = strlen(buf);
102
103 /* looping protocol */
104 while (true)
105 {
106 mutt_str_copy(buf + olen, "\r\n", bufsize - olen);
107 mutt_socket_send(adata->conn, buf);
108 if (mutt_socket_readln_d(inbuf, sizeof(inbuf), adata->conn, MUTT_SOCK_LOG_FULL) < 0)
109 {
110 sasl_dispose(&saslconn);
111 adata->status = POP_DISCONNECTED;
112 FREE(&buf);
113 return POP_A_SOCKET;
114 }
115
116 /* Note we don't exit if rc==SASL_OK when client_start is true.
117 * This is because the first loop has only sent the AUTH string, we
118 * need to loop at least once more to send the pc/olen returned
119 * by sasl_client_start(). */
120 if (!client_start && (rc != SASL_CONTINUE))
121 break;
122
123 if (mutt_str_startswith(inbuf, "+ ") &&
124 (sasl_decode64(inbuf + 2, strlen(inbuf + 2), buf, bufsize - 1, &len) != SASL_OK))
125 {
126 mutt_debug(LL_DEBUG1, "error base64-decoding server response\n");
127 goto bail;
128 }
129
130 if (!client_start)
131 {
132 while (true)
133 {
134 rc = sasl_client_step(saslconn, buf, len, &interaction, &pc, &olen);
135 if (rc != SASL_INTERACT)
136 break;
137 mutt_sasl_interact(interaction);
138 }
139 }
140 else
141 {
142 olen = client_start;
143 client_start = 0;
144 }
145
146 /* Even if sasl_client_step() returns SASL_OK, we should send at
147 * least one more line to the server. */
148 if ((rc != SASL_CONTINUE) && (rc != SASL_OK))
149 break;
150
151 /* send out response, or line break if none needed */
152 if (pc)
153 {
154 if ((olen * 2) > bufsize)
155 {
156 bufsize = olen * 2;
157 mutt_mem_realloc(&buf, bufsize);
158 }
159 if (sasl_encode64(pc, olen, buf, bufsize, &olen) != SASL_OK)
160 {
161 mutt_debug(LL_DEBUG1, "error base64-encoding client response\n");
162 goto bail;
163 }
164 }
165 }
166
167 if (rc != SASL_OK)
168 goto bail;
169
170 if (mutt_str_startswith(inbuf, "+OK"))
171 {
172 mutt_sasl_setup_conn(adata->conn, saslconn);
173 FREE(&buf);
174 return POP_A_SUCCESS;
175 }
176
177bail:
178 sasl_dispose(&saslconn);
179
180 /* terminate SASL session if the last response is not +OK nor -ERR */
181 if (mutt_str_startswith(inbuf, "+ "))
182 {
183 snprintf(buf, bufsize, "*\r\n");
184 if (pop_query(adata, buf, bufsize) == -1)
185 {
186 FREE(&buf);
187 return POP_A_SOCKET;
188 }
189 }
190
191 FREE(&buf);
192 // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
193 mutt_error(_("%s authentication failed"), "SASL");
194
195 return POP_A_FAILURE;
196}
197#endif
198
204void pop_apop_timestamp(struct PopAccountData *adata, char *buf)
205{
206 char *p1 = NULL, *p2 = NULL;
207
208 FREE(&adata->timestamp);
209
210 if ((p1 = strchr(buf, '<')) && (p2 = strchr(p1, '>')))
211 {
212 p2[1] = '\0';
213 adata->timestamp = mutt_str_dup(p1);
214 }
215}
216
220static enum PopAuthRes pop_auth_apop(struct PopAccountData *adata, const char *method)
221{
222 struct Md5Ctx md5ctx;
223 unsigned char digest[16];
224 char hash[33] = { 0 };
225 char buf[1024] = { 0 };
226
227 if (mutt_account_getpass(&adata->conn->account) || !adata->conn->account.pass[0])
228 return POP_A_FAILURE;
229
230 if (!adata->timestamp)
231 return POP_A_UNAVAIL;
232
233 if (!mutt_addr_valid_msgid(adata->timestamp))
234 {
235 mutt_error(_("POP timestamp is invalid"));
236 return POP_A_UNAVAIL;
237 }
238
239 // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
240 mutt_message(_("Authenticating (%s)..."), "APOP");
241
242 /* Compute the authentication hash to send to the server */
243 mutt_md5_init_ctx(&md5ctx);
244 mutt_md5_process(adata->timestamp, &md5ctx);
245 mutt_md5_process(adata->conn->account.pass, &md5ctx);
246 mutt_md5_finish_ctx(&md5ctx, digest);
247 mutt_md5_toascii(digest, hash);
248
249 /* Send APOP command to server */
250 snprintf(buf, sizeof(buf), "APOP %s %s\r\n", adata->conn->account.user, hash);
251
252 switch (pop_query(adata, buf, sizeof(buf)))
253 {
254 case 0:
255 return POP_A_SUCCESS;
256 case -1:
257 return POP_A_SOCKET;
258 }
259
260 // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
261 mutt_error(_("%s authentication failed"), "APOP");
262
263 return POP_A_FAILURE;
264}
265
269static enum PopAuthRes pop_auth_user(struct PopAccountData *adata, const char *method)
270{
271 if (!adata->cmd_user)
272 return POP_A_UNAVAIL;
273
274 if (mutt_account_getpass(&adata->conn->account) || !adata->conn->account.pass[0])
275 return POP_A_FAILURE;
276
277 mutt_message(_("Logging in..."));
278
279 char buf[1024] = { 0 };
280 snprintf(buf, sizeof(buf), "USER %s\r\n", adata->conn->account.user);
281 int rc = pop_query(adata, buf, sizeof(buf));
282
283 if (adata->cmd_user == 2)
284 {
285 if (rc == 0)
286 {
287 adata->cmd_user = 1;
288
289 mutt_debug(LL_DEBUG1, "set USER capability\n");
290 }
291
292 if (rc == -2)
293 {
294 adata->cmd_user = 0;
295
296 mutt_debug(LL_DEBUG1, "unset USER capability\n");
297 snprintf(adata->err_msg, sizeof(adata->err_msg), "%s",
298 _("Command USER is not supported by server"));
299 }
300 }
301
302 if (rc == 0)
303 {
304 snprintf(buf, sizeof(buf), "PASS %s\r\n", adata->conn->account.pass);
305 const short c_debug_level = cs_subset_number(NeoMutt->sub, "debug_level");
306 rc = pop_query_d(adata, buf, sizeof(buf),
307 /* don't print the password unless we're at the ungodly debugging level */
308 (c_debug_level < MUTT_SOCK_LOG_FULL) ? "PASS *\r\n" : NULL);
309 }
310
311 switch (rc)
312 {
313 case 0:
314 return POP_A_SUCCESS;
315 case -1:
316 return POP_A_SOCKET;
317 }
318
319 mutt_error("%s %s", _("Login failed"), adata->err_msg);
320
321 return POP_A_FAILURE;
322}
323
327static enum PopAuthRes pop_auth_oauth(struct PopAccountData *adata, const char *method)
328{
329 /* If they did not explicitly request or configure oauth then fail quietly */
330 const char *const c_pop_oauth_refresh_command = cs_subset_string(NeoMutt->sub, "pop_oauth_refresh_command");
331 if (!method && !c_pop_oauth_refresh_command)
332 return POP_A_UNAVAIL;
333
334 // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
335 mutt_message(_("Authenticating (%s)..."), "OAUTHBEARER");
336
337 char *oauthbearer = mutt_account_getoauthbearer(&adata->conn->account, false);
338 if (!oauthbearer)
339 return POP_A_FAILURE;
340
341 size_t auth_cmd_len = strlen(oauthbearer) + 30;
342 char *auth_cmd = mutt_mem_malloc(auth_cmd_len);
343 snprintf(auth_cmd, auth_cmd_len, "AUTH OAUTHBEARER %s\r\n", oauthbearer);
344 FREE(&oauthbearer);
345
346 int rc = pop_query_d(adata, auth_cmd, strlen(auth_cmd),
347#ifdef DEBUG
348 /* don't print the bearer token unless we're at the ungodly debugging level */
349 (cs_subset_number(NeoMutt->sub, "debug_level") < MUTT_SOCK_LOG_FULL) ?
350 "AUTH OAUTHBEARER *\r\n" :
351#endif
352 NULL);
353 FREE(&auth_cmd);
354
355 switch (rc)
356 {
357 case 0:
358 return POP_A_SUCCESS;
359 case -1:
360 return POP_A_SOCKET;
361 }
362
363 /* The error response was a SASL continuation, so "continue" it.
364 * See RFC7628 3.2.3 */
365 mutt_socket_send(adata->conn, "\001");
366
367 char *err = adata->err_msg;
368 char decoded_err[1024] = { 0 };
369 int len = mutt_b64_decode(adata->err_msg, decoded_err, sizeof(decoded_err) - 1);
370 if (len >= 0)
371 {
372 decoded_err[len] = '\0';
373 err = decoded_err;
374 }
375 mutt_error("%s %s", _("Authentication failed"), err);
376
377 return POP_A_FAILURE;
378}
379
383static const struct PopAuth PopAuthenticators[] = {
384 // clang-format off
385 { pop_auth_oauth, "oauthbearer" },
386#ifdef USE_SASL_CYRUS
387 { pop_auth_sasl, NULL },
388#endif
389 { pop_auth_apop, "apop" },
390 { pop_auth_user, "user" },
391 { NULL, NULL },
392 // clang-format on
393};
394
403bool pop_auth_is_valid(const char *authenticator)
404{
405 for (size_t i = 0; i < mutt_array_size(PopAuthenticators); i++)
406 {
407 const struct PopAuth *auth = &PopAuthenticators[i];
408 if (auth->method && mutt_istr_equal(auth->method, authenticator))
409 return true;
410 }
411
412 return false;
413}
414
425{
426 struct ConnAccount *cac = &adata->conn->account;
427 const struct PopAuth *authenticator = NULL;
428 int attempts = 0;
429 int rc = POP_A_UNAVAIL;
430
431 if ((mutt_account_getuser(cac) < 0) || (cac->user[0] == '\0'))
432 {
433 return -3;
434 }
435
436 const struct Slist *c_pop_authenticators = cs_subset_slist(NeoMutt->sub, "pop_authenticators");
437 const bool c_pop_auth_try_all = cs_subset_bool(NeoMutt->sub, "pop_auth_try_all");
438 if (c_pop_authenticators && (c_pop_authenticators->count > 0))
439 {
440 /* Try user-specified list of authentication methods */
441 struct ListNode *np = NULL;
442 STAILQ_FOREACH(np, &c_pop_authenticators->head, entries)
443 {
444 mutt_debug(LL_DEBUG2, "Trying method %s\n", np->data);
445 authenticator = PopAuthenticators;
446
447 while (authenticator->authenticate)
448 {
449 if (!authenticator->method || mutt_istr_equal(authenticator->method, np->data))
450 {
451 rc = authenticator->authenticate(adata, np->data);
452 if (rc == POP_A_SOCKET)
453 {
454 switch (pop_connect(adata))
455 {
456 case 0:
457 {
458 rc = authenticator->authenticate(adata, np->data);
459 break;
460 }
461 case -2:
462 rc = POP_A_FAILURE;
463 }
464 }
465
466 if (rc != POP_A_UNAVAIL)
467 attempts++;
468 if ((rc == POP_A_SUCCESS) || (rc == POP_A_SOCKET) ||
469 ((rc == POP_A_FAILURE) && !c_pop_auth_try_all))
470 {
471 break;
472 }
473 }
474 authenticator++;
475 }
476 }
477 }
478 else
479 {
480 /* Fall back to default: any authenticator */
481 mutt_debug(LL_DEBUG2, "Using any available method\n");
482 authenticator = PopAuthenticators;
483
484 while (authenticator->authenticate)
485 {
486 rc = authenticator->authenticate(adata, NULL);
487 if (rc == POP_A_SOCKET)
488 {
489 switch (pop_connect(adata))
490 {
491 case 0:
492 {
493 rc = authenticator->authenticate(adata, NULL);
494 break;
495 }
496 case -2:
497 rc = POP_A_FAILURE;
498 }
499 }
500
501 if (rc != POP_A_UNAVAIL)
502 attempts++;
503 if ((rc == POP_A_SUCCESS) || (rc == POP_A_SOCKET) ||
504 ((rc == POP_A_FAILURE) && !c_pop_auth_try_all))
505 {
506 break;
507 }
508
509 authenticator++;
510 }
511 }
512
513 switch (rc)
514 {
515 case POP_A_SUCCESS:
516 return 0;
517 case POP_A_SOCKET:
518 return -1;
519 case POP_A_UNAVAIL:
520 if (attempts == 0)
521 mutt_error(_("No authenticators available"));
522 }
523
524 return -2;
525}
bool mutt_addr_valid_msgid(const char *msgid)
Is this a valid Message ID?
Definition: address.c:764
Email Address Handling.
int mutt_b64_decode(const char *in, char *out, size_t olen)
Convert null-terminated base64 string to raw bytes.
Definition: base64.c:136
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
const struct Slist * cs_subset_slist(const struct ConfigSubset *sub, const char *name)
Get a string-list config item by name.
Definition: helpers.c:268
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:169
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
Convenience wrapper for the config headers.
Connection Library.
int mutt_account_getpass(struct ConnAccount *cac)
Fetch password into ConnAccount, if necessary.
Definition: connaccount.c:130
int mutt_account_getuser(struct ConnAccount *cac)
Retrieve username into ConnAccount, if necessary.
Definition: connaccount.c:50
char * mutt_account_getoauthbearer(struct ConnAccount *cac, bool xoauth2)
Get an OAUTHBEARER/XOAUTH2 token.
Definition: connaccount.c:195
Convenience wrapper for the core headers.
#define mutt_error(...)
Definition: logging.h:87
#define mutt_message(...)
Definition: logging.h:86
#define mutt_debug(LEVEL,...)
Definition: logging.h:84
@ LL_DEBUG2
Log at debug level 2.
Definition: logging.h:41
@ LL_DEBUG1
Log at debug level 1.
Definition: logging.h:40
void mutt_md5_process(const char *str, struct Md5Ctx *md5ctx)
Process a NULL-terminated string.
Definition: md5.c:356
void mutt_md5_init_ctx(struct Md5Ctx *md5ctx)
Initialise the MD5 computation.
Definition: md5.c:262
void * mutt_md5_finish_ctx(struct Md5Ctx *md5ctx, void *resbuf)
Process the remaining bytes in the buffer.
Definition: md5.c:286
void mutt_md5_toascii(const void *digest, char *resbuf)
Convert a binary MD5 digest into ASCII Hexadecimal.
Definition: md5.c:457
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
#define FREE(x)
Definition: memory.h:43
#define MAX(a, b)
Definition: memory.h:30
#define mutt_array_size(x)
Definition: memory.h:36
Convenience wrapper for the library headers.
#define _(a)
Definition: message.h:28
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:819
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:250
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:227
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:652
Pop-specific Account data.
static enum PopAuthRes pop_auth_user(struct PopAccountData *adata, const char *method)
USER authenticator - Implements PopAuth::authenticate()
Definition: auth.c:269
bool pop_auth_is_valid(const char *authenticator)
Check if string is a valid pop authentication method.
Definition: auth.c:403
static const struct PopAuth PopAuthenticators[]
Accepted authentication methods.
Definition: auth.c:383
static enum PopAuthRes pop_auth_apop(struct PopAccountData *adata, const char *method)
APOP authenticator - Implements PopAuth::authenticate()
Definition: auth.c:220
void pop_apop_timestamp(struct PopAccountData *adata, char *buf)
Get the server timestamp for APOP authentication.
Definition: auth.c:204
static enum PopAuthRes pop_auth_oauth(struct PopAccountData *adata, const char *method)
Authenticate a POP connection using OAUTHBEARER - Implements PopAuth::authenticate()
Definition: auth.c:327
int pop_authenticate(struct PopAccountData *adata)
Authenticate with a POP server.
Definition: auth.c:424
int pop_connect(struct PopAccountData *adata)
Open connection.
Definition: lib.c:273
int pop_query_d(struct PopAccountData *adata, char *buf, size_t buflen, char *msg)
Send data from buffer and receive answer to the same buffer.
Definition: lib.c:457
PopAuthRes
POP authentication responses.
Definition: private.h:58
@ POP_A_UNAVAIL
No valid authentication method.
Definition: private.h:62
@ POP_A_SUCCESS
Authenticated successfully.
Definition: private.h:59
@ POP_A_FAILURE
Authentication failed.
Definition: private.h:61
@ POP_A_SOCKET
Connection lost.
Definition: private.h:60
#define pop_query(adata, buf, buflen)
Definition: private.h:109
@ POP_DISCONNECTED
Disconnected from server.
Definition: private.h:51
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:352
int mutt_sasl_interact(sasl_interact_t *interaction)
Perform an SASL interaction with the user.
Definition: sasl.c:694
int mutt_sasl_client_new(struct Connection *conn, sasl_conn_t **saslconn)
Wrapper for sasl_client_new()
Definition: sasl.c:599
void mutt_sasl_setup_conn(struct Connection *conn, sasl_conn_t *saslconn)
Set up an SASL connection.
Definition: sasl.c:731
GUI display the mailboxes in a side panel.
int mutt_socket_readln_d(char *buf, size_t buflen, struct Connection *conn, int dbg)
Read a line from a socket.
Definition: socket.c:247
#define MUTT_SOCK_LOG_FULL
Definition: socket.h:56
#define mutt_socket_send(conn, buf)
Definition: socket.h:59
char * data
Pointer to data.
Definition: buffer.h:35
Login details for a remote server.
Definition: connaccount.h:53
char user[128]
Username.
Definition: connaccount.h:56
char pass[256]
Password.
Definition: connaccount.h:57
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:50
A List node for strings.
Definition: list.h:35
char * data
String.
Definition: list.h:36
Cursor for the MD5 hashing.
Definition: md5.h:37
Container for Accounts, Notifications.
Definition: neomutt.h:37
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
POP-specific Account data -.
Definition: adata.h:37
char err_msg[POP_CMD_RESPONSE]
Definition: adata.h:56
unsigned int status
Definition: adata.h:39
struct Connection * conn
Connection to POP server.
Definition: adata.h:38
char * timestamp
Definition: adata.h:54
unsigned int cmd_user
optional command USER
Definition: adata.h:44
struct Buffer auth_list
list of auth mechanisms
Definition: adata.h:53
POP authentication multiplexor.
Definition: private.h:78
const char * method
Name of authentication method supported, NULL means variable.
Definition: private.h:87
enum PopAuthRes(* authenticate)(struct PopAccountData *adata, const char *method)
Authenticate a POP connection.
Definition: private.h:85
String list.
Definition: slist.h:47
struct ListHead head
List containing values.
Definition: slist.h:48
size_t count
Number of values in list.
Definition: slist.h:49