]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_gss.c
965975b899acc926a1461c34015c7e39b38c6fec
[netatalk.git] / etc / uams / uams_gss.c
1 /*
2  * $Id: uams_gss.c,v 1.2.2.4.2.1 2004-12-07 18:41:10 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  * Copyright (c) 2004 Bjoern Fernhomberg
8  * All Rights Reserved.  See COPYRIGHT.
9  */
10
11 #ifdef HAVE_CONFIG_H
12 #include "config.h"
13 #endif /* HAVE_CONFIG_H */
14
15 #ifndef ATACC
16 #include <stdio.h>
17 #include <stdlib.h>
18 #ifdef HAVE_UNISTD_H
19 #include <unistd.h>
20 #endif /* HAVE_UNISTD_H */
21
22 /* STDC check */
23 #if STDC_HEADERS
24 #include <string.h>
25 #else /* STDC_HEADERS */
26 #ifndef HAVE_STRCHR
27 #define strchr index
28 #define strrchr index
29 #endif /* HAVE_STRCHR */
30 char *strchr (), *strrchr ();
31 #ifndef HAVE_MEMCPY
32 #define memcpy(d,s,n) bcopy ((s), (d), (n))
33 #define memmove(d,s,n) bcopy ((s), (d), (n))
34 #endif /* ! HAVE_MEMCPY */
35 #endif /* STDC_HEADERS */
36
37 #include <errno.h>
38 #include <atalk/logger.h>
39 #include <atalk/afp.h>
40 #include <atalk/uam.h>
41 #include <atalk/util.h>
42
43 /* Kerberos includes */
44
45 #if HAVE_GSSAPI_H
46 #include <gssapi.h>
47 #endif
48
49 #if HAVE_GSSAPI_GSSAPI_H
50 #include <gssapi/gssapi.h>
51 #endif
52
53 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
54 #include <gssapi/gssapi_generic.h>
55 #endif
56
57 #if HAVE_GSSAPI_GSSAPI_KRB5_H
58 #include <gssapi/gssapi_krb5.h>
59 #endif
60
61 #if HAVE_COM_ERR_H
62 #include <com_err.h>
63 #endif
64
65 /* We work around something I don't entirely understand... */
66 /* BF: This is a Heimdal/MIT compatibility fix */
67 #ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE
68 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
69 #endif
70
71 #define MIN(a, b) ((a > b) ? b : a)
72
73 static void log_status( char *s, OM_uint32 major_status, 
74                         OM_uint32 minor_status )
75 {
76     gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
77     OM_uint32 min_status, maj_status;
78     OM_uint32 maj_ctx = 0, min_ctx = 0;
79
80     while (1) {
81         maj_status = gss_display_status( &min_status, major_status,
82                                         GSS_C_GSS_CODE, GSS_C_NULL_OID,
83                                         &maj_ctx, &msg );
84         LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
85                                 (int)msg.length, msg.value, strerror(errno));
86         gss_release_buffer(&min_status, &msg);
87
88         if (!maj_ctx)
89             break;
90     }
91     while (1) {
92         maj_status = gss_display_status( &min_status, minor_status,
93                                         GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
94                                         &min_ctx, &msg );
95         LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s, 
96                                 (int)msg.length, msg.value, strerror(errno));
97         gss_release_buffer(&min_status, &msg);
98
99         if (!min_ctx)
100             break;
101     }
102 }
103
104
105 static void log_ctx_flags( OM_uint32 flags )
106 {
107 #ifdef DEBUG1
108     if (flags & GSS_C_DELEG_FLAG)
109         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" );
110     if (flags & GSS_C_MUTUAL_FLAG)
111         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" );
112     if (flags & GSS_C_REPLAY_FLAG)
113         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" );
114     if (flags & GSS_C_SEQUENCE_FLAG)
115         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" );
116     if (flags & GSS_C_CONF_FLAG)
117         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" );
118     if (flags & GSS_C_INTEG_FLAG)
119         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" );
120 #endif
121 }
122
123 static void log_principal(gss_name_t server_name)
124 {
125 #ifdef DEBUG1
126     OM_uint32 major_status = 0, minor_status = 0;
127     gss_buffer_desc exported_name;
128
129     /* Only for debugging purposes, check the gssapi internal representation */
130     major_status = gss_export_name(&minor_status, server_name, &exported_name);
131     LOG(log_debug, logtype_uams, "log_principal: exported server name is %s", (char*) exported_name.value);
132     gss_release_buffer( &minor_status, &exported_name );
133 #endif
134 }
135
136 /* get the principal from afpd and import it into server_name */
137 static int get_afpd_principal(void *obj, gss_name_t *server_name)
138 {
139     OM_uint32 major_status = 0, minor_status = 0;
140     char *realm, *fqdn, *service, *principal, *p;
141     int realmlen=0, fqdnlen=0, servicelen=0;
142     size_t principal_length;
143     gss_buffer_desc s_princ_buffer;
144
145     /* get all the required information from afpd */
146     if (uam_afpserver_option(obj, UAM_OPTION_KRB5REALM, (void*) &realm, &realmlen) < 0) 
147         return 1;
148     if (uam_afpserver_option(obj, UAM_OPTION_FQDN, (void*) &fqdn, &fqdnlen) < 0) 
149         return 1;
150     if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0)
151         return 1;
152
153     /* we need all the info, log error and return if one's missing */
154     if (!service || !servicelen || !fqdn || !fqdnlen || !realm || !realmlen) {
155         LOG(log_error, logtype_uams, 
156             "get_afpd_principal: could not retrieve required information from afpd.");
157         return 1;
158     }
159
160     /* allocate memory to hold the temporary principal string */
161     principal_length = servicelen + 1 + fqdnlen + 1 + realmlen + 1;
162     if ( NULL == (principal = (char*) malloc( principal_length)) ) {
163         LOG(log_error, logtype_uams,
164             "get_afpd_principal: out of memory allocating %u bytes", 
165             principal_length);
166         return 1;
167     }
168
169     /*
170      * Build the principal string.
171      * Format: 'service/fqdn@realm'
172      */
173     strlcpy( principal, service, principal_length);
174     strlcat( principal, "/", principal_length);
175
176     /* 
177      * The fqdn we get from afpd may contain a port.
178      * We need to strip the port from fqdn for principal.
179      */
180     p = strchr(fqdn, ':');
181     if (p)
182        *p = '\0';
183     strlcat( principal, fqdn, principal_length);
184     if (p)
185        *p = ':';
186     strlcat( principal, "@", principal_length);
187     strlcat( principal, realm, principal_length);
188
189     /*
190      * Import our principal into the gssapi internal representation
191      * stored in server_name.
192      */
193     s_princ_buffer.value = principal;
194     s_princ_buffer.length = strlen( principal ) + 1;
195
196     LOG(log_debug, logtype_uams, "get_afpd_principal: importing principal `%s'", principal);
197     major_status = gss_import_name( &minor_status,
198                     &s_princ_buffer,
199                     GSS_C_NO_OID,
200                     server_name );
201
202     /* 
203      * Get rid of malloc'ed memmory.
204      * Don't release the s_princ_buffer, we free principal instead.
205      */
206     free(principal);
207
208     if (major_status != GSS_S_COMPLETE) {
209         /* Importing our service principal failed, bail out. */
210         log_status( "import_principal", major_status, minor_status );
211         return 1;
212     }
213     return 0;
214 }
215
216
217 /* get the username */
218 static int get_client_username(char *username, int ulen, gss_name_t *client_name)
219 {
220     OM_uint32 major_status = 0, minor_status = 0;
221     gss_buffer_desc client_name_buffer;
222     char *p;
223     int namelen, ret=0;
224
225     /* 
226      * To extract the unix username, use gss_display_name on client_name.
227      * We do rely on gss_display_name returning a zero terminated string.
228      * The username returned contains the realm and possibly an instance.
229      * We only want the username for afpd, so we have to strip those from
230      * the username before copying it to afpd's buffer.
231      */
232
233     major_status = gss_display_name( &minor_status, *client_name,
234                                      &client_name_buffer, (gss_OID *)NULL );
235     if (major_status != GSS_S_COMPLETE) {
236         log_status( "display_name", major_status, minor_status );
237         return 1;
238     }
239
240     LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value);
241
242     /* chop off realm */
243     p = strchr( client_name_buffer.value, '@' );
244     if (p)
245         *p = 0;
246     /* FIXME: chop off instance? */
247     p = strchr( client_name_buffer.value, '/' );
248     if (p)
249         *p = 0;
250
251     /* check if this username fits into afpd's username buffer */
252     namelen = strlen(client_name_buffer.value);
253     if ( namelen >= ulen ) {
254         /* The username is too long for afpd's buffer, bail out */
255         LOG(log_error, logtype_uams,
256             "get_client_username: username `%s' too long", client_name_buffer.value);
257         ret = 1;
258     }
259     else {
260         /* copy stripped username to afpd's buffer */
261         strlcpy(username, client_name_buffer.value, ulen);
262     }
263
264     /* we're done with client_name_buffer, release it */
265     gss_release_buffer(&minor_status, &client_name_buffer );
266
267     return ret;
268 }
269
270 /* wrap afpd's sessionkey */
271 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
272 {
273     OM_uint32 status = 0;
274     int ret=0;
275     gss_buffer_desc sesskey_buff, wrap_buff;
276
277     /* 
278      * gss_wrap afpd's session_key.
279      * This is needed fo OS X 10.3 clients. They request this information
280      * with type 8 (kGetKerberosSessionKey) on FPGetSession.
281      * See AFP 3.1 specs, page 77.
282      */
283
284     sesskey_buff.value = sinfo->sessionkey;
285     sesskey_buff.length = sinfo->sessionkey_len;
286
287     /* gss_wrap the session key with the default mechanism. 
288        Require both confidentiality and integrity services */
289     gss_wrap (&status, context, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff);
290
291     if ( status != GSS_S_COMPLETE) {
292         LOG(log_error, logtype_uams, "wrap_sessionkey: failed to gss_wrap sessionkey");
293         log_status( "GSS wrap", 0, status );
294         return 1;
295     }
296
297     /* store the wrapped session key in afpd's session_info struct */
298     if ( NULL == (sinfo->cryptedkey = malloc ( wrap_buff.length )) ) {
299         LOG(log_error, logtype_uams, 
300             "wrap_sessionkey: out of memory tyring to allocate %u bytes", 
301             wrap_buff.length);
302         ret = 1;
303     } else {
304         /* cryptedkey is binary data */
305         memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
306         sinfo->cryptedkey_len = wrap_buff.length;
307     }
308
309     /* we're done with buffer, release */
310     gss_release_buffer( &status, &wrap_buff );
311
312     return ret;
313 }
314
315 /*-------------*/
316 static int acquire_credentials (gss_name_t *server_name, gss_cred_id_t *server_creds)
317 {
318     OM_uint32 major_status = 0, minor_status = 0;
319
320     LOG(log_debug, logtype_uams,
321         "acquire credentials: acquiring credentials (uid = %d, keytab = %s)",
322         (int)geteuid(), getenv( "KRB5_KTNAME") );
323    /*
324     * Acquire credentials usable for accepting context negotiations.
325     * Credentials are for server_name, have an indefinite lifetime,
326     * have no specific mechanisms, are to be used for accepting context 
327     * negotiations and are to be placed in server_creds. 
328     * We don't care about the mechanisms or about the time for which they are valid.
329     */
330     major_status = gss_acquire_cred( &minor_status, *server_name,
331                         GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
332                         server_creds, NULL, NULL );
333
334     if (major_status != GSS_S_COMPLETE) {
335         log_status( "acquire_cred", major_status, minor_status );
336         return 1;
337     }
338    
339     return 0;
340 }
341
342 /*-------------*/
343 static int accept_sec_context (gss_ctx_id_t *context, gss_cred_id_t server_creds, 
344                            gss_buffer_desc *ticket_buffer, gss_name_t *client_name,
345                            gss_buffer_desc *authenticator_buff)
346 {
347     OM_uint32 major_status = 0, minor_status = 0, ret_flags;
348
349     /* Initialize autheticator buffer. */
350     authenticator_buff->length = 0;
351     authenticator_buff->value = NULL;
352
353     LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)", 
354         ticket_buffer->length);
355
356     /* 
357      * Try to accept the secondary context using the tocken in ticket_buffer.
358      * We don't care about the mechanisms used, nor for the time. 
359      * We don't act as a proxy either.
360      */
361     major_status = gss_accept_sec_context( &minor_status, context,
362                         server_creds, ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
363                         client_name, NULL, authenticator_buff,
364                         &ret_flags, NULL, NULL );
365
366     if (major_status != GSS_S_COMPLETE) {
367         log_status( "accept_sec_context", major_status, minor_status );
368         return 1;
369     }
370     log_ctx_flags( ret_flags );
371     return 0;
372 }
373     
374
375 /* return 0 on success */
376 static int do_gss_auth(void *obj, char *ibuf, int ticket_len,
377                  char *rbuf, int *rbuflen, char *username, int ulen,
378                  struct session_info *sinfo ) 
379 {
380     OM_uint32 status = 0;
381     gss_name_t server_name, client_name;
382     gss_cred_id_t server_creds;
383     gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
384     gss_buffer_desc ticket_buffer, authenticator_buff;
385     int ret = 0;
386
387     /* import our principal name from afpd */
388     if (get_afpd_principal(obj, &server_name) != 0) {
389         return 1;
390     }
391     log_principal(server_name);
392   
393     /* Now we have to acquire our credentials */
394     if ((ret = acquire_credentials (&server_name, &server_creds)))
395         goto cleanup_vars;
396
397     /* 
398      * Try to accept the secondary context, using the ticket/token the 
399      * client sent us. Ticket is stored at current ibuf position. 
400      * Don't try to release ticket_buffer later, it points into ibuf! 
401      */
402     ticket_buffer.length = ticket_len;
403     ticket_buffer.value = ibuf;
404
405     ret = accept_sec_context (&context_handle, server_creds, &ticket_buffer, 
406                           &client_name, &authenticator_buff);
407
408     if (!ret) {
409         /* We succesfully acquired the secondary context, now get the 
410            username for afpd and gss_wrap the sessionkey */
411         if ( 0 == (ret = get_client_username(username, ulen, &client_name)) ) {
412             ret = wrap_sessionkey(context_handle, sinfo);
413         }
414
415         if (!ret) {
416             /* FIXME: Is copying the authenticator really necessary? 
417                Where is this documented? */
418             u_int16_t auth_len = htons( authenticator_buff.length );
419
420             /* copy the authenticator length into the reply buffer */
421             memcpy( rbuf, &auth_len, sizeof(auth_len) );
422             *rbuflen += sizeof(auth_len);
423             rbuf += sizeof(auth_len);
424
425             /* copy the authenticator value into the reply buffer */
426             memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
427             *rbuflen += authenticator_buff.length;
428         }
429
430         /* Clean up after ourselves */
431         gss_release_name( &status, &client_name );
432         if ( authenticator_buff.value)
433             gss_release_buffer( &status, &authenticator_buff );
434
435         gss_delete_sec_context( &status, &context_handle, NULL );
436     } 
437     gss_release_cred( &status, &server_creds );
438
439 cleanup_vars:
440     gss_release_name( &status, &server_name );
441     
442     return ret;
443 }
444
445 /* -------------------------- */
446 static int gss_login(void *obj, struct passwd **uam_pwd,
447                      char *ibuf, int ibuflen,
448                      char *rbuf, int *rbuflen)
449 {
450
451     u_int16_t  temp16;
452
453     *rbuflen = 0;
454
455     /* The reply contains a two-byte ID value - note 
456      * that Apple's implementation seems to always return 1 as well
457      */
458     temp16 = htons( 1 );
459     memcpy(rbuf, &temp16, sizeof(temp16));
460     *rbuflen += sizeof(temp16);
461     return AFPERR_AUTHCONT;
462 }
463
464 static int gss_logincont(void *obj, struct passwd **uam_pwd,
465                      char *ibuf, int ibuflen,
466                      char *rbuf, int *rbuflen)
467 {
468     struct passwd *pwd = NULL;
469     u_int16_t login_id;
470     char *username;
471     u_int16_t ticket_len;
472     char *p;
473     int rblen;
474     int userlen;
475     struct session_info *sinfo;
476
477     /* Apple's AFP 3.1 documentation specifies that this command
478      * takes the following format:
479      * pad (byte)
480      * id returned in LoginExt response (u_int16_t)
481      * username (format unspecified) padded, when necessary, to end on an even boundary
482      * ticket length (u_int16_t)
483      * ticket
484      */
485
486     /* Observation of AFP clients in the wild indicate that the actual
487      * format of this request is as follows:
488      * pad (byte) [consumed before login_ext is called]
489      * ?? (byte) - always observed to be 0
490      * id returned in LoginExt response (u_int16_t)
491      * username, encoding unspecified, null terminated C string, 
492      *   padded when the terminating null is an even numbered byte.
493      *   The packet is formated such that the username begins on an 
494      *   odd numbered byte. Eg if the username is 3 characters and the
495      *   terminating null makes 4, expect to pad the the result.
496      *   The encoding of this string is unknown.
497      * ticket length (u_int16_t)
498      * ticket
499      */
500
501     rblen = *rbuflen = 0;
502
503     if (ibuflen < 3) {
504         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet");
505         return AFPERR_PARAM;
506     }
507     ibuf++, ibuflen--; /* ?? */
508
509     /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
510     memcpy( &login_id, ibuf, sizeof(login_id) );
511     ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
512     login_id = ntohs( login_id );
513
514     /* get the username buffer from apfd */
515     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
516         return AFPERR_MISC;
517
518     /* get the session_info structure from afpd. We need the session key */
519     if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, (void *)&sinfo, NULL) < 0)
520         return AFPERR_MISC;
521
522     if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
523         /* Should never happen. Most likely way too old afpd version */
524         LOG(log_info, logtype_uams, "internal error: afpd's sessionkey not set");
525         return AFPERR_MISC;
526     }
527
528     /* We skip past the 'username' parameter because all that matters is the ticket */
529     p = ibuf;
530     while( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
531     if (ibuflen < 4) {
532         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
533         return AFPERR_PARAM;
534     }
535
536     ibuf++, ibuflen--; /* null termination */
537
538     if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
539
540     LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
541
542     /* get the length of the ticket the client sends us */
543     memcpy(&ticket_len, ibuf, sizeof(ticket_len));
544     ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
545     ticket_len = ntohs( ticket_len );
546
547     /* a little bounds checking */
548     if (ticket_len > ibuflen) {
549         LOG(log_info, logtype_uams,
550             "uams_gss.c :LoginCont: invalid ticket length (%u > %u)", ticket_len, ibuflen);
551         return AFPERR_PARAM;
552     }
553
554     /* now try to authenticate */
555     if (!do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
556         /* We use the username we got back from the gssapi client_name.
557            Should we compare this to the username the client sent in the clear?
558            We know the character encoding of the cleartext username (UTF8), what
559            encoding is the gssapi name in? */
560         if((pwd = uam_getname( obj, username, userlen )) == NULL) {
561             LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
562             return AFPERR_PARAM;
563         }
564         if (uam_checkuser(pwd) < 0) {
565             LOG(log_info, logtype_uams, "%s not a valid user", username);
566             return AFPERR_NOTAUTH;
567         }
568         *rbuflen = rblen;
569         *uam_pwd = pwd;
570         return AFP_OK;
571     } else {
572         LOG(log_info, logtype_uams, "do_gss_auth failed" );
573         *rbuflen = 0;
574         return AFPERR_MISC;
575     }
576 }
577
578 /*
579  * For the krb5 uam, this function only needs to return a two-byte
580  * login-session id. None of the data provided by the client up to this
581  * point is trustworthy as we'll have a signed ticket to parse in logincont.
582  */
583 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
584                      char *ibuf, int ibuflen,
585                      char *rbuf, int *rbuflen)
586 {
587     u_int16_t  temp16;
588
589     *rbuflen = 0;
590
591     /* The reply contains a two-byte ID value - note 
592      * that Apple's implementation seems to always return 1 as well
593      */
594     temp16 = htons( 1 );
595     memcpy(rbuf, &temp16, sizeof(temp16));
596     *rbuflen += sizeof(temp16);
597     return AFPERR_AUTHCONT;
598 }
599
600 /* logout */
601 static void gss_logout() {
602 }
603
604 int uam_setup(const char *path)
605 {
606     if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2", 
607                    gss_login, gss_logincont, gss_logout, gss_login_ext) < 0)
608     if (uam_register(UAM_SERVER_LOGIN, path, "Client Krb v2", 
609                    gss_login, gss_logincont, gss_logout) < 0)
610         return -1;
611
612   return 0;
613 }
614
615 static void uam_cleanup(void)
616 {
617   uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
618 }
619
620 UAM_MODULE_EXPORT struct uam_export uams_gss = {
621   UAM_MODULE_SERVER,
622   UAM_MODULE_VERSION,
623   uam_setup, uam_cleanup
624 };
625 #endif