Andreas Krennmair:
[apps/madmutt.git] / imap / auth_sasl.c
1 /*
2  * Copyright (C) 2000-3 Brendan Cully <brendan@kublai.com>
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 /* SASL login/authentication code */
20
21 #include "mutt.h"
22 #include "mutt_sasl.h"
23 #include "imap_private.h"
24 #include "auth.h"
25
26 #ifdef USE_SASL2
27 #include <sasl/sasl.h>
28 #include <sasl/saslutil.h>
29 #else
30 #include <sasl.h>
31 #include <saslutil.h>
32 #endif
33
34 /* imap_auth_sasl: Default authenticator if available. */
35 imap_auth_res_t imap_auth_sasl (IMAP_DATA* idata, const char* method)
36 {
37   sasl_conn_t* saslconn;
38   sasl_interact_t* interaction = NULL;
39   int rc, irc;
40   char buf[HUGE_STRING];
41   const char* mech;
42 #ifdef USE_SASL2
43   const char *pc = NULL;
44 #else
45   char* pc = NULL;
46 #endif
47   unsigned int len, olen;
48   unsigned char client_start;
49
50   if (mutt_sasl_client_new (idata->conn, &saslconn) < 0)
51   {
52     dprint (1, (debugfile,
53       "imap_auth_sasl: Error allocating SASL connection.\n"));
54     return IMAP_AUTH_FAILURE;
55   }
56
57   rc = SASL_FAIL;
58
59   /* If the user hasn't specified a method, use any available */
60   if (!method)
61   {
62     method = idata->capstr;
63
64     /* hack for SASL ANONYMOUS support:
65      * 1. Fetch username. If it's "" or "anonymous" then
66      * 2. attempt sasl_client_start with only "AUTH=ANONYMOUS" capability
67      * 3. if sasl_client_start fails, fall through... */
68
69     if (mutt_account_getuser (&idata->conn->account))
70       return IMAP_AUTH_FAILURE;
71
72     if (mutt_bit_isset (idata->capabilities, AUTH_ANON) &&
73         (!idata->conn->account.user[0] ||
74          !ascii_strncmp (idata->conn->account.user, "anonymous", 9)))
75 #ifdef USE_SASL2
76       rc = sasl_client_start (saslconn, "AUTH=ANONYMOUS", NULL, &pc, &olen, 
77                               &mech);
78 #else
79       rc = sasl_client_start (saslconn, "AUTH=ANONYMOUS", NULL, NULL, &pc, &olen,
80                               &mech);
81 #endif
82   }
83   
84   if (rc != SASL_OK && rc != SASL_CONTINUE)
85     do
86     {
87 #ifdef USE_SASL2
88       rc = sasl_client_start (saslconn, method, &interaction,
89         &pc, &olen, &mech);
90 #else
91       rc = sasl_client_start (saslconn, method, NULL, &interaction,
92         &pc, &olen, &mech);
93 #endif
94       if (rc == SASL_INTERACT)
95         mutt_sasl_interact (interaction);
96     }
97     while (rc == SASL_INTERACT);
98
99   client_start = (olen > 0);
100
101   if (rc != SASL_OK && rc != SASL_CONTINUE)
102   {
103     if (method)
104       dprint (2, (debugfile, "imap_auth_sasl: %s unavailable\n", method));
105     else
106       dprint (1, (debugfile, "imap_auth_sasl: Failure starting authentication exchange. No shared mechanisms?\n"));
107     /* SASL doesn't support LOGIN, so fall back */
108
109     return IMAP_AUTH_UNAVAIL;
110   }
111
112   mutt_message (_("Authenticating (%s)..."), mech);
113
114   snprintf (buf, sizeof (buf), "AUTHENTICATE %s", mech);
115   imap_cmd_start (idata, buf);
116   irc = IMAP_CMD_CONTINUE;
117
118   /* looping protocol */
119   while (rc == SASL_CONTINUE || olen > 0)
120   {
121     do
122       irc = imap_cmd_step (idata);
123     while (irc == IMAP_CMD_CONTINUE);
124
125     if (method && irc == IMAP_CMD_NO)
126     {
127       dprint (2, (debugfile, "imap_auth_sasl: %s failed\n", method));
128       sasl_dispose (&saslconn);
129       return IMAP_AUTH_UNAVAIL;
130     }
131
132     if (irc == IMAP_CMD_BAD || irc == IMAP_CMD_NO)
133       goto bail;
134
135     if (irc == IMAP_CMD_RESPOND)
136     {
137 #ifdef USE_SASL2
138       if (sasl_decode64 (idata->cmd.buf+2, strlen (idata->cmd.buf+2), buf, LONG_STRING-1,
139 #else
140       if (sasl_decode64 (idata->cmd.buf+2, strlen (idata->cmd.buf+2), buf,
141 #endif
142                          &len) != SASL_OK)
143       {
144         dprint (1, (debugfile, "imap_auth_sasl: error base64-decoding server response.\n"));
145         goto bail;
146       }
147     }
148
149     if (!client_start)
150     {
151       do
152       {
153         rc = sasl_client_step (saslconn, buf, len, &interaction, &pc, &olen);
154         if (rc == SASL_INTERACT)
155           mutt_sasl_interact (interaction);
156       }
157       while (rc == SASL_INTERACT);
158     }
159     else
160       client_start = 0;
161
162     /* send out response, or line break if none needed */
163     if (olen)
164     {
165       if (sasl_encode64 (pc, olen, buf, sizeof (buf), &olen) != SASL_OK)
166       {
167         dprint (1, (debugfile, "imap_auth_sasl: error base64-encoding client response.\n"));
168         goto bail;
169       }
170
171       /* sasl_client_st(art|ep) allocate pc with malloc, expect me to 
172        * free it */
173 #ifndef USE_SASL2
174       FREE (&pc);
175 #endif
176     }
177     
178     if (irc == IMAP_CMD_RESPOND)
179     {
180       strfcpy (buf + olen, "\r\n", sizeof (buf) - olen);
181       mutt_socket_write (idata->conn, buf);
182     }
183
184     /* If SASL has errored out, send an abort string to the server */
185     if (rc < 0)
186     {
187       mutt_socket_write (idata->conn, "*\r\n");
188       dprint (1, (debugfile, "imap_auth_sasl: sasl_client_step error %d\n",rc));
189     }
190           
191     olen = 0;
192   }
193
194   while (irc != IMAP_CMD_OK)
195     if ((irc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
196       break;
197
198   if (rc != SASL_OK)
199     goto bail;
200
201   if (imap_code (idata->cmd.buf))
202   {
203     mutt_sasl_setup_conn (idata->conn, saslconn);
204     return IMAP_AUTH_SUCCESS;
205   }
206
207  bail:
208   mutt_error _("SASL authentication failed.");
209   mutt_sleep(2);
210   sasl_dispose (&saslconn);
211
212   return IMAP_AUTH_FAILURE;
213 }