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