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