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