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