]> git.llucax.com Git - software/mutt-debian.git/blob - imap/auth_gss.c
Move Mutt with NNTP support to mutt-nntp package
[software/mutt-debian.git] / imap / auth_gss.c
1 /*
2  * Copyright (C) 1999-2001,2005,2009 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */ 
18
19 /* GSS login/authentication code */
20
21 #if HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include "mutt.h"
26 #include "imap_private.h"
27 #include "auth.h"
28
29 #include <netinet/in.h>
30
31 #ifdef HAVE_HEIMDAL
32 #  include <gssapi/gssapi.h>
33 #  define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE
34 #else
35 #  include <gssapi/gssapi.h>
36 #  include <gssapi/gssapi_generic.h>
37 #endif
38
39 #define GSS_BUFSIZE 8192
40
41 #define GSS_AUTH_P_NONE      1
42 #define GSS_AUTH_P_INTEGRITY 2
43 #define GSS_AUTH_P_PRIVACY   4
44 static void print_gss_error(OM_uint32 err_maj, OM_uint32 err_min)
45 {
46         OM_uint32 maj_stat, min_stat; 
47         OM_uint32 msg_ctx = 0;
48         gss_buffer_desc status_string;
49         char buf_maj[512];
50         char buf_min[512];
51         
52         do
53         {
54                 maj_stat = gss_display_status (&min_stat,
55                                                err_maj,
56                                                GSS_C_GSS_CODE,
57                                                GSS_C_NO_OID,
58                                                &msg_ctx,
59                                                &status_string);
60                 if (GSS_ERROR(maj_stat))
61                         break;
62                 strncpy(buf_maj, (char*) status_string.value, sizeof(buf_maj));
63                 gss_release_buffer(&min_stat, &status_string);
64                 
65                 maj_stat = gss_display_status (&min_stat,
66                                                err_min,
67                                                GSS_C_MECH_CODE,
68                                                GSS_C_NULL_OID,
69                                                &msg_ctx,
70                                                &status_string);
71                 if (!GSS_ERROR(maj_stat))
72                 {
73                         strncpy(buf_min, (char*) status_string.value, sizeof(buf_min));
74                         gss_release_buffer(&min_stat, &status_string);
75                 }
76         } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
77         
78         dprint (2, (debugfile, "((%s:%d )(%s:%d))", buf_maj, err_maj, buf_min, err_min));
79 }
80
81 /* imap_auth_gss: AUTH=GSSAPI support. */
82 imap_auth_res_t imap_auth_gss (IMAP_DATA* idata, const char* method)
83 {
84   gss_buffer_desc request_buf, send_token;
85   gss_buffer_t sec_token;
86   gss_name_t target_name;
87   gss_ctx_id_t context;
88 #ifdef DEBUG
89   gss_OID mech_name;
90 #endif
91   gss_qop_t quality;
92   int cflags;
93   OM_uint32 maj_stat, min_stat;
94   char buf1[GSS_BUFSIZE], buf2[GSS_BUFSIZE], server_conf_flags;
95   unsigned long buf_size;
96   int rc;
97
98   if (!mutt_bit_isset (idata->capabilities, AGSSAPI))
99     return IMAP_AUTH_UNAVAIL;
100
101   if (mutt_account_getuser (&idata->conn->account))
102     return IMAP_AUTH_FAILURE;
103   
104   /* get an IMAP service ticket for the server */
105   snprintf (buf1, sizeof (buf1), "imap@%s", idata->conn->account.host);
106   request_buf.value = buf1;
107   request_buf.length = strlen (buf1) + 1;
108   maj_stat = gss_import_name (&min_stat, &request_buf, gss_nt_service_name,
109     &target_name);
110   if (maj_stat != GSS_S_COMPLETE)
111   {
112     dprint (2, (debugfile, "Couldn't get service name for [%s]\n", buf1));
113     return IMAP_AUTH_UNAVAIL;
114   }
115 #ifdef DEBUG    
116   else if (debuglevel >= 2)
117   {
118     maj_stat = gss_display_name (&min_stat, target_name, &request_buf,
119       &mech_name);
120     dprint (2, (debugfile, "Using service name [%s]\n",
121       (char*) request_buf.value));
122     maj_stat = gss_release_buffer (&min_stat, &request_buf);
123   }
124 #endif
125   /* Acquire initial credentials - without a TGT GSSAPI is UNAVAIL */
126   sec_token = GSS_C_NO_BUFFER;
127   context = GSS_C_NO_CONTEXT;
128
129   /* build token */
130   maj_stat = gss_init_sec_context (&min_stat, GSS_C_NO_CREDENTIAL, &context,
131     target_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG, 0, 
132     GSS_C_NO_CHANNEL_BINDINGS, sec_token, NULL, &send_token,
133     (unsigned int*) &cflags, NULL);
134   if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
135   {
136     print_gss_error(maj_stat, min_stat);
137     dprint (1, (debugfile, "Error acquiring credentials - no TGT?\n"));
138     gss_release_name (&min_stat, &target_name);
139
140     return IMAP_AUTH_UNAVAIL;
141   }
142
143   /* now begin login */
144   mutt_message _("Authenticating (GSSAPI)...");
145
146   imap_cmd_start (idata, "AUTHENTICATE GSSAPI");
147
148   /* expect a null continuation response ("+") */
149   do
150     rc = imap_cmd_step (idata);
151   while (rc == IMAP_CMD_CONTINUE);
152
153   if (rc != IMAP_CMD_RESPOND)
154   {
155     dprint (2, (debugfile, "Invalid response from server: %s\n", buf1));
156     gss_release_name (&min_stat, &target_name);
157     goto bail;
158   }
159
160   /* now start the security context initialisation loop... */
161   dprint (2, (debugfile, "Sending credentials\n"));
162   mutt_to_base64 ((unsigned char*) buf1, send_token.value, send_token.length,
163     sizeof (buf1) - 2);
164   gss_release_buffer (&min_stat, &send_token);
165   safe_strcat (buf1, sizeof (buf1), "\r\n");
166   mutt_socket_write (idata->conn, buf1);
167
168   while (maj_stat == GSS_S_CONTINUE_NEEDED)
169   {
170     /* Read server data */
171     do
172       rc = imap_cmd_step (idata);
173     while (rc == IMAP_CMD_CONTINUE);
174
175     if (rc != IMAP_CMD_RESPOND)
176     {
177       dprint (1, (debugfile, "Error receiving server response.\n"));
178       gss_release_name (&min_stat, &target_name);
179       goto bail;
180     }
181
182     request_buf.length = mutt_from_base64 (buf2, idata->buf + 2);
183     request_buf.value = buf2;
184     sec_token = &request_buf;
185
186     /* Write client data */
187     maj_stat = gss_init_sec_context (&min_stat, GSS_C_NO_CREDENTIAL, &context,
188       target_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG, 0, 
189       GSS_C_NO_CHANNEL_BINDINGS, sec_token, NULL, &send_token,
190       (unsigned int*) &cflags, NULL);
191     if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
192     {
193       print_gss_error(maj_stat, min_stat);
194       dprint (1, (debugfile, "Error exchanging credentials\n"));
195       gss_release_name (&min_stat, &target_name);
196
197       goto err_abort_cmd;
198     }
199     mutt_to_base64 ((unsigned char*) buf1, send_token.value,
200       send_token.length, sizeof (buf1) - 2);
201     gss_release_buffer (&min_stat, &send_token);
202     safe_strcat (buf1, sizeof (buf1), "\r\n");
203     mutt_socket_write (idata->conn, buf1);
204   }
205
206   gss_release_name (&min_stat, &target_name);
207
208   /* get security flags and buffer size */
209   do
210     rc = imap_cmd_step (idata);
211   while (rc == IMAP_CMD_CONTINUE);
212
213   if (rc != IMAP_CMD_RESPOND)
214   {
215     dprint (1, (debugfile, "Error receiving server response.\n"));
216     goto bail;
217   }
218   request_buf.length = mutt_from_base64 (buf2, idata->buf + 2);
219   request_buf.value = buf2;
220
221   maj_stat = gss_unwrap (&min_stat, context, &request_buf, &send_token,
222     &cflags, &quality);
223   if (maj_stat != GSS_S_COMPLETE)
224   {
225     print_gss_error(maj_stat, min_stat);
226     dprint (2, (debugfile, "Couldn't unwrap security level data\n"));
227     gss_release_buffer (&min_stat, &send_token);
228     goto err_abort_cmd;
229   }
230   dprint (2, (debugfile, "Credential exchange complete\n"));
231
232   /* first octet is security levels supported. We want NONE */
233   server_conf_flags = ((char*) send_token.value)[0];
234   if ( !(((char*) send_token.value)[0] & GSS_AUTH_P_NONE) )
235   {
236     dprint (2, (debugfile, "Server requires integrity or privacy\n"));
237     gss_release_buffer (&min_stat, &send_token);
238     goto err_abort_cmd;
239   }
240
241   /* we don't care about buffer size if we don't wrap content. But here it is */
242   ((char*) send_token.value)[0] = 0;
243   buf_size = ntohl (*((long *) send_token.value));
244   gss_release_buffer (&min_stat, &send_token);
245   dprint (2, (debugfile, "Unwrapped security level flags: %c%c%c\n",
246     server_conf_flags & GSS_AUTH_P_NONE      ? 'N' : '-',
247     server_conf_flags & GSS_AUTH_P_INTEGRITY ? 'I' : '-',
248     server_conf_flags & GSS_AUTH_P_PRIVACY   ? 'P' : '-'));
249   dprint (2, (debugfile, "Maximum GSS token size is %ld\n", buf_size));
250
251   /* agree to terms (hack!) */
252   buf_size = htonl (buf_size); /* not relevant without integrity/privacy */
253   memcpy (buf1, &buf_size, 4);
254   buf1[0] = GSS_AUTH_P_NONE;
255   /* server decides if principal can log in as user */
256   strncpy (buf1 + 4, idata->conn->account.user, sizeof (buf1) - 4);
257   request_buf.value = buf1;
258   request_buf.length = 4 + strlen (idata->conn->account.user) + 1;
259   maj_stat = gss_wrap (&min_stat, context, 0, GSS_C_QOP_DEFAULT, &request_buf,
260     &cflags, &send_token);
261   if (maj_stat != GSS_S_COMPLETE)
262   {
263     dprint (2, (debugfile, "Error creating login request\n"));
264     goto err_abort_cmd;
265   }
266
267   mutt_to_base64 ((unsigned char*) buf1, send_token.value, send_token.length,
268                   sizeof (buf1) - 2);
269   dprint (2, (debugfile, "Requesting authorisation as %s\n",
270     idata->conn->account.user));
271   safe_strcat (buf1, sizeof (buf1), "\r\n");
272   mutt_socket_write (idata->conn, buf1);
273
274   /* Joy of victory or agony of defeat? */
275   do
276     rc = imap_cmd_step (idata);
277   while (rc == IMAP_CMD_CONTINUE);
278   if (rc == IMAP_CMD_RESPOND)
279   {
280     dprint (1, (debugfile, "Unexpected server continuation request.\n"));
281     goto err_abort_cmd;
282   }
283   if (imap_code (idata->buf))
284   {
285     /* flush the security context */
286     dprint (2, (debugfile, "Releasing GSS credentials\n"));
287     maj_stat = gss_delete_sec_context (&min_stat, &context, &send_token);
288     if (maj_stat != GSS_S_COMPLETE)
289       dprint (1, (debugfile, "Error releasing credentials\n"));
290
291     /* send_token may contain a notification to the server to flush
292      * credentials. RFC 1731 doesn't specify what to do, and since this
293      * support is only for authentication, we'll assume the server knows
294      * enough to flush its own credentials */
295     gss_release_buffer (&min_stat, &send_token);
296
297     return IMAP_AUTH_SUCCESS;
298   }
299   else
300     goto bail;
301
302  err_abort_cmd:
303   mutt_socket_write (idata->conn, "*\r\n");
304   do
305     rc = imap_cmd_step (idata);
306   while (rc == IMAP_CMD_CONTINUE);
307
308  bail:
309   mutt_error _("GSSAPI authentication failed.");
310   mutt_sleep (2);
311   return IMAP_AUTH_FAILURE;
312 }