Rocco Rutte:
[apps/madmutt.git] / pop / pop_auth.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 2000-2001 Vsevolod Volkov <vvv@mutt.org.ua>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #if HAVE_CONFIG_H
11 # include "config.h"
12 #endif
13
14 #include "mutt.h"
15 #include "ascii.h"
16 #include "mx.h"
17 #include "md5.h"
18 #include "pop.h"
19
20 #include "lib/mem.h"
21 #include "lib/intl.h"
22 #include "lib/debug.h"
23
24 #include <string.h>
25 #include <unistd.h>
26
27 #ifdef USE_SASL
28 #ifdef USE_SASL2
29 #include <sasl/sasl.h>
30 #include <sasl/saslutil.h>
31 #else
32 #include <sasl.h>
33 #include <saslutil.h>
34 #endif
35
36 #include "mutt_sasl.h"
37 #endif
38
39 #ifdef USE_SASL
40 /* SASL authenticator */
41 static pop_auth_res_t pop_auth_sasl (POP_DATA * pop_data, const char *method)
42 {
43   sasl_conn_t *saslconn;
44   sasl_interact_t *interaction = NULL;
45   int rc;
46   char buf[LONG_STRING];
47   char inbuf[LONG_STRING];
48   const char *mech;
49
50 #ifdef USE_SASL2
51   const char *pc = NULL;
52 #else
53   char *pc = NULL;
54 #endif
55   unsigned int len, olen;
56   unsigned char client_start;
57
58   if (mutt_sasl_client_new (pop_data->conn, &saslconn) < 0) {
59     debug_print (1, ("Error allocating SASL connection.\n"));
60     return POP_A_FAILURE;
61   }
62
63   if (!method)
64     method = pop_data->auth_list;
65
66   FOREVER {
67 #ifdef USE_SASL2
68     rc =
69       sasl_client_start (saslconn, method, &interaction, &pc, &olen, &mech);
70 #else
71     rc = sasl_client_start (saslconn, method, NULL,
72                             &interaction, &pc, &olen, &mech);
73 #endif
74     if (rc != SASL_INTERACT)
75       break;
76     mutt_sasl_interact (interaction);
77   }
78
79   if (rc != SASL_OK && rc != SASL_CONTINUE) {
80     debug_print (1, ("Failure starting authentication exchange. No shared mechanisms?\n"));
81
82     /* SASL doesn't support suggested mechanisms, so fall back */
83     return POP_A_UNAVAIL;
84   }
85
86   client_start = (olen > 0);
87
88   mutt_message _("Authenticating (SASL)...");
89
90   snprintf (buf, sizeof (buf), "AUTH %s", mech);
91   olen = strlen (buf);
92
93   /* looping protocol */
94   FOREVER {
95     strfcpy (buf + olen, "\r\n", sizeof (buf) - olen);
96     mutt_socket_write (pop_data->conn, buf);
97     if (mutt_socket_readln (inbuf, sizeof (inbuf), pop_data->conn) < 0) {
98       sasl_dispose (&saslconn);
99       pop_data->status = POP_DISCONNECTED;
100       return POP_A_SOCKET;
101     }
102
103     if (rc != SASL_CONTINUE)
104       break;
105
106 #ifdef USE_SASL2
107     if (!str_ncmp (inbuf, "+ ", 2)
108         && sasl_decode64 (inbuf, strlen (inbuf), buf, LONG_STRING - 1,
109                           &len) != SASL_OK)
110 #else
111     if (!str_ncmp (inbuf, "+ ", 2)
112         && sasl_decode64 (inbuf, strlen (inbuf), buf, &len) != SASL_OK)
113 #endif
114     {
115       debug_print (1, ("error base64-decoding server response.\n"));
116       goto bail;
117     }
118
119     if (!client_start)
120       FOREVER {
121       rc = sasl_client_step (saslconn, buf, len, &interaction, &pc, &olen);
122       if (rc != SASL_INTERACT)
123         break;
124       mutt_sasl_interact (interaction);
125       }
126     else
127       client_start = 0;
128
129     if (rc != SASL_CONTINUE && (olen == 0 || rc != SASL_OK))
130       break;
131
132     /* send out response, or line break if none needed */
133     if (pc) {
134       if (sasl_encode64 (pc, olen, buf, sizeof (buf), &olen) != SASL_OK) {
135         debug_print (1, ("error base64-encoding client response.\n"));
136         goto bail;
137       }
138
139       /* sasl_client_st(art|ep) allocate pc with malloc, expect me to 
140        * free it */
141 #ifndef USE_SASL2
142       mem_free (&pc);
143 #endif
144     }
145   }
146
147   if (rc != SASL_OK)
148     goto bail;
149
150   if (!str_ncmp (inbuf, "+OK", 3)) {
151     mutt_sasl_setup_conn (pop_data->conn, saslconn);
152     return POP_A_SUCCESS;
153   }
154
155 bail:
156   sasl_dispose (&saslconn);
157
158   /* terminate SASL sessoin if the last responce is not +OK nor -ERR */
159   if (!str_ncmp (inbuf, "+ ", 2)) {
160     snprintf (buf, sizeof (buf), "*\r\n");
161     if (pop_query (pop_data, buf, sizeof (buf)) == PQ_NOT_CONNECTED)
162       return POP_A_SOCKET;
163   }
164
165   mutt_error _("SASL authentication failed.");
166
167   mutt_sleep (2);
168
169   return POP_A_FAILURE;
170 }
171 #endif
172
173 /* Get the server timestamp for APOP authentication */
174 void pop_apop_timestamp (POP_DATA * pop_data, char *buf)
175 {
176   char *p1, *p2;
177
178   mem_free (&pop_data->timestamp);
179
180   if ((p1 = strchr (buf, '<')) && (p2 = strchr (p1, '>'))) {
181     p2[1] = '\0';
182     pop_data->timestamp = str_dup (p1);
183   }
184 }
185
186 /* APOP authenticator */
187 static pop_auth_res_t pop_auth_apop (POP_DATA * pop_data, const char *method)
188 {
189   MD5_CTX mdContext;
190   unsigned char digest[16];
191   char hash[33];
192   char buf[LONG_STRING];
193   int i;
194
195   if (!pop_data->timestamp)
196     return POP_A_UNAVAIL;
197
198   mutt_message _("Authenticating (APOP)...");
199
200   /* Compute the authentication hash to send to the server */
201   MD5Init (&mdContext);
202   MD5Update (&mdContext, (unsigned char *) pop_data->timestamp,
203              strlen (pop_data->timestamp));
204   MD5Update (&mdContext, (unsigned char *) pop_data->conn->account.pass,
205              strlen (pop_data->conn->account.pass));
206   MD5Final (digest, &mdContext);
207
208   for (i = 0; i < sizeof (digest); i++)
209     sprintf (hash + 2 * i, "%02x", digest[i]);
210
211   /* Send APOP command to server */
212   snprintf (buf, sizeof (buf), "APOP %s %s\r\n", pop_data->conn->account.user,
213             hash);
214
215   switch (pop_query (pop_data, buf, sizeof (buf))) {
216   case PQ_OK:
217     return POP_A_SUCCESS;
218   case PQ_NOT_CONNECTED:
219     return POP_A_SOCKET;
220   case PFD_FUNCT_ERROR:
221   case PQ_ERR:
222   default:
223     break;
224   }
225
226   mutt_error ("%s %s", _("APOP authentication failed."), pop_data->err_msg);
227   mutt_sleep (2);
228
229   return POP_A_FAILURE;
230 }
231
232 /* USER authenticator */
233 static pop_auth_res_t pop_auth_user (POP_DATA * pop_data, const char *method)
234 {
235   char buf[LONG_STRING];
236   pop_query_status ret;
237
238   if (pop_data->cmd_user == CMD_NOT_AVAILABLE)
239     return POP_A_UNAVAIL;
240
241   mutt_message _("Logging in...");
242
243   snprintf (buf, sizeof (buf), "USER %s\r\n", pop_data->conn->account.user);
244   ret = pop_query (pop_data, buf, sizeof (buf));
245
246   if (pop_data->cmd_user == CMD_UNKNOWN) {
247     if (ret == PQ_OK) {
248       pop_data->cmd_user = CMD_AVAILABLE;
249
250       debug_print (1, ("set USER capability\n"));
251     }
252
253     if (ret == PQ_ERR) {
254       pop_data->cmd_user = CMD_NOT_AVAILABLE;
255
256       debug_print (1, ("unset USER capability\n"));
257       snprintf (pop_data->err_msg, sizeof (pop_data->err_msg),
258                 _("Command USER is not supported by server."));
259     }
260   }
261
262   if (ret == PQ_OK) {
263     snprintf (buf, sizeof (buf), "PASS %s\r\n", pop_data->conn->account.pass);
264     ret = pop_query_d (pop_data, buf, sizeof (buf),
265 #ifdef DEBUG
266     /* don't print the password unless we're at the ungodly debugging level */
267     DebugLevel < M_SOCK_LOG_FULL ? "PASS *\r\n" :
268 #endif
269     NULL);
270   }
271
272   switch (ret) {
273   case PQ_OK:
274     return POP_A_SUCCESS;
275   case PQ_NOT_CONNECTED:
276     return POP_A_SOCKET;
277   case PFD_FUNCT_ERROR:
278   case PQ_ERR:
279   default:
280     break;
281   }
282
283   mutt_error ("%s %s", _("Login failed."), pop_data->err_msg);
284   mutt_sleep (2);
285
286   return POP_A_FAILURE;
287 }
288
289 static pop_auth_t pop_authenticators[] = {
290 #ifdef USE_SASL
291   {pop_auth_sasl, NULL},
292 #endif
293   {pop_auth_apop, "apop"},
294   {pop_auth_user, "user"},
295   {NULL}
296 };
297
298 /*
299  * Authentication
300  *  0 - successful,
301  * -1 - conection lost,
302  * -2 - login failed,
303  * -3 - authentication canceled.
304 */
305 pop_query_status pop_authenticate (POP_DATA * pop_data)
306 {
307   ACCOUNT *acct = &pop_data->conn->account;
308   pop_auth_t *authenticator;
309   char *methods;
310   char *comma;
311   char *method;
312   int attempts = 0;
313   int ret = POP_A_UNAVAIL;
314
315   if (mutt_account_getuser (acct) || !acct->user[0] ||
316       mutt_account_getpass (acct) || !acct->pass[0])
317     return PFD_FUNCT_ERROR;
318
319   if (PopAuthenticators && *PopAuthenticators) {
320     /* Try user-specified list of authentication methods */
321     methods = str_dup (PopAuthenticators);
322     method = methods;
323
324     while (method) {
325       comma = strchr (method, ':');
326       if (comma)
327         *comma++ = '\0';
328       debug_print (2, ("Trying method %s\n", method));
329       authenticator = pop_authenticators;
330
331       while (authenticator->authenticate) {
332         if (!authenticator->method ||
333             !ascii_strcasecmp (authenticator->method, method)) {
334           ret = authenticator->authenticate (pop_data, method);
335           if (ret == POP_A_SOCKET)
336             switch (pop_connect (pop_data)) {
337             case PQ_OK:
338               {
339                 ret = authenticator->authenticate (pop_data, method);
340                 break;
341               }
342             case PQ_ERR:
343               ret = POP_A_FAILURE;
344             }
345
346           if (ret != POP_A_UNAVAIL)
347             attempts++;
348           if (ret == POP_A_SUCCESS || ret == POP_A_SOCKET ||
349               (ret == POP_A_FAILURE && !option (OPTPOPAUTHTRYALL))) {
350             comma = NULL;
351             break;
352           }
353         }
354         authenticator++;
355       }
356
357       method = comma;
358     }
359
360     mem_free (&methods);
361   }
362   else {
363     /* Fall back to default: any authenticator */
364     debug_print (2, ("Using any available method.\n"));
365     authenticator = pop_authenticators;
366
367     while (authenticator->authenticate) {
368       ret = authenticator->authenticate (pop_data, authenticator->method);
369       if (ret == POP_A_SOCKET)
370         switch (pop_connect (pop_data)) {
371         case PQ_OK:
372           {
373             ret =
374               authenticator->authenticate (pop_data, authenticator->method);
375             break;
376           }
377         case PQ_ERR:
378           ret = POP_A_FAILURE;
379         }
380
381       if (ret != POP_A_UNAVAIL)
382         attempts++;
383       if (ret == POP_A_SUCCESS || ret == POP_A_SOCKET ||
384           (ret == POP_A_FAILURE && !option (OPTPOPAUTHTRYALL)))
385         break;
386
387       authenticator++;
388     }
389   }
390
391   switch (ret) {
392   case POP_A_SUCCESS:
393     return PQ_OK;
394   case POP_A_SOCKET:
395     return PQ_NOT_CONNECTED;
396   case POP_A_UNAVAIL:
397     if (!attempts)
398       mutt_error (_("No authenticators available"));
399   }
400
401   return PQ_ERR;
402 }