mutt stable branch with some hacks
1/*
2 * Copyright (C) 2000-2001 Vsevolod Volkov <vvv@mutt.org.ua>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#if HAVE_CONFIG_H
20# include "config.h"
21#endif
22
23#include "mutt.h"
24#include "mx.h"
25#include "md5.h"
26#include "pop.h"
27
28#include <string.h>
29#include <unistd.h>
30
31#ifdef USE_SASL
32#include <sasl/sasl.h>
33#include <sasl/saslutil.h>
34
35#include "mutt_sasl.h"
36#endif
37
38#ifdef USE_SASL
39/* SASL authenticator */
40static pop_auth_res_t pop_auth_sasl (POP_DATA *pop_data, const char *method)
41{
42 sasl_conn_t *saslconn;
43 sasl_interact_t *interaction = NULL;
44 int rc;
45 char *buf = NULL;
46 size_t bufsize = 0;
47 char inbuf[LONG_STRING];
48 const char* mech;
49 const char *pc = NULL;
50 unsigned int len, olen, client_start;
51
52 if (mutt_account_getpass (&pop_data->conn->account) ||
53 !pop_data->conn->account.pass[0])
54 return POP_A_FAILURE;
55
56 if (mutt_sasl_client_new (pop_data->conn, &saslconn) < 0)
57 {
58 dprint (1, (debugfile, "pop_auth_sasl: Error allocating SASL connection.\n"));
59 return POP_A_FAILURE;
60 }
61
62 if (!method)
63 method = pop_data->auth_list;
64
65 FOREVER
66 {
67 rc = sasl_client_start(saslconn, method, &interaction, &pc, &olen, &mech);
68 if (rc != SASL_INTERACT)
69 break;
70 mutt_sasl_interact (interaction);
71 }
72
73 if (rc != SASL_OK && rc != SASL_CONTINUE)
74 {
75 dprint (1, (debugfile, "pop_auth_sasl: Failure starting authentication exchange. No shared mechanisms?\n"));
76
77 /* SASL doesn't support suggested mechanisms, so fall back */
78 sasl_dispose (&saslconn);
79 return POP_A_UNAVAIL;
80 }
81
82 /* About client_start: If sasl_client_start() returns data via pc/olen,
83 * the client is expected to send this first (after the AUTH string is sent).
84 * sasl_client_start() may in fact return SASL_OK in this case.
85 */
86 client_start = olen;
87
88 mutt_message _("Authenticating (SASL)...");
89
90 bufsize = ((olen * 2) > LONG_STRING) ? (olen * 2) : LONG_STRING;
91 buf = safe_malloc (bufsize);
92
93 snprintf (buf, bufsize, "AUTH %s", mech);
94 olen = strlen (buf);
95
96 /* looping protocol */
97 FOREVER
98 {
99 strfcpy (buf + olen, "\r\n", bufsize - olen);
100 mutt_socket_write (pop_data->conn, buf);
101 if (mutt_socket_readln (inbuf, sizeof (inbuf), pop_data->conn) < 0)
102 {
103 sasl_dispose (&saslconn);
104 pop_data->status = POP_DISCONNECTED;
105 FREE (&buf);
106 return POP_A_SOCKET;
107 }
108
109 /* Note we don't exit if rc==SASL_OK when client_start is true.
110 * This is because the first loop has only sent the AUTH string, we
111 * need to loop at least once more to send the pc/olen returned
112 * by sasl_client_start().
113 */
114 if (!client_start && rc != SASL_CONTINUE)
115 break;
116
117 if (!mutt_strncmp (inbuf, "+ ", 2)
118 && sasl_decode64 (inbuf+2, strlen (inbuf+2), buf, bufsize - 1, &len) != SASL_OK)
119 {
120 dprint (1, (debugfile, "pop_auth_sasl: error base64-decoding server response.\n"));
121 goto bail;
122 }
123
124 if (!client_start)
125 FOREVER
126 {
127 rc = sasl_client_step (saslconn, buf, len, &interaction, &pc, &olen);
128 if (rc != SASL_INTERACT)
129 break;
130 mutt_sasl_interact (interaction);
131 }
132 else
133 {
134 olen = client_start;
135 client_start = 0;
136 }
137
138 /* Even if sasl_client_step() returns SASL_OK, we should send at
139 * least one more line to the server. See #3862.
140 */
141 if (rc != SASL_CONTINUE && rc != SASL_OK)
142 break;
143
144 /* send out response, or line break if none needed */
145 if (pc)
146 {
147 if ((olen * 2) > bufsize)
148 {
149 bufsize = olen * 2;
150 safe_realloc (&buf, bufsize);
151 }
152 if (sasl_encode64 (pc, olen, buf, bufsize, &olen) != SASL_OK)
153 {
154 dprint (1, (debugfile, "pop_auth_sasl: error base64-encoding client response.\n"));
155 goto bail;
156 }
157 }
158 }
159
160 if (rc != SASL_OK)
161 goto bail;
162
163 if (!mutt_strncmp (inbuf, "+OK", 3))
164 {
165 mutt_sasl_setup_conn (pop_data->conn, saslconn);
166 FREE (&buf);
167 return POP_A_SUCCESS;
168 }
169
170bail:
171 sasl_dispose (&saslconn);
172
173 /* terminate SASL session if the last response is not +OK nor -ERR */
174 if (!mutt_strncmp (inbuf, "+ ", 2))
175 {
176 snprintf (buf, bufsize, "*\r\n");
177 if (pop_query (pop_data, buf, sizeof (buf)) == -1)
178 {
179 FREE (&buf);
180 return POP_A_SOCKET;
181 }
182 }
183
184 FREE (&buf);
185 mutt_error _("SASL authentication failed.");
186 mutt_sleep (2);
187
188 return POP_A_FAILURE;
189}
190#endif
191
192/* Get the server timestamp for APOP authentication */
193void pop_apop_timestamp (POP_DATA *pop_data, char *buf)
194{
195 char *p1, *p2;
196
197 FREE (&pop_data->timestamp);
198
199 if ((p1 = strchr (buf, '<')) && (p2 = strchr (p1, '>')))
200 {
201 p2[1] = '\0';
202 pop_data->timestamp = safe_strdup (p1);
203 }
204}
205
206/* APOP authenticator */
207static pop_auth_res_t pop_auth_apop (POP_DATA *pop_data, const char *method)
208{
209 struct md5_ctx ctx;
210 unsigned char digest[16];
211 char hash[33];
212 char buf[LONG_STRING];
213 size_t i;
214
215 if (mutt_account_getpass (&pop_data->conn->account) ||
216 !pop_data->conn->account.pass[0])
217 return POP_A_FAILURE;
218
219 if (!pop_data->timestamp)
220 return POP_A_UNAVAIL;
221
222 if (rfc822_valid_msgid (pop_data->timestamp) < 0)
223 {
224 mutt_error _("POP timestamp is invalid!");
225 mutt_sleep (2);
226 return POP_A_UNAVAIL;
227 }
228
229 mutt_message _("Authenticating (APOP)...");
230
231 /* Compute the authentication hash to send to the server */
232 md5_init_ctx (&ctx);
233 md5_process_bytes (pop_data->timestamp, strlen (pop_data->timestamp), &ctx);
234 md5_process_bytes (pop_data->conn->account.pass,
235 strlen (pop_data->conn->account.pass), &ctx);
236 md5_finish_ctx (&ctx, digest);
237
238 for (i = 0; i < sizeof (digest); i++)
239 sprintf (hash + 2 * i, "%02x", digest[i]);
240
241 /* Send APOP command to server */
242 snprintf (buf, sizeof (buf), "APOP %s %s\r\n", pop_data->conn->account.user, hash);
243
244 switch (pop_query (pop_data, buf, sizeof (buf)))
245 {
246 case 0:
247 return POP_A_SUCCESS;
248 case -1:
249 return POP_A_SOCKET;
250 }
251
252 mutt_error _("APOP authentication failed.");
253 mutt_sleep (2);
254
255 return POP_A_FAILURE;
256}
257
258/* USER authenticator */
259static pop_auth_res_t pop_auth_user (POP_DATA *pop_data, const char *method)
260{
261 char buf[LONG_STRING];
262 int ret;
263
264 if (!pop_data->cmd_user)
265 return POP_A_UNAVAIL;
266
267 if (mutt_account_getpass (&pop_data->conn->account) ||
268 !pop_data->conn->account.pass[0])
269 return POP_A_FAILURE;
270
271 mutt_message _("Logging in...");
272
273 snprintf (buf, sizeof (buf), "USER %s\r\n", pop_data->conn->account.user);
274 ret = pop_query (pop_data, buf, sizeof (buf));
275
276 if (pop_data->cmd_user == 2)
277 {
278 if (ret == 0)
279 {
280 pop_data->cmd_user = 1;
281
282 dprint (1, (debugfile, "pop_auth_user: set USER capability\n"));
283 }
284
285 if (ret == -2)
286 {
287 pop_data->cmd_user = 0;
288
289 dprint (1, (debugfile, "pop_auth_user: unset USER capability\n"));
290 snprintf (pop_data->err_msg, sizeof (pop_data->err_msg), "%s",
291 _("Command USER is not supported by server."));
292 }
293 }
294
295 if (ret == 0)
296 {
297 snprintf (buf, sizeof (buf), "PASS %s\r\n", pop_data->conn->account.pass);
298 ret = pop_query_d (pop_data, buf, sizeof (buf),
299#ifdef DEBUG
300 /* don't print the password unless we're at the ungodly debugging level */
301 debuglevel < MUTT_SOCK_LOG_FULL ? "PASS *\r\n" :
302#endif
303 NULL);
304 }
305
306 switch (ret)
307 {
308 case 0:
309 return POP_A_SUCCESS;
310 case -1:
311 return POP_A_SOCKET;
312 }
313
314 mutt_error ("%s %s", _("Login failed."), pop_data->err_msg);
315 mutt_sleep (2);
316
317 return POP_A_FAILURE;
318}
319
320/* OAUTHBEARER authenticator */
321static pop_auth_res_t pop_auth_oauth (POP_DATA *pop_data, const char *method)
322{
323 char *oauthbearer = NULL;
324 char decoded_err[LONG_STRING];
325 char *err = NULL;
326 char *auth_cmd = NULL;
327 size_t auth_cmd_len;
328 int ret, len;
329
330 /* If they did not explicitly request or configure oauth then fail quietly */
331 if (!(method || PopOauthRefreshCmd))
332 return POP_A_UNAVAIL;
333
334 mutt_message _("Authenticating (OAUTHBEARER)...");
335
336 oauthbearer = mutt_account_getoauthbearer (&pop_data->conn->account);
337 if (oauthbearer == NULL)
338 return POP_A_FAILURE;
339
340 auth_cmd_len = strlen (oauthbearer) + 30;
341 auth_cmd = safe_malloc (auth_cmd_len);
342 snprintf (auth_cmd, auth_cmd_len, "AUTH OAUTHBEARER %s\r\n", oauthbearer);
343 FREE (&oauthbearer);
344
345 ret = pop_query_d (pop_data, auth_cmd, strlen (auth_cmd),
346#ifdef DEBUG
347 /* don't print the bearer token unless we're at the ungodly debugging level */
348 debuglevel < MUTT_SOCK_LOG_FULL ? "AUTH OAUTHBEARER *\r\n" :
349#endif
350 NULL);
351 FREE (&auth_cmd);
352
353 switch (ret)
354 {
355 case 0:
356 return POP_A_SUCCESS;
357 case -1:
358 return POP_A_SOCKET;
359 }
360
361 /* The error response was a SASL continuation, so "continue" it.
362 * See RFC 7628 3.2.3
363 */
364 mutt_socket_write (pop_data->conn, "\001");
365
366 err = pop_data->err_msg;
367 len = mutt_from_base64 (decoded_err, pop_data->err_msg, sizeof(decoded_err) - 1);
368 if (len >= 0)
369 {
370 decoded_err[len] = '\0';
371 err = decoded_err;
372 }
373 mutt_error ("%s %s", _("Authentication failed."), err);
374 mutt_sleep (2);
375
376 return POP_A_FAILURE;
377}
378
379static const pop_auth_t pop_authenticators[] = {
380 { pop_auth_oauth, "oauthbearer" },
381#ifdef USE_SASL
382 { pop_auth_sasl, NULL },
383#endif
384 { pop_auth_apop, "apop" },
385 { pop_auth_user, "user" },
386 { NULL, NULL }
387};
388
389/*
390 * Authentication
391 * 0 - successful,
392 * -1 - connection lost,
393 * -2 - login failed,
394 * -3 - authentication canceled.
395 */
396int pop_authenticate (POP_DATA* pop_data)
397{
398 ACCOUNT *acct = &pop_data->conn->account;
399 const pop_auth_t* authenticator;
400 char* methods;
401 char* comma;
402 char* method;
403 int attempts = 0;
404 int ret = POP_A_UNAVAIL;
405
406 if (mutt_account_getuser (acct) || !acct->user[0])
407 return -3;
408
409 if (PopAuthenticators)
410 {
411 /* Try user-specified list of authentication methods */
412 methods = safe_strdup (PopAuthenticators);
413 method = methods;
414
415 while (method)
416 {
417 comma = strchr (method, ':');
418 if (comma)
419 *comma++ = '\0';
420 dprint (2, (debugfile, "pop_authenticate: Trying method %s\n", method));
421 authenticator = pop_authenticators;
422
423 while (authenticator->authenticate)
424 {
425 if (!authenticator->method ||
426 !ascii_strcasecmp (authenticator->method, method))
427 {
428 ret = authenticator->authenticate (pop_data, method);
429 if (ret == POP_A_SOCKET)
430 switch (pop_connect (pop_data))
431 {
432 case 0:
433 {
434 ret = authenticator->authenticate (pop_data, method);
435 break;
436 }
437 case -2:
438 ret = POP_A_FAILURE;
439 }
440
441 if (ret != POP_A_UNAVAIL)
442 attempts++;
443 if (ret == POP_A_SUCCESS || ret == POP_A_SOCKET ||
444 (ret == POP_A_FAILURE && !option (OPTPOPAUTHTRYALL)))
445 {
446 comma = NULL;
447 break;
448 }
449 }
450 authenticator++;
451 }
452
453 method = comma;
454 }
455
456 FREE (&methods);
457 }
458 else
459 {
460 /* Fall back to default: any authenticator */
461 dprint (2, (debugfile, "pop_authenticate: Using any available method.\n"));
462 authenticator = pop_authenticators;
463
464 while (authenticator->authenticate)
465 {
466 ret = authenticator->authenticate (pop_data, NULL);
467 if (ret == POP_A_SOCKET)
468 switch (pop_connect (pop_data))
469 {
470 case 0:
471 {
472 ret = authenticator->authenticate (pop_data, NULL);
473 break;
474 }
475 case -2:
476 ret = POP_A_FAILURE;
477 }
478
479 if (ret != POP_A_UNAVAIL)
480 attempts++;
481 if (ret == POP_A_SUCCESS || ret == POP_A_SOCKET ||
482 (ret == POP_A_FAILURE && !option (OPTPOPAUTHTRYALL)))
483 break;
484
485 authenticator++;
486 }
487 }
488
489 switch (ret)
490 {
491 case POP_A_SUCCESS:
492 return 0;
493 case POP_A_SOCKET:
494 return -1;
495 case POP_A_UNAVAIL:
496 if (!attempts)
497 mutt_error (_("No authenticators available"));
498 }
499
500 return -2;
501}