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