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