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.
11 #endif /* HAVE_CONFIG_H */
17 #endif /* HAVE_UNISTD_H */
20 #include <arpa/inet.h>
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>
28 /* Kerberos includes */
34 #if HAVE_GSSAPI_GSSAPI_H
35 #include <gssapi/gssapi.h>
38 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
39 #include <gssapi/gssapi_generic.h>
42 #if HAVE_GSSAPI_GSSAPI_KRB5_H
43 #include <gssapi/gssapi_krb5.h>
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
60 #define MIN(a, b) ((a > b) ? b : a)
62 static void log_status( char *s, OM_uint32 major_status,
63 OM_uint32 minor_status )
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;
70 maj_status = gss_display_status( &min_status, major_status,
71 GSS_C_GSS_CODE, GSS_C_NULL_OID,
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);
81 maj_status = gss_display_status( &min_status, minor_status,
82 GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
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);
94 static void log_ctx_flags( OM_uint32 flags )
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" );
112 static void log_principal(gss_name_t server_name)
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;
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 );
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)
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;
135 /* get information from afpd */
136 if (uam_afpserver_option(obj, UAM_OPTION_FQDN, (void*) &fqdn, &fqdnlen) < 0)
138 LOG(log_debug, logtype_uams, "get_afpd_principal: fqdn: %s", fqdn);
140 if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0)
142 LOG(log_debug, logtype_uams, "get_afpd_principal: service: %s", service);
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)");
149 *server_name = GSS_C_NO_NAME;
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",
163 * Build the principal string.
164 * Format: 'service@fqdn'
166 strlcpy( principal, service, principal_length);
167 strlcat( principal, "@", principal_length);
170 * The fqdn we get from afpd may contain a port.
171 * We need to strip the port from fqdn for principal.
173 if ((p = strchr(fqdn, ':')))
176 strlcat( principal, fqdn, principal_length);
180 * Import our principal into the gssapi internal representation
181 * stored in server_name.
183 s_princ_buffer.value = principal;
184 s_princ_buffer.length = strlen( principal ) + 1;
186 LOG(log_debug, logtype_uams, "get_afpd_principal: importing principal `%s'", principal);
187 major_status = gss_import_name( &minor_status,
189 GSS_C_NT_HOSTBASED_SERVICE,
193 * Get rid of malloc'ed memmory.
194 * Don't release the s_princ_buffer, we free principal instead.
198 if (major_status != GSS_S_COMPLETE) {
199 /* Importing our service principal failed, bail out. */
200 log_status( "import_principal", major_status, minor_status );
207 /* get the username */
208 static int get_client_username(char *username, int ulen, gss_name_t *client_name)
210 OM_uint32 major_status = 0, minor_status = 0;
211 gss_buffer_desc client_name_buffer;
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.
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 );
230 LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value);
233 p = strchr( client_name_buffer.value, '@' );
236 /* FIXME: chop off instance? */
237 p = strchr( client_name_buffer.value, '/' );
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);
250 /* copy stripped username to afpd's buffer */
251 strlcpy(username, client_name_buffer.value, ulen);
254 /* we're done with client_name_buffer, release it */
255 gss_release_buffer(&minor_status, &client_name_buffer );
260 /* wrap afpd's sessionkey */
261 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
263 OM_uint32 status = 0;
265 gss_buffer_desc sesskey_buff, wrap_buff;
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.
274 sesskey_buff.value = sinfo->sessionkey;
275 sesskey_buff.length = sinfo->sessionkey_len;
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);
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 );
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",
294 /* cryptedkey is binary data */
295 memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
296 sinfo->cryptedkey_len = wrap_buff.length;
299 /* we're done with buffer, release */
300 gss_release_buffer( &status, &wrap_buff );
306 static int acquire_credentials (gss_name_t *server_name, gss_cred_id_t *server_creds)
308 OM_uint32 major_status = 0, minor_status = 0;
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);
316 LOG(log_debug, logtype_uams,
317 "acquire credentials: acquiring credentials (uid = %d) - $KRB5_KTNAME not found in env",
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.
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 );
331 if (major_status != GSS_S_COMPLETE) {
332 log_status( "acquire_cred", major_status, minor_status );
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)
344 OM_uint32 major_status = 0, minor_status = 0, ret_flags;
346 /* Initialize autheticator buffer. */
347 authenticator_buff->length = 0;
348 authenticator_buff->value = NULL;
350 LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)",
351 ticket_buffer->length);
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.
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 );
363 if (major_status != GSS_S_COMPLETE) {
364 log_status( "accept_sec_context", major_status, minor_status );
367 log_ctx_flags( ret_flags );
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 )
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;
384 /* import our principal name from afpd */
385 if (get_afpd_principal(obj, &server_name) != 0) {
388 log_principal(server_name);
390 /* Now we have to acquire our credentials */
391 if ((ret = acquire_credentials (&server_name, &server_creds)))
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!
399 ticket_buffer.length = ticket_len;
400 ticket_buffer.value = ibuf;
402 ret = accept_sec_context (&context_handle, server_creds, &ticket_buffer,
403 &client_name, &authenticator_buff);
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);
413 /* FIXME: Is copying the authenticator really necessary?
414 Where is this documented? */
415 uint16_t auth_len = htons( authenticator_buff.length );
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);
422 /* copy the authenticator value into the reply buffer */
423 memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
424 *rbuflen += authenticator_buff.length;
427 /* Clean up after ourselves */
428 gss_release_name( &status, &client_name );
429 if ( authenticator_buff.value)
430 gss_release_buffer( &status, &authenticator_buff );
432 gss_delete_sec_context( &status, &context_handle, NULL );
434 gss_release_cred( &status, &server_creds );
437 gss_release_name( &status, &server_name );
442 /* -------------------------- */
443 static int gss_login(void *obj, struct passwd **uam_pwd,
444 char *ibuf, size_t ibuflen,
445 char *rbuf, size_t *rbuflen)
452 /* The reply contains a two-byte ID value - note
453 * that Apple's implementation seems to always return 1 as well
456 memcpy(rbuf, &temp16, sizeof(temp16));
457 *rbuflen += sizeof(temp16);
458 return AFPERR_AUTHCONT;
461 static int gss_logincont(void *obj, struct passwd **uam_pwd,
462 char *ibuf, size_t ibuflen,
463 char *rbuf, size_t *rbuflen)
465 struct passwd *pwd = NULL;
472 struct session_info *sinfo;
474 /* Apple's AFP 3.1 documentation specifies that this command
475 * takes the following format:
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)
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)
498 rblen = *rbuflen = 0;
500 if (ibuflen < 1 +sizeof(login_id)) {
501 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet");
504 ibuf++, ibuflen--; /* ?? */
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 );
511 /* get the username buffer from apfd */
512 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
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)
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");
525 /* We skip past the 'username' parameter because all that matters is the ticket */
527 while( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
529 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
533 ibuf++, ibuflen--; /* null termination */
535 if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
537 LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
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 );
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);
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;
561 if (uam_checkuser(pwd) < 0) {
562 LOG(log_info, logtype_uams, "%s not a valid user", username);
563 return AFPERR_NOTAUTH;
569 LOG(log_info, logtype_uams, "do_gss_auth failed" );
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.
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)
588 /* The reply contains a two-byte ID value - note
589 * that Apple's implementation seems to always return 1 as well
592 memcpy(rbuf, &temp16, sizeof(temp16));
593 *rbuflen += sizeof(temp16);
594 return AFPERR_AUTHCONT;
598 static void gss_logout() {
601 int uam_setup(const char *path)
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)
612 static void uam_cleanup(void)
614 uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
617 UAM_MODULE_EXPORT struct uam_export uams_gss = {
620 uam_setup, uam_cleanup