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 <stdbool.h> 14#include <stdint.h> 15#include <stdlib.h> 16#include <string.h> 17#include <arpa/inet.h> 18 19#include <atalk/logger.h> 20#include <atalk/afp.h> 21#include <atalk/uam.h> 22#include <atalk/util.h> 23#include <atalk/compat.h> 24 25/* Kerberos includes */ 26#ifdef HAVE_GSSAPI_GSSAPI_H 27#include <gssapi/gssapi.h> 28#else 29#include <gssapi.h> 30#endif // HAVE_GSSAPI_GSSAPI_H 31 32#define LOG_UAMS(log_level, ...) \ 33 LOG(log_level, logtype_uams, __VA_ARGS__) 34 35#define LOG_LOGINCONT(log_level, ...) \ 36 LOG_UAMS(log_level, "FPLoginCont: " __VA_ARGS__) 37 38static void log_status(char *s, 39 OM_uint32 major_status, 40 OM_uint32 minor_status) 41{ 42 gss_buffer_desc msg = GSS_C_EMPTY_BUFFER; 43 OM_uint32 min_status, maj_status; 44 OM_uint32 maj_ctx = 0, min_ctx = 0; 45 46 while (1) { 47 maj_status = gss_display_status( &min_status, major_status, 48 GSS_C_GSS_CODE, GSS_C_NULL_OID, 49 &maj_ctx, &msg ); 50 LOG_UAMS(log_error, "%s %.*s (error %s)", 51 s, msg.length, msg.value, strerror(errno)); 52 gss_release_buffer(&min_status, &msg); 53 54 if (!maj_ctx) 55 break; 56 } 57 58 while (1) { 59 maj_status = gss_display_status( &min_status, minor_status, 60 GSS_C_MECH_CODE, GSS_C_NULL_OID, 61 &min_ctx, &msg ); 62 LOG_UAMS(log_error, "%s %.*s (error %s)", 63 s, msg.length, msg.value, strerror(errno)); 64 gss_release_buffer(&min_status, &msg); 65 66 if (!min_ctx) 67 break; 68 } 69} 70 71static void log_ctx_flags(OM_uint32 flags) 72{ 73 if (flags & GSS_C_DELEG_FLAG) 74 LOG_LOGINCONT(log_debug, "context flag: GSS_C_DELEG_FLAG"); 75 if (flags & GSS_C_MUTUAL_FLAG) 76 LOG_LOGINCONT(log_debug, "context flag: GSS_C_MUTUAL_FLAG"); 77 if (flags & GSS_C_REPLAY_FLAG) 78 LOG_LOGINCONT(log_debug, "context flag: GSS_C_REPLAY_FLAG"); 79 if (flags & GSS_C_SEQUENCE_FLAG) 80 LOG_LOGINCONT(log_debug, "context flag: GSS_C_SEQUENCE_FLAG"); 81 if (flags & GSS_C_CONF_FLAG) 82 LOG_LOGINCONT(log_debug, "context flag: GSS_C_CONF_FLAG"); 83 if (flags & GSS_C_INTEG_FLAG) 84 LOG_LOGINCONT(log_debug, "context flag: GSS_C_INTEG_FLAG"); 85} 86 87static void log_service_name(gss_ctx_id_t context) 88{ 89 OM_uint32 major_status = 0, minor_status = 0; 90 gss_name_t service_name; 91 gss_buffer_desc service_name_buffer; 92 93 major_status = gss_inquire_context(&minor_status, 94 context, 95 NULL, 96 &service_name, 97 NULL, 98 NULL, 99 NULL, 100 NULL, 101 NULL); 102 if (major_status != GSS_S_COMPLETE) { 103 log_status("gss_inquire_context", major_status, minor_status); 104 return; 105 } 106 107 major_status = gss_display_name(&minor_status, 108 service_name, 109 &service_name_buffer, 110 NULL); 111 if (major_status == GSS_S_COMPLETE) { 112 LOG_LOGINCONT(log_debug, 113 "service principal is `%s'", 114 service_name_buffer.value); 115 116 gss_release_buffer(&minor_status, &service_name_buffer); 117 } else 118 log_status("gss_display_name", major_status, minor_status); 119 120 gss_release_name(&minor_status, &service_name); 121} 122 123static int get_client_username(char *username, 124 int ulen, 125 gss_name_t *client_name) 126{ 127 OM_uint32 major_status = 0, minor_status = 0; 128 gss_buffer_desc client_name_buffer; 129 char *p; 130 int ret = 0; 131 132 /* 133 * To extract the unix username, use gss_display_name on client_name. 134 * We do rely on gss_display_name returning a zero terminated string. 135 * The username returned contains the realm and possibly an instance. 136 * We only want the username for afpd, so we have to strip those from 137 * the username before copying it to afpd's buffer. 138 */ 139 140 major_status = gss_display_name(&minor_status, 141 *client_name, 142 &client_name_buffer, 143 NULL); 144 if (major_status != GSS_S_COMPLETE) { 145 log_status("gss_display_name", major_status, minor_status); 146 return 1; 147 } 148 149 LOG_LOGINCONT(log_debug, 150 "user principal is `%s'", 151 client_name_buffer.value); 152 153 /* chop off realm */ 154 p = strchr(client_name_buffer.value, '@'); 155 if (p) 156 *p = 0; 157 /* FIXME: chop off instance? */ 158 p = strchr(client_name_buffer.value, '/'); 159 if (p) 160 *p = 0; 161 162 /* check if this username fits into afpd's username buffer */ 163 size_t cnblen = strlen(client_name_buffer.value); 164 if (cnblen >= ulen) { 165 /* The username is too long for afpd's buffer, bail out */ 166 LOG_LOGINCONT(log_info, 167 "username `%s' too long (%d)", 168 client_name_buffer.value, cnblen); 169 ret = 1; 170 } else { 171 /* copy stripped username to afpd's buffer */ 172 strlcpy(username, client_name_buffer.value, ulen); 173 } 174 175 gss_release_buffer(&minor_status, &client_name_buffer); 176 177 return ret; 178} 179 180/* wrap afpd's sessionkey */ 181static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo) 182{ 183 OM_uint32 major_status = 0, minor_status = 0; 184 gss_buffer_desc sesskey_buff, wrap_buff; 185 int ret = 0; 186 187 /* 188 * gss_wrap afpd's session_key. 189 * This is needed fo OS X 10.3 clients. They request this information 190 * with type 8 (kGetKerberosSessionKey) on FPGetSession. 191 * See AFP 3.1 specs, page 77. 192 */ 193 sesskey_buff.value = sinfo->sessionkey; 194 sesskey_buff.length = sinfo->sessionkey_len; 195 196 /* gss_wrap the session key with the default mechanism. 197 Require both confidentiality and integrity services */ 198 major_status = gss_wrap(&minor_status, 199 context, 200 true, 201 GSS_C_QOP_DEFAULT, 202 &sesskey_buff, 203 NULL, 204 &wrap_buff); 205 206 if (major_status != GSS_S_COMPLETE) { 207 log_status("gss_wrap", major_status, minor_status); 208 return 1; 209 } 210 211 /* store the wrapped session key in afpd's session_info struct */ 212 if (NULL == (sinfo->cryptedkey = malloc(wrap_buff.length))) { 213 LOG_UAMS(log_error, 214 "wrap_sessionkey: out of memory tyring to allocate %u bytes", 215 wrap_buff.length); 216 ret = 1; 217 } else { 218 /* cryptedkey is binary data */ 219 memcpy(sinfo->cryptedkey, wrap_buff.value, wrap_buff.length); 220 sinfo->cryptedkey_len = wrap_buff.length; 221 } 222 223 /* we're done with buffer, release */ 224 gss_release_buffer(&minor_status, &wrap_buff); 225 226 return ret; 227} 228 229static int accept_sec_context(gss_ctx_id_t *context, 230 gss_buffer_desc *ticket_buffer, 231 gss_name_t *client_name, 232 gss_buffer_desc *authenticator_buff) 233{ 234 OM_uint32 major_status = 0, minor_status = 0, flags = 0; 235 236 /* Initialize autheticator buffer. */ 237 authenticator_buff->length = 0; 238 authenticator_buff->value = NULL; 239 240 LOG_LOGINCONT(log_debug, 241 "accepting context (ticketlen: %u)", 242 ticket_buffer->length); 243 244 /* 245 * Try to accept the secondary context using the token in ticket_buffer. 246 * We don't care about the principals or mechanisms used, nor for the time. 247 * We don't act as a proxy either. 248 */ 249 major_status = gss_accept_sec_context(&minor_status, 250 context, 251 GSS_C_NO_CREDENTIAL, 252 ticket_buffer, 253 GSS_C_NO_CHANNEL_BINDINGS, 254 client_name, 255 NULL, 256 authenticator_buff, 257 &flags, 258 NULL, 259 NULL); 260 261 if (major_status != GSS_S_COMPLETE) { 262 log_status("gss_accept_sec_context", major_status, minor_status); 263 return 1; 264 } 265 266 log_ctx_flags(flags); 267 return 0; 268} 269 270static int do_gss_auth(void *obj, 271 char *ibuf, size_t ibuflen, 272 char *rbuf, int *rbuflen, 273 char *username, size_t ulen, 274 struct session_info *sinfo ) 275{ 276 OM_uint32 status = 0; 277 gss_name_t client_name; 278 gss_ctx_id_t context = GSS_C_NO_CONTEXT; 279 gss_buffer_desc ticket_buffer, authenticator_buff; 280 int ret = 0; 281 282 /* 283 * Try to accept the secondary context, using the ticket/token the 284 * client sent us. Ticket is stored at current ibuf position. 285 * Don't try to release ticket_buffer later, it points into ibuf! 286 */ 287 ticket_buffer.length = ibuflen; 288 ticket_buffer.value = ibuf; 289 290 if ((ret = accept_sec_context(&context, 291 &ticket_buffer, 292 &client_name, 293 &authenticator_buff))) 294 return ret; 295 log_service_name(context); 296 297 /* We succesfully acquired the secondary context, now get the 298 username for afpd and gss_wrap the sessionkey */ 299 if ((ret = get_client_username(username, ulen, &client_name))) 300 goto cleanup_client_name; 301 302 if ((ret = wrap_sessionkey(context, sinfo))) 303 goto cleanup_client_name; 304 305 /* Authenticated, construct the reply using: 306 * authenticator length (uint16_t) 307 * authenticator 308 */ 309 /* copy the authenticator length into the reply buffer */ 310 uint16_t auth_len = htons(authenticator_buff.length); 311 memcpy(rbuf, &auth_len, sizeof(auth_len)); 312 *rbuflen += sizeof(auth_len); 313 rbuf += sizeof(auth_len); 314 315 /* copy the authenticator value into the reply buffer */ 316 memcpy(rbuf, authenticator_buff.value, authenticator_buff.length); 317 *rbuflen += authenticator_buff.length; 318 319cleanup_client_name: 320 gss_release_name(&status, &client_name); 321 gss_release_buffer(&status, &authenticator_buff); 322 gss_delete_sec_context(&status, &context, NULL); 323 324 return ret; 325} 326 327/* -------------------------- */ 328 329/* 330 * For the gss uam, this function only needs to return a two-byte 331 * login-session id. None of the data provided by the client up to this 332 * point is trustworthy as we'll have a signed ticket to parse in logincont. 333 */ 334static int gss_login(void *obj, 335 struct passwd **uam_pwd, 336 char *ibuf, size_t ibuflen, 337 char *rbuf, size_t *rbuflen) 338{ 339 *rbuflen = 0; 340 341 /* The reply contains a two-byte ID value - note 342 * that Apple's implementation seems to always return 1 as well 343 */ 344 uint16_t temp16 = htons(1); 345 memcpy(rbuf, &temp16, sizeof(temp16)); 346 *rbuflen += sizeof(temp16); 347 348 return AFPERR_AUTHCONT; 349} 350 351static int gss_logincont(void *obj, 352 struct passwd **uam_pwd, 353 char *ibuf, size_t ibuflen, 354 char *rbuf, size_t *rbuflen) 355{ 356 struct passwd *pwd = NULL; 357 uint16_t login_id; 358 char *username; 359 uint16_t ticket_len; 360 char *p; 361 int rblen; 362 size_t userlen; 363 struct session_info *sinfo; 364 365 /* Apple's AFP 3.1 documentation specifies that this command 366 * takes the following format: 367 * pad (byte) 368 * id returned in LoginExt response (uint16_t) 369 * username (format unspecified) 370 * padded, when necessary, to end on an even boundary 371 * ticket length (uint16_t) 372 * ticket 373 */ 374 375 /* Observation of AFP clients in the wild indicate that the actual 376 * format of this request is as follows: 377 * pad (byte) [consumed before login_ext is called] 378 * ?? (byte) - always observed to be 0 379 * id returned in LoginExt response (uint16_t) 380 * username, encoding unspecified, null terminated C string, 381 * padded when the terminating null is an even numbered byte. 382 * The packet is formated such that the username begins on an 383 * odd numbered byte. Eg if the username is 3 characters and the 384 * terminating null makes 4, expect to pad the the result. 385 * The encoding of this string is unknown. 386 * ticket length (uint16_t) 387 * ticket 388 */ 389 390 rblen = *rbuflen = 0; 391 392 if (ibuflen < 1 +sizeof(login_id)) { 393 LOG_LOGINCONT(log_info, "received incomplete packet"); 394 return AFPERR_PARAM; 395 } 396 ibuf++, ibuflen--; /* ?? */ 397 398 /* 2 byte ID from LoginExt -- always '00 01' in this implementation */ 399 memcpy(&login_id, ibuf, sizeof(login_id)); 400 ibuf += sizeof(login_id), ibuflen -= sizeof(login_id); 401 login_id = ntohs(login_id); 402 403 /* get the username buffer from apfd */ 404 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, &username, &userlen) < 0) 405 return AFPERR_MISC; 406 407 /* get the session_info structure from afpd. We need the session key */ 408 if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, &sinfo, NULL) < 0) 409 return AFPERR_MISC; 410 411 if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) { 412 /* Should never happen. Most likely way too old afpd version */ 413 LOG_LOGINCONT(log_error, "internal error: afpd's sessionkey not set"); 414 return AFPERR_MISC; 415 } 416 417 /* We skip past the 'username' parameter because all that matters is the ticket */ 418 p = ibuf; 419 while ( *ibuf && ibuflen ) { ibuf++, ibuflen--; } 420 if (ibuflen < 4) { 421 LOG_LOGINCONT(log_info, "user is %s, no ticket", p); 422 return AFPERR_PARAM; 423 } 424 425 ibuf++, ibuflen--; /* null termination */ 426 427 if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */ 428 429 LOG_LOGINCONT(log_debug, "client thinks user is %s", p); 430 431 /* get the length of the ticket the client sends us */ 432 memcpy(&ticket_len, ibuf, sizeof(ticket_len)); 433 ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len); 434 ticket_len = ntohs(ticket_len); 435 436 /* a little bounds checking */ 437 if (ticket_len > ibuflen) { 438 LOG_LOGINCONT(log_info, 439 "invalid ticket length (%u > %u)", 440 ticket_len, ibuflen); 441 return AFPERR_PARAM; 442 } 443 444 /* now try to authenticate */ 445 if (do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) { 446 LOG_LOGINCONT(log_info, "do_gss_auth() failed" ); 447 *rbuflen = 0; 448 return AFPERR_MISC; 449 } 450 451 /* We use the username we got back from the gssapi client_name. 452 Should we compare this to the username the client sent in the clear? 453 We know the character encoding of the cleartext username (UTF8), what 454 encoding is the gssapi name in? */ 455 if ((pwd = uam_getname( obj, username, userlen )) == NULL) { 456 LOG_LOGINCONT(log_info, "uam_getname() failed for %s", username); 457 return AFPERR_NOTAUTH; 458 } 459 if (uam_checkuser(pwd) < 0) { 460 LOG_LOGINCONT(log_info, "`%s'' not a valid user", username); 461 return AFPERR_NOTAUTH; 462 } 463 464 *rbuflen = rblen; 465 *uam_pwd = pwd; 466 return AFP_OK; 467} 468 469/* logout */ 470static void gss_logout() { 471} 472 473static int gss_login_ext(void *obj, 474 char *uname, 475 struct passwd **uam_pwd, 476 char *ibuf, size_t ibuflen, 477 char *rbuf, size_t *rbuflen) 478{ 479 return gss_login(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen); 480} 481 482static int uam_setup(const char *path) 483{ 484 return uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2", 485 gss_login, gss_logincont, gss_logout, gss_login_ext); 486} 487 488static void uam_cleanup(void) 489{ 490 uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2"); 491} 492 493UAM_MODULE_EXPORT struct uam_export uams_gss = { 494 UAM_MODULE_SERVER, 495 UAM_MODULE_VERSION, 496 uam_setup, 497 uam_cleanup 498}; 499