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