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_sasl_client_new (pop_data->conn, &saslconn) < 0)
53 {
54 dprint (1, (debugfile, "pop_auth_sasl: Error allocating SASL connection.\n"));
55 return POP_A_FAILURE;
56 }
57
58 if (!method)
59 method = pop_data->auth_list;
60
61 FOREVER
62 {
63 rc = sasl_client_start(saslconn, method, &interaction, &pc, &olen, &mech);
64 if (rc != SASL_INTERACT)
65 break;
66 mutt_sasl_interact (interaction);
67 }
68
69 if (rc != SASL_OK && rc != SASL_CONTINUE)
70 {
71 dprint (1, (debugfile, "pop_auth_sasl: Failure starting authentication exchange. No shared mechanisms?\n"));
72
73 /* SASL doesn't support suggested mechanisms, so fall back */
74 return POP_A_UNAVAIL;
75 }
76
77 /* About client_start: If sasl_client_start() returns data via pc/olen,
78 * the client is expected to send this first (after the AUTH string is sent).
79 * sasl_client_start() may in fact return SASL_OK in this case.
80 */
81 client_start = olen;
82
83 mutt_message _("Authenticating (SASL)...");
84
85 bufsize = ((olen * 2) > LONG_STRING) ? (olen * 2) : LONG_STRING;
86 buf = safe_malloc (bufsize);
87
88 snprintf (buf, bufsize, "AUTH %s", mech);
89 olen = strlen (buf);
90
91 /* looping protocol */
92 FOREVER
93 {
94 strfcpy (buf + olen, "\r\n", bufsize - olen);
95 mutt_socket_write (pop_data->conn, buf);
96 if (mutt_socket_readln (inbuf, sizeof (inbuf), pop_data->conn) < 0)
97 {
98 sasl_dispose (&saslconn);
99 pop_data->status = POP_DISCONNECTED;
100 FREE (&buf);
101 return POP_A_SOCKET;
102 }
103
104 /* Note we don't exit if rc==SASL_OK when client_start is true.
105 * This is because the first loop has only sent the AUTH string, we
106 * need to loop at least once more to send the pc/olen returned
107 * by sasl_client_start().
108 */
109 if (!client_start && rc != SASL_CONTINUE)
110 break;
111
112 if (!mutt_strncmp (inbuf, "+ ", 2)
113 && sasl_decode64 (inbuf+2, strlen (inbuf+2), buf, bufsize - 1, &len) != SASL_OK)
114 {
115 dprint (1, (debugfile, "pop_auth_sasl: error base64-decoding server response.\n"));
116 goto bail;
117 }
118
119 if (!client_start)
120 FOREVER
121 {
122 rc = sasl_client_step (saslconn, buf, len, &interaction, &pc, &olen);
123 if (rc != SASL_INTERACT)
124 break;
125 mutt_sasl_interact (interaction);
126 }
127 else
128 {
129 olen = client_start;
130 client_start = 0;
131 }
132
133 /* Even if sasl_client_step() returns SASL_OK, we should send at
134 * least one more line to the server. See #3862.
135 */
136 if (rc != SASL_CONTINUE && rc != SASL_OK)
137 break;
138
139 /* send out response, or line break if none needed */
140 if (pc)
141 {
142 if ((olen * 2) > bufsize)
143 {
144 bufsize = olen * 2;
145 safe_realloc (&buf, bufsize);
146 }
147 if (sasl_encode64 (pc, olen, buf, bufsize, &olen) != SASL_OK)
148 {
149 dprint (1, (debugfile, "pop_auth_sasl: error base64-encoding client response.\n"));
150 goto bail;
151 }
152 }
153 }
154
155 if (rc != SASL_OK)
156 goto bail;
157
158 if (!mutt_strncmp (inbuf, "+OK", 3))
159 {
160 mutt_sasl_setup_conn (pop_data->conn, saslconn);
161 FREE (&buf);
162 return POP_A_SUCCESS;
163 }
164
165bail:
166 sasl_dispose (&saslconn);
167
168 /* terminate SASL session if the last response is not +OK nor -ERR */
169 if (!mutt_strncmp (inbuf, "+ ", 2))
170 {
171 snprintf (buf, bufsize, "*\r\n");
172 if (pop_query (pop_data, buf, sizeof (buf)) == -1)
173 {
174 FREE (&buf);
175 return POP_A_SOCKET;
176 }
177 }
178
179 FREE (&buf);
180 mutt_error _("SASL authentication failed.");
181 mutt_sleep (2);
182
183 return POP_A_FAILURE;
184}
185#endif
186
187/* Get the server timestamp for APOP authentication */
188void pop_apop_timestamp (POP_DATA *pop_data, char *buf)
189{
190 char *p1, *p2;
191
192 FREE (&pop_data->timestamp);
193
194 if ((p1 = strchr (buf, '<')) && (p2 = strchr (p1, '>')))
195 {
196 p2[1] = '\0';
197 pop_data->timestamp = safe_strdup (p1);
198 }
199}
200
201/* APOP authenticator */
202static pop_auth_res_t pop_auth_apop (POP_DATA *pop_data, const char *method)
203{
204 struct md5_ctx ctx;
205 unsigned char digest[16];
206 char hash[33];
207 char buf[LONG_STRING];
208 size_t i;
209
210 if (!pop_data->timestamp)
211 return POP_A_UNAVAIL;
212
213 if (rfc822_valid_msgid (pop_data->timestamp) < 0)
214 {
215 mutt_error _("POP timestamp is invalid!");
216 mutt_sleep (2);
217 return POP_A_UNAVAIL;
218 }
219
220 mutt_message _("Authenticating (APOP)...");
221
222 /* Compute the authentication hash to send to the server */
223 md5_init_ctx (&ctx);
224 md5_process_bytes (pop_data->timestamp, strlen (pop_data->timestamp), &ctx);
225 md5_process_bytes (pop_data->conn->account.pass,
226 strlen (pop_data->conn->account.pass), &ctx);
227 md5_finish_ctx (&ctx, digest);
228
229 for (i = 0; i < sizeof (digest); i++)
230 sprintf (hash + 2 * i, "%02x", digest[i]);
231
232 /* Send APOP command to server */
233 snprintf (buf, sizeof (buf), "APOP %s %s\r\n", pop_data->conn->account.user, hash);
234
235 switch (pop_query (pop_data, buf, sizeof (buf)))
236 {
237 case 0:
238 return POP_A_SUCCESS;
239 case -1:
240 return POP_A_SOCKET;
241 }
242
243 mutt_error _("APOP authentication failed.");
244 mutt_sleep (2);
245
246 return POP_A_FAILURE;
247}
248
249/* USER authenticator */
250static pop_auth_res_t pop_auth_user (POP_DATA *pop_data, const char *method)
251{
252 char buf[LONG_STRING];
253 int ret;
254
255 if (!pop_data->cmd_user)
256 return POP_A_UNAVAIL;
257
258 mutt_message _("Logging in...");
259
260 snprintf (buf, sizeof (buf), "USER %s\r\n", pop_data->conn->account.user);
261 ret = pop_query (pop_data, buf, sizeof (buf));
262
263 if (pop_data->cmd_user == 2)
264 {
265 if (ret == 0)
266 {
267 pop_data->cmd_user = 1;
268
269 dprint (1, (debugfile, "pop_auth_user: set USER capability\n"));
270 }
271
272 if (ret == -2)
273 {
274 pop_data->cmd_user = 0;
275
276 dprint (1, (debugfile, "pop_auth_user: unset USER capability\n"));
277 snprintf (pop_data->err_msg, sizeof (pop_data->err_msg),
278 _("Command USER is not supported by server."));
279 }
280 }
281
282 if (ret == 0)
283 {
284 snprintf (buf, sizeof (buf), "PASS %s\r\n", pop_data->conn->account.pass);
285 ret = pop_query_d (pop_data, buf, sizeof (buf),
286#ifdef DEBUG
287 /* don't print the password unless we're at the ungodly debugging level */
288 debuglevel < MUTT_SOCK_LOG_FULL ? "PASS *\r\n" :
289#endif
290 NULL);
291 }
292
293 switch (ret)
294 {
295 case 0:
296 return POP_A_SUCCESS;
297 case -1:
298 return POP_A_SOCKET;
299 }
300
301 mutt_error ("%s %s", _("Login failed."), pop_data->err_msg);
302 mutt_sleep (2);
303
304 return POP_A_FAILURE;
305}
306
307static const pop_auth_t pop_authenticators[] = {
308#ifdef USE_SASL
309 { pop_auth_sasl, NULL },
310#endif
311 { pop_auth_apop, "apop" },
312 { pop_auth_user, "user" },
313 { NULL, NULL }
314};
315
316/*
317 * Authentication
318 * 0 - successful,
319 * -1 - connection lost,
320 * -2 - login failed,
321 * -3 - authentication canceled.
322*/
323int pop_authenticate (POP_DATA* pop_data)
324{
325 ACCOUNT *acct = &pop_data->conn->account;
326 const pop_auth_t* authenticator;
327 char* methods;
328 char* comma;
329 char* method;
330 int attempts = 0;
331 int ret = POP_A_UNAVAIL;
332
333 if (mutt_account_getuser (acct) || !acct->user[0] ||
334 mutt_account_getpass (acct) || !acct->pass[0])
335 return -3;
336
337 if (PopAuthenticators && *PopAuthenticators)
338 {
339 /* Try user-specified list of authentication methods */
340 methods = safe_strdup (PopAuthenticators);
341 method = methods;
342
343 while (method)
344 {
345 comma = strchr (method, ':');
346 if (comma)
347 *comma++ = '\0';
348 dprint (2, (debugfile, "pop_authenticate: Trying method %s\n", method));
349 authenticator = pop_authenticators;
350
351 while (authenticator->authenticate)
352 {
353 if (!authenticator->method ||
354 !ascii_strcasecmp (authenticator->method, method))
355 {
356 ret = authenticator->authenticate (pop_data, method);
357 if (ret == POP_A_SOCKET)
358 switch (pop_connect (pop_data))
359 {
360 case 0:
361 {
362 ret = authenticator->authenticate (pop_data, method);
363 break;
364 }
365 case -2:
366 ret = POP_A_FAILURE;
367 }
368
369 if (ret != POP_A_UNAVAIL)
370 attempts++;
371 if (ret == POP_A_SUCCESS || ret == POP_A_SOCKET ||
372 (ret == POP_A_FAILURE && !option (OPTPOPAUTHTRYALL)))
373 {
374 comma = NULL;
375 break;
376 }
377 }
378 authenticator++;
379 }
380
381 method = comma;
382 }
383
384 FREE (&methods);
385 }
386 else
387 {
388 /* Fall back to default: any authenticator */
389 dprint (2, (debugfile, "pop_authenticate: Using any available method.\n"));
390 authenticator = pop_authenticators;
391
392 while (authenticator->authenticate)
393 {
394 ret = authenticator->authenticate (pop_data, authenticator->method);
395 if (ret == POP_A_SOCKET)
396 switch (pop_connect (pop_data))
397 {
398 case 0:
399 {
400 ret = authenticator->authenticate (pop_data, authenticator->method);
401 break;
402 }
403 case -2:
404 ret = POP_A_FAILURE;
405 }
406
407 if (ret != POP_A_UNAVAIL)
408 attempts++;
409 if (ret == POP_A_SUCCESS || ret == POP_A_SOCKET ||
410 (ret == POP_A_FAILURE && !option (OPTPOPAUTHTRYALL)))
411 break;
412
413 authenticator++;
414 }
415 }
416
417 switch (ret)
418 {
419 case POP_A_SUCCESS:
420 return 0;
421 case POP_A_SOCKET:
422 return -1;
423 case POP_A_UNAVAIL:
424 if (!attempts)
425 mutt_error (_("No authenticators available"));
426 }
427
428 return -2;
429}