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