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