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 */ 29char *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 76static 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 108static 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 126static 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 */ 141static 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 */ 220static 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 */ 273static 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/*-------------*/ 318static 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/*-------------*/ 352static 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 */ 385static 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 448cleanup_vars: 449 gss_release_name( &status, &server_name ); 450 451 return ret; 452} 453 454/* -------------------------- */ 455static 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 473static 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 */ 592static 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 */ 610static void gss_logout() { 611} 612 613int 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 624static void uam_cleanup(void) 625{ 626 uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2"); 627} 628 629UAM_MODULE_EXPORT struct uam_export uams_gss = { 630 UAM_MODULE_SERVER, 631 UAM_MODULE_VERSION, 632 uam_setup, uam_cleanup 633}; 634