]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_gss.c
remove \r lineendings
[netatalk.git] / etc / uams / uams_gss.c
1 /*
2  * $Id: uams_gss.c,v 1.2.2.4 2004-06-20 15:51:46 bfernhomberg Exp $
3  *
4  * Copyright (c) 1990,1993 Regents of The University of Michigan.
5  * Copyright (c) 1999 Adrian Sun (asun@u.washington.edu) 
6  * Copyright (c) 2003 The Reed Institute
7  * All Rights Reserved.  See COPYRIGHT.
8  */
9
10 #ifdef HAVE_CONFIG_H
11 #include "config.h"
12 #endif /* HAVE_CONFIG_H */
13
14 #ifndef ATACC
15 #include <stdio.h>
16 #include <stdlib.h>
17 #ifdef HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif /* HAVE_UNISTD_H */
20
21 /* STDC check */
22 #if STDC_HEADERS
23 #include <string.h>
24 #else /* STDC_HEADERS */
25 #ifndef HAVE_STRCHR
26 #define strchr index
27 #define strrchr index
28 #endif /* HAVE_STRCHR */
29 char *strchr (), *strrchr ();
30 #ifndef HAVE_MEMCPY
31 #define memcpy(d,s,n) bcopy ((s), (d), (n))
32 #define memmove(d,s,n) bcopy ((s), (d), (n))
33 #endif /* ! HAVE_MEMCPY */
34 #endif /* STDC_HEADERS */
35
36 #include <errno.h>
37 #include <atalk/logger.h>
38 #include <atalk/afp.h>
39 #include <atalk/uam.h>
40
41 /* Kerberos includes */
42
43 #if HAVE_GSSAPI_H
44 #include <gssapi.h>
45 #endif
46
47 #if HAVE_GSSAPI_GSSAPI_H
48 #include <gssapi/gssapi.h>
49 #endif
50
51 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
52 #include <gssapi/gssapi_generic.h>
53 #endif
54
55 #if HAVE_GSSAPI_GSSAPI_KRB5_H
56 #include <gssapi/gssapi_krb5.h>
57 #endif
58
59 #if HAVE_COM_ERR_H
60 #include <com_err.h>
61 #endif
62
63 /* We work around something I don't entirely understand... */
64 /* BF: This is a Heimdal/MIT compatibility fix */
65 #ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE
66 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
67 #endif
68
69 #define MIN(a, b) ((a > b) ? b : a)
70
71 static void log_status( char *s, OM_uint32 major_status, 
72                         OM_uint32 minor_status )
73 {
74     gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
75     OM_uint32 min_status, maj_status;
76     OM_uint32 maj_ctx = 0, min_ctx = 0;
77
78     while (1) {
79         maj_status = gss_display_status( &min_status, major_status,
80                                         GSS_C_GSS_CODE, GSS_C_NULL_OID,
81                                         &maj_ctx, &msg );
82         LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
83                                 (int)msg.length, msg.value, strerror(errno));
84         gss_release_buffer(&min_status, &msg);
85
86         if (!maj_ctx)
87             break;
88     }
89     while (1) {
90         maj_status = gss_display_status( &min_status, minor_status,
91                                         GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
92                                         &min_ctx, &msg );
93         LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s, 
94                                 (int)msg.length, msg.value, strerror(errno));
95         gss_release_buffer(&min_status, &msg);
96
97         if (!min_ctx)
98             break;
99     }
100 }
101
102
103 void log_ctx_flags( OM_uint32 flags )
104 {
105     if (flags & GSS_C_DELEG_FLAG)
106         LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" );
107     if (flags & GSS_C_MUTUAL_FLAG)
108         LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" );
109     if (flags & GSS_C_REPLAY_FLAG)
110         LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" );
111     if (flags & GSS_C_SEQUENCE_FLAG)
112         LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" );
113     if (flags & GSS_C_CONF_FLAG)
114         LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" );
115     if (flags & GSS_C_INTEG_FLAG)
116         LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" );
117 }
118
119 /* return 0 on success */
120 static int do_gss_auth( char *service, char *ibuf, int ticket_len,
121                  char *rbuf, int *rbuflen, char *username, int ulen,
122                  struct session_info *sinfo ) 
123 {
124     OM_uint32 major_status = 0, minor_status = 0;
125     gss_name_t server_name;
126     gss_cred_id_t server_creds;
127     gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
128     gss_buffer_desc ticket_buffer, authenticator_buff, sesskey_buff, wrap_buff;
129     gss_name_t client_name;
130     OM_uint32   ret_flags;
131     int ret = 0;
132     /* FIXME
133      * princ specifies the principal to be used.
134      * The user specifies it when configuring afpd anyway,
135      * we should be able to detect it.
136      */
137     gss_buffer_desc s_princ_buffer;
138
139     s_princ_buffer.value = service;
140     s_princ_buffer.length = strlen( service ) + 1;
141     
142     /* outline:
143      * gss_import_name (need a way to get this from afpd)
144      * gss_acquire_credential
145      * gss_accept_sec_context
146      * ...
147      */
148     LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: importing name" );
149     major_status = gss_import_name( &minor_status, 
150                     &s_princ_buffer, 
151                     GSS_C_NT_HOSTBASED_SERVICE,
152                     &server_name );
153     if (major_status != GSS_S_COMPLETE) {
154         log_status( "import_name", major_status, minor_status );
155         ret = 1;
156         goto cleanup_vars;
157     }
158     
159     LOG(log_debug, logtype_uams, 
160         "uams_gss.c :do_gss_auth: acquiring credentials (uid = %d, keytab = %s)",
161         (int)geteuid(), getenv( "KRB5_KTNAME") );
162
163     major_status = gss_acquire_cred( &minor_status, server_name, 
164                         GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
165                         &server_creds, NULL, NULL );    
166     if (major_status != GSS_S_COMPLETE) {
167         log_status( "acquire_cred", major_status, minor_status );
168         ret = 1;
169         goto cleanup_vars;
170     }
171
172     /* The GSSAPI docs say that this should be done in a big "do" loop,
173      * but Apple's implementation doesn't seem to support this behavior.
174      */
175     ticket_buffer.length = ticket_len;
176     ticket_buffer.value = ibuf;
177     authenticator_buff.length = 0;
178     authenticator_buff.value = NULL;
179     LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: accepting context (ticketlen: %u)", ticket_buffer.length);
180     major_status = gss_accept_sec_context( &minor_status, &context_handle,
181                         server_creds, &ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
182                         &client_name, NULL, &authenticator_buff,
183                         &ret_flags, NULL, NULL );
184
185     if (major_status == GSS_S_COMPLETE) {
186         gss_buffer_desc client_name_buffer;
187
188 #ifdef DEBUG1
189         log_ctx_flags( ret_flags );
190 #endif
191         /* use gss_display_name on client_name */
192         major_status = gss_display_name( &minor_status, client_name,
193                                 &client_name_buffer, (gss_OID *)NULL );
194         if (major_status == GSS_S_COMPLETE) {
195
196             sesskey_buff.value = sinfo->sessionkey;
197             sesskey_buff.length = sinfo->sessionkey_len;
198
199             gss_wrap (&minor_status, context_handle, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff); 
200             if ( minor_status == GSS_S_COMPLETE) {
201                 sinfo->cryptedkey = malloc ( wrap_buff.length );
202                 memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
203                 sinfo->cryptedkey_len = wrap_buff.length;
204             }
205             else {
206                 log_status( "GSS wrap", major_status, minor_status );
207             }
208
209             if (wrap_buff.value)
210                 gss_release_buffer( &minor_status, &wrap_buff );
211
212             u_int16_t auth_len = htons( authenticator_buff.length );
213             /* save the username... note that doing it this way is
214              * not the best idea: if a principal is truncated, a user could be
215              * impersonated
216              */
217             memcpy( username, client_name_buffer.value, 
218                 MIN(client_name_buffer.length, ulen - 1));
219             username[MIN(client_name_buffer.length, ulen - 1)] = 0;
220         
221             LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: user is %s!", username );
222             /* copy the authenticator length into the reply buffer */
223             memcpy( rbuf, &auth_len, sizeof(auth_len) );
224             *rbuflen += sizeof(auth_len), rbuf += sizeof(auth_len);
225
226             /* copy the authenticator value into the reply buffer */
227             memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
228             *rbuflen += authenticator_buff.length;
229
230             gss_release_buffer( &minor_status, &client_name_buffer );
231         } else {
232             log_status( "display_name", major_status, minor_status );
233             ret = 1;
234         }
235
236         
237
238
239         /* Clean up after ourselves */
240         gss_release_name( &minor_status, &client_name );
241
242         /* This will SIGSEGV, as it would free ibuf */
243         /*gss_release_buffer( &minor_status, &ticket_buffer );*/
244
245         if ( authenticator_buff.value)
246                 gss_release_buffer( &minor_status, &authenticator_buff );
247
248         gss_delete_sec_context( &minor_status, 
249                         &context_handle, NULL );
250     } else {
251         log_status( "accept_sec_context", major_status, minor_status );
252         ret = 1;
253     }
254     gss_release_cred( &minor_status, &server_creds );
255
256 cleanup_vars:
257     gss_release_name( &minor_status, &server_name );
258     
259     return ret;
260 }
261
262 /* -------------------------- */
263 static int gss_login(void *obj, struct passwd **uam_pwd,
264                      char *ibuf, int ibuflen,
265                      char *rbuf, int *rbuflen)
266 {
267
268     u_int16_t  temp16;
269
270     *rbuflen = 0;
271
272     /* The reply contains a two-byte ID value - note 
273      * that Apple's implementation seems to always return 1 as well
274      */
275     temp16 = htons( 1 );
276     memcpy(rbuf, &temp16, sizeof(temp16));
277     *rbuflen += sizeof(temp16);
278     return AFPERR_AUTHCONT;
279 }
280
281 static int gss_logincont(void *obj, struct passwd **uam_pwd,
282                      char *ibuf, int ibuflen,
283                      char *rbuf, int *rbuflen)
284 {
285     struct passwd *pwd = NULL;
286     u_int16_t login_id;
287     char *username;
288     u_int16_t ticket_len;
289     char *p;
290     int rblen;
291     char *service;
292     int userlen, servicelen;
293     struct session_info *sinfo;
294
295     /* Apple's AFP 3.1 documentation specifies that this command
296      * takes the following format:
297      * pad (byte)
298      * id returned in LoginExt response (u_int16_t)
299      * username (format unspecified) padded, when necessary, to end on an even boundary
300      * ticket length (u_int16_t)
301      * ticket
302      */
303
304     /* Observation of AFP clients in the wild indicate that the actual
305      * format of this request is as follows:
306      * pad (byte) [consumed before login_ext is called]
307      * ?? (byte) - always observed to be 0
308      * id returned in LoginExt response (u_int16_t)
309      * username, encoding unspecified, null terminated C string, 
310      *   padded when the terminating null is an even numbered byte.
311      *   The packet is formated such that the username begins on an 
312      *   odd numbered byte. Eg if the username is 3 characters and the
313      *   terminating null makes 4, expect to pad the the result.
314      *   The encoding of this string is unknown.
315      * ticket length (u_int16_t)
316      * ticket
317      */
318
319     rblen = *rbuflen = 0;
320
321     if (ibuflen < 3) {
322         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet", p);
323         return AFPERR_PARAM;
324     }
325     ibuf++, ibuflen--; /* ?? */
326
327     /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
328     memcpy( &login_id, ibuf, sizeof(login_id) );
329     ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
330     login_id = ntohs( login_id );
331
332     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
333         return AFPERR_MISC;
334
335     if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0)
336         return AFPERR_MISC;
337
338     if (service == NULL) 
339         return AFPERR_MISC;
340
341     if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, (void *)&sinfo, NULL) < 0)
342         return AFPERR_MISC;
343
344     if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
345         LOG(log_info, logtype_uams, "internal error: sessionkey not set");
346         return AFPERR_PARAM;
347     }
348
349     /* We skip past the 'username' parameter because all that matters is the ticket */
350     p = ibuf;
351     while( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
352     if (ibuflen < 4) {
353         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
354         return AFPERR_PARAM;
355     }
356
357     ibuf++, ibuflen--; /* null termination */
358
359     if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
360
361     LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
362
363     memcpy(&ticket_len, ibuf, sizeof(ticket_len));
364     ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
365     ticket_len = ntohs( ticket_len );
366
367     if (ticket_len > ibuflen) {
368         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: invalid ticket length (%u > %u)", ticket_len, ibuflen);
369         return AFPERR_PARAM;
370     }
371
372     if (!do_gss_auth(service, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
373         char *at = strchr( username, '@' );
374
375         // Chop off the realm name
376         if (at)
377             *at = '\0';
378         if((pwd = uam_getname( obj, username, userlen )) == NULL) {
379             LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
380             return AFPERR_PARAM;
381         }
382         if (uam_checkuser(pwd) < 0) {
383             LOG(log_info, logtype_uams, "%s not a valid user", username);
384             return AFPERR_NOTAUTH;
385         }
386         *rbuflen = rblen;
387         *uam_pwd = pwd;
388         return AFP_OK;
389     } else {
390         LOG(log_info, logtype_uams, "do_gss_auth failed" );
391         *rbuflen = 0;
392         return AFPERR_MISC;
393     }
394 }
395
396 /*
397  * For the krb5 uam, this function only needs to return a two-byte
398  * login-session id. None of the data provided by the client up to this
399  * point is trustworthy as we'll have a signed ticket to parse in logincont.
400  */
401 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
402                      char *ibuf, int ibuflen,
403                      char *rbuf, int *rbuflen)
404 {
405     u_int16_t  temp16;
406
407     *rbuflen = 0;
408
409     /* The reply contains a two-byte ID value - note 
410      * that Apple's implementation seems to always return 1 as well
411      */
412     temp16 = htons( 1 );
413     memcpy(rbuf, &temp16, sizeof(temp16));
414     *rbuflen += sizeof(temp16);
415     return AFPERR_AUTHCONT;
416 }
417
418 /* logout */
419 static void gss_logout() {
420 }
421
422 int uam_setup(const char *path)
423 {
424     if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2", 
425                    gss_login, gss_logincont, gss_logout, gss_login_ext) < 0)
426     if (uam_register(UAM_SERVER_LOGIN, path, "Client Krb v2", 
427                    gss_login, gss_logincont, gss_logout) < 0)
428         return -1;
429
430   return 0;
431 }
432
433 static void uam_cleanup(void)
434 {
435   uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
436 }
437
438 UAM_MODULE_EXPORT struct uam_export uams_gss = {
439   UAM_MODULE_SERVER,
440   UAM_MODULE_VERSION,
441   uam_setup, uam_cleanup
442 };
443 #endif