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