1/* 2 * Copyright (c) 2000-2009 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24#include <SystemConfiguration/SystemConfiguration.h> 25#include <SystemConfiguration/SCSchemaDefinitions.h> 26#include <CoreServices/CoreServices.h> 27#include <CoreServices/CoreServicesPriv.h> 28#include <netdb.h> 29#include <syslog.h> 30#include "webdavlib.h" 31 32#define STREAM_EVENT_BUFSIZE 4096 33 34// This limits how many times a request will be retried 35// due to receiving EAGAIN from the handleXXXError() routines. 36#define WEBDAVLIB_MAX_AGAIN_COUNT 10 37 38// This is what we send as the User Agent header 39#define WEBAVLIB_USER_AGENT_STRING "WebDAVLib/1.3" 40 41/* Macro to simplify common CFRelease usage */ 42#define CFReleaseNull(obj) do { if(obj != NULL) { CFRelease(obj); obj = NULL; } } while (0) 43 44// Context for the callbacks 45enum CheckAuthCallbackStatus {CheckAuthInprogress = 0, CheckAuthCallbackDone = 1, CheckAuthCallbackStreamError = 2, 46 CheckAuthRedirection = 3}; 47struct callback_ctx { 48 CFMutableDataRef theData; // buffer for the reply message 49 CFHTTPMessageRef response; // holds the response message 50 CFMutableDictionaryRef sslPropDict; // holds ssl properties for the stream 51 CFHTTPAuthenticationRef serverAuth; // holds the authentication object for the server 52 53 uint32_t againCount; // Counts how many retries due to EAGAIN 54 55 boolean_t triedServerCredentials; // TRUE if we have tried server credentials 56 boolean_t triedProxyServerCredentials; // TRUE if we have tried proxy server credentials 57 58 // Dealing with sending credentials securely 59 boolean_t requireSecureLogin; // TRUE if credentials must be sent securely (i.e. forbids BASIC Auth without SSL) 60 boolean_t secureConnection; // TRUE if SSL connection 61 62 // Proxy 63 CFStringRef proxyRealm; 64 boolean_t httpProxyEnabled; // true if an http proxy is configured (according to SCDynamicStore) 65 CFStringRef httpProxyServer; // name or address of the http proxy server 66 int httpProxyPort; 67 boolean_t httpsProxyEnabled; // true if an secure proxy (https) is configured (according to SCDynamicStore) 68 CFStringRef httpsProxyServer; // name or address of the https proxy server 69 int httpsProxyPort; 70 CFDictionaryRef proxyDict; // hold the proxy dictionary 71 SCDynamicStoreRef proxyStore; // dynamic store for proxy info 72 CFHTTPAuthenticationRef proxyAuth; // holds the authentication object for the proxy server 73 CFIndex statusCode; // only valid when status is CheckAuthCallbackDone 74 CFStreamError streamError; // only valid when status is CheckAuthCallbackStreamError 75 enum CheckAuthCallbackStatus status; 76}; 77 78// function prototypes 79// enum WEBDAVLIBAuthStatus checkServerAuth(CFURLRef a_url); 80static enum WEBDAVLIBAuthStatus finalStatusFromStatusCode(struct callback_ctx *ctx, int *error); 81static int handleStreamError(struct callback_ctx *ctx, boolean_t *tryAgain); 82static int handleSSLErrors(struct callback_ctx *ctx, boolean_t *tryAgain); 83static enum WEBDAVLIBAuthStatus sendOptionsRequest(CFURLRef a_url, struct callback_ctx *ctx, int *result); 84static enum WEBDAVLIBAuthStatus sendOptionsRequestAuthenticated(CFURLRef a_url, struct callback_ctx *ctx, CFDictionaryRef creds, int *result); 85static void applyCredentialsToRequest(struct callback_ctx *ctx, CFDictionaryRef creds, CFHTTPMessageRef request); 86static void checkServerAuth_handleStreamEvent(CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo); 87static int updateNetworkProxies(struct callback_ctx *ctx); 88static void releaseContextItems(struct callback_ctx *ctx); 89static void initContext(struct callback_ctx *ctx); 90 91// Globals 92static SCDynamicStoreRef gProxyStore; 93 94enum 95{ 96 kHttpDefaultPort = 80, // default port for HTTP 97 kHttpsDefaultPort = 443 // default port for HTTPS 98}; 99 100 101enum WEBDAVLIBAuthStatus 102queryForProxy(CFURLRef a_url, CFMutableDictionaryRef proxyInfo, int *error) 103{ 104 enum WEBDAVLIBAuthStatus finalStatus; 105 int result; 106 struct callback_ctx ctx; 107 CFStringRef cf_port; 108 109 initContext(&ctx); 110 111 finalStatus = sendOptionsRequest(a_url, &ctx, &result); 112 *error = result; 113 114 switch (finalStatus) { 115 case WEBDAVLIB_Success: 116 syslog(LOG_DEBUG, "%s: Returning WEBDAVLIB_Success", __FUNCTION__); 117 break; 118 case WEBDAVLIB_ProxyAuth: 119 syslog(LOG_DEBUG, "%s: Returning WEBDAVLIB_ProxyAuth", __FUNCTION__); 120 if(ctx.httpProxyEnabled == TRUE) { 121 // Return http proxy server info in proxyInfo dictionary 122 CFDictionarySetValue(proxyInfo, kWebDAVLibProxySchemeKey, CFSTR("http")); 123 CFDictionarySetValue(proxyInfo, kWebDAVLibProxyServerNameKey, ctx.httpProxyServer); 124 125 if (ctx.proxyRealm != NULL) 126 CFDictionarySetValue(proxyInfo, kWebDAVLibProxyRealmKey, ctx.proxyRealm); 127 128 cf_port = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), ctx.httpProxyPort); 129 130 if (cf_port != NULL) { 131 CFDictionarySetValue(proxyInfo, kWebDAVLibProxyPortKey, cf_port); 132 CFRelease(cf_port); 133 } 134 } 135 else { 136 // Return https proxy server info in proxyInfo dictionary 137 CFDictionarySetValue(proxyInfo, kWebDAVLibProxySchemeKey, CFSTR("https")); 138 CFDictionarySetValue(proxyInfo, kWebDAVLibProxyServerNameKey, ctx.httpsProxyServer); 139 140 if (ctx.proxyRealm != NULL) 141 CFDictionarySetValue(proxyInfo, kWebDAVLibProxyRealmKey, ctx.proxyRealm); 142 143 cf_port = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), ctx.httpsProxyPort); 144 if (cf_port != NULL) { 145 CFDictionarySetValue(proxyInfo, kWebDAVLibProxyPortKey, cf_port); 146 CFRelease(cf_port); 147 } 148 } 149 break; 150 case WEBDAVLIB_ServerAuth: 151 syslog(LOG_DEBUG, "%s: Returning WEBDAVLIB_ServerAuth", __FUNCTION__); 152 break; 153 case WEBDAVLIB_UnexpectedStatus: 154 syslog(LOG_DEBUG, "%s: Returning WEBDAVLIB_UnexpectedStatus, errno: %d", __FUNCTION__, result); 155 break; 156 case WEBDAVLIB_IOError: 157 syslog(LOG_DEBUG, "%s: Returning WEBDAVLIB_IOError, errno: %d", __FUNCTION__, result); 158 break; 159 } 160 161 // Release context items 162 releaseContextItems(&ctx); 163 164 return (finalStatus); 165} 166 167enum WEBDAVLIBAuthStatus 168connectToServer(CFURLRef a_url, CFDictionaryRef creds, boolean_t requireSecureLogin, int *error) 169{ 170 enum WEBDAVLIBAuthStatus finalStatus; 171 int result; 172 struct callback_ctx ctx; 173 CFStringRef cf_port; 174 175 initContext(&ctx); 176 177 // remember if caller wants credentials to be sent securely 178 ctx.requireSecureLogin = requireSecureLogin; 179 180 finalStatus = sendOptionsRequestAuthenticated(a_url, &ctx, creds, &result); 181 *error = result; 182 183 switch (finalStatus) { 184 case WEBDAVLIB_Success: 185 syslog(LOG_DEBUG, "%s: Returning WEBDAVLIB_Success", __FUNCTION__); 186 break; 187 case WEBDAVLIB_ProxyAuth: 188 syslog(LOG_DEBUG, "%s: Returning WEBDAVLIB_ProxyAuth", __FUNCTION__); 189 break; 190 case WEBDAVLIB_ServerAuth: 191 syslog(LOG_DEBUG, "%s: Returning WEBDAVLIB_ServerAuth", __FUNCTION__); 192 break; 193 case WEBDAVLIB_UnexpectedStatus: 194 syslog(LOG_DEBUG, "%s: Returning WEBDAVLIB_UnexpectedStatus, errno %d", __FUNCTION__, result); 195 break; 196 case WEBDAVLIB_IOError: 197 syslog(LOG_DEBUG, "%s: Returning WEBDAVLIB_IOError, errno %d", __FUNCTION__, result); 198 break; 199 } 200 201 // Release context items 202 releaseContextItems(&ctx); 203 204 return (finalStatus); 205} 206 207 208static enum WEBDAVLIBAuthStatus 209sendOptionsRequest(CFURLRef a_url, struct callback_ctx *ctx, int *err) 210{ 211 CFHTTPMessageRef message; 212 CFReadStreamRef rdStream; 213 CFURLRef myURL; 214 CFStringRef urlStr; 215 boolean_t done, tryAgain; 216 enum WEBDAVLIBAuthStatus finalStatus; 217 218 *err = 0; 219 220 // initialize the context struct 221 ctx->status = CheckAuthInprogress; 222 ctx->theData = CFDataCreateMutable(NULL, 0); 223 ctx->sslPropDict = NULL; 224 urlStr = NULL; 225 myURL = CFRetain(a_url); 226 227 CFStreamClientContext context = {0, ctx, NULL, NULL, NULL}; 228 229 // update proxy information 230 updateNetworkProxies(ctx); 231 232 done = FALSE; 233 while (done == FALSE) { 234 // create a CFHTTP message object 235 message = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("OPTIONS"), myURL, kCFHTTPVersion1_1); 236 CFHTTPMessageSetHeaderFieldValue(message, CFSTR("User-Agent"), CFSTR(WEBAVLIB_USER_AGENT_STRING)); 237 CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Accept"), CFSTR("*/*")); 238 239 rdStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, message); 240 CFRelease(message); 241 message = NULL; 242 243 ctx->status = CheckAuthInprogress; 244 245 // apply http/https proxy properties 246 if (ctx->proxyDict != NULL) 247 CFReadStreamSetProperty(rdStream, kCFStreamPropertyHTTPProxy, ctx->proxyDict); 248 249 // apply SSL properties 250 if (ctx->sslPropDict != NULL) 251 CFReadStreamSetProperty(rdStream, kCFStreamPropertySSLSettings, ctx->sslPropDict); 252 253 // Set up the callback and schedule 254 CFReadStreamSetClient(rdStream, 255 kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred, 256 checkServerAuth_handleStreamEvent, 257 &context); 258 CFReadStreamScheduleWithRunLoop(rdStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); 259 260 // Open the stream and run the runloop 261 CFReadStreamOpen(rdStream); 262 263 while (ctx->status == CheckAuthInprogress) { 264 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 20, TRUE); 265 } 266 267 if (ctx->status == CheckAuthCallbackDone) { 268 // We received an http status code 269 finalStatus = finalStatusFromStatusCode(ctx, err); 270 271 if (finalStatus == WEBDAVLIB_ProxyAuth) { 272 // create an authentication object so we can fetch the realm 273 ctx->proxyAuth = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, ctx->response); 274 ctx->proxyRealm = CFHTTPAuthenticationCopyRealm(ctx->proxyAuth); 275 } 276 277 done = TRUE; 278 } 279 else if (ctx->status == CheckAuthRedirection) { 280 // Handle 3xx redirection 281 if(++ctx->againCount > WEBDAVLIB_MAX_AGAIN_COUNT) 282 { 283 // too many redirects 284 *err = EIO; 285 finalStatus = WEBDAVLIB_IOError; 286 done = TRUE; 287 } 288 else { 289 urlStr = CFHTTPMessageCopyHeaderFieldValue(ctx->response, CFSTR("Location")); 290 if (urlStr == NULL) { 291 *err = EIO; 292 finalStatus = WEBDAVLIB_IOError; 293 done = TRUE; 294 } 295 else { 296 myURL = CFURLCreateWithString(kCFAllocatorDefault, urlStr, NULL); 297 CFRelease(urlStr); 298 urlStr = NULL; 299 300 if (myURL == NULL) { 301 *err = EIO; 302 finalStatus = WEBDAVLIB_IOError; 303 done = TRUE; 304 } 305 306 syslog(LOG_DEBUG, "%s: Handling a redirection", __FUNCTION__); 307 } 308 } 309 } 310 else if (ctx->status == CheckAuthCallbackStreamError) { 311 *err = handleStreamError(ctx, &tryAgain); 312 313 if ((tryAgain == FALSE) || (++ctx->againCount > WEBDAVLIB_MAX_AGAIN_COUNT)) { 314 finalStatus = WEBDAVLIB_IOError; 315 done = TRUE; 316 } 317 } 318 319 // Unschedule the callback and close the read stream 320 CFReadStreamSetClient(rdStream, kCFStreamEventNone, NULL, NULL); 321 CFReadStreamUnscheduleFromRunLoop(rdStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); 322 CFReadStreamClose(rdStream); 323 CFRelease(rdStream); 324 rdStream = NULL; 325 326 CFRelease(ctx->theData); 327 ctx->theData = CFDataCreateMutable(NULL, 0); 328 } 329 330 if (myURL != NULL) 331 CFRelease(myURL); 332 333 return (finalStatus); 334} 335 336static enum WEBDAVLIBAuthStatus 337sendOptionsRequestAuthenticated(CFURLRef a_url, struct callback_ctx *ctx, CFDictionaryRef creds, int *err) 338{ 339 CFHTTPMessageRef message; 340 CFReadStreamRef rdStream; 341 CFURLRef myURL; 342 CFStringRef urlStr, method; 343 boolean_t done, tryAgain; 344 345 enum WEBDAVLIBAuthStatus finalStatus; 346 347 *err = 0; 348 urlStr = NULL; 349 myURL = CFRetain(a_url); 350 351 // initialize the context struct 352 ctx->status = CheckAuthInprogress; 353 ctx->theData = CFDataCreateMutable(NULL, 0); 354 ctx->sslPropDict = NULL; 355 356 CFStreamClientContext context = {0, ctx, NULL, NULL, NULL}; 357 358 // update proxy information 359 updateNetworkProxies(ctx); 360 361 done = FALSE; 362 while (done == FALSE) { 363 // create a CFHTTP message object 364 message = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("OPTIONS"), myURL, kCFHTTPVersion1_1); 365 CFHTTPMessageSetHeaderFieldValue(message, CFSTR("User-Agent"), CFSTR(WEBAVLIB_USER_AGENT_STRING)); 366 CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Accept"), CFSTR("*/*")); 367 368 // Apply authentication objects to the request 369 applyCredentialsToRequest(ctx, creds, message); 370 371 rdStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, message); 372 CFRelease(message); 373 message = NULL; 374 375 ctx->status = CheckAuthInprogress; 376 377 // apply http/https proxy properties 378 if (ctx->proxyDict != NULL) 379 CFReadStreamSetProperty(rdStream, kCFStreamPropertyHTTPProxy, ctx->proxyDict); 380 381 // apply SSL properties 382 if (ctx->sslPropDict != NULL) 383 CFReadStreamSetProperty(rdStream, kCFStreamPropertySSLSettings, ctx->sslPropDict); 384 385 // Set up the callback and schedule 386 CFReadStreamSetClient(rdStream, 387 kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred, 388 checkServerAuth_handleStreamEvent, 389 &context); 390 CFReadStreamScheduleWithRunLoop(rdStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); 391 392 // Open the stream and run the runloop 393 CFReadStreamOpen(rdStream); 394 395 while (ctx->status == CheckAuthInprogress) { 396 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 20, TRUE); 397 } 398 399 if (ctx->status == CheckAuthCallbackDone) { 400 // We received an http status code 401 finalStatus = finalStatusFromStatusCode(ctx, err); 402 403 if (finalStatus == WEBDAVLIB_ServerAuth) { 404 405 // do we have an authentication object? 406 if (ctx->serverAuth == NULL) { 407 // create a authentication object for server credentials on the next loop 408 ctx->serverAuth = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, ctx->response); 409 410 if (ctx->serverAuth != NULL) { 411 if (CFHTTPAuthenticationIsValid(ctx->serverAuth, NULL) == FALSE) { 412 // game over 413 syslog(LOG_DEBUG, "%s: Server CFHTTPAuthenticationIsValid is FALSE", __FUNCTION__); 414 *err = EIO; 415 done = TRUE; 416 } 417 418 // Do we need credentials at this point? 419 if ( (done == FALSE) && (CFHTTPAuthenticationRequiresUserNameAndPassword(ctx->serverAuth) == TRUE) ) { 420 // Were we given server credentials? 421 if (CFDictionaryContainsKey(creds, kWebDAVLibUserNameKey) == FALSE) { 422 // No server credentials, so just return WEBDAVLIB_ServerAuth 423 syslog(LOG_DEBUG, "%s: No server credentials in dictionary", __FUNCTION__); 424 done = TRUE; 425 } 426 427 // Check if credentials must be sent securely 428 if ( (done == FALSE) && (ctx->requireSecureLogin == TRUE) && (ctx->secureConnection == FALSE) ) { 429 method = CFHTTPAuthenticationCopyMethod(ctx->serverAuth); 430 if ( method != NULL ) { 431 if (CFEqual(method, CFSTR("Basic")) == TRUE) { 432 // game over 433 syslog(LOG_ERR, "%s: Authentication (Basic) too weak", __FUNCTION__); 434 done = TRUE; 435 *err = EAUTH; 436 } 437 CFRelease(method); 438 } 439 } 440 441 } 442 } 443 else { 444 // game over 445 syslog(LOG_DEBUG, "%s: Initial Server CFHTTPAuthenticationCreateFromResponse returned NULL", __FUNCTION__); 446 done = TRUE; 447 *err = EIO; 448 } 449 } 450 else { 451 // We have an auth abject for the server, we need to update it and use it 452 if (CFHTTPAuthenticationIsValid(ctx->serverAuth, NULL) == FALSE) { 453 CFRelease(ctx->serverAuth); 454 ctx->serverAuth = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, ctx->response); 455 456 if (ctx->serverAuth != NULL) { 457 if (CFHTTPAuthenticationIsValid(ctx->serverAuth, NULL) == FALSE) { 458 // game over 459 syslog(LOG_DEBUG, "%s: Server CFHTTPAuthenticationIsValid is FALSE", __FUNCTION__); 460 *err = EIO; 461 done = TRUE; 462 } 463 464 // Do we need server credentials at this point? 465 if ( (done == FALSE) && (CFHTTPAuthenticationRequiresUserNameAndPassword(ctx->serverAuth) == TRUE) ) { 466 // Were we given server credentials? 467 if (CFDictionaryContainsKey(creds, kWebDAVLibUserNameKey) == FALSE) { 468 // No server credentials, so just return WEBDAVLIB_ServerAuth 469 syslog(LOG_DEBUG, "%s: No server credentials in dictionary", __FUNCTION__); 470 done = TRUE; 471 } 472 473 // Have we already tried server credentials? 474 if ( (done == FALSE) && (ctx->triedServerCredentials == TRUE) ) { 475 // The server credentials were rejected, just return WEBDAVLIB_ServerAuth 476 syslog(LOG_DEBUG, "%s: Server credentials were not accepted", __FUNCTION__); 477 done = TRUE; 478 } 479 480 // Check if credentials must be sent securely 481 if ( (done == FALSE) && (ctx->requireSecureLogin == TRUE) && (ctx->secureConnection == FALSE)) { 482 method = CFHTTPAuthenticationCopyMethod(ctx->serverAuth); 483 if ( method != NULL ) { 484 if (CFEqual(method, CFSTR("Basic")) == TRUE) { 485 // game over 486 syslog(LOG_ERR, "%s: Authentication (Basic) too weak", __FUNCTION__); 487 done = TRUE; 488 *err = EAUTH; 489 } 490 CFRelease(method); 491 } 492 } 493 } 494 } 495 else { 496 // game over 497 syslog(LOG_DEBUG, "%s: Server CFHTTPAuthenticationCreateFromResponse returned NULL", __FUNCTION__); 498 *err = EIO; 499 done = TRUE; 500 } 501 } 502 } 503 } 504 else if (finalStatus == WEBDAVLIB_ProxyAuth) { 505 // do we have an authentication object? 506 if (ctx->proxyAuth == NULL) { 507 if (CFDictionaryContainsKey(creds, kWebDAVLibProxyUserNameKey) == FALSE) { 508 // No proxy server creds, so just return WEBAVLIB_ProxyAuth 509 syslog(LOG_DEBUG, "%s: No proxy server credentials in dictionary", __FUNCTION__); 510 done = TRUE; 511 continue; 512 } 513 514 // create an authentication object for proxy server credentials on the next loop 515 ctx->proxyAuth = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, ctx->response); 516 517 if (ctx->proxyAuth != NULL) { 518 if (CFHTTPAuthenticationIsValid(ctx->proxyAuth, NULL) == FALSE) { 519 // game over 520 syslog(LOG_DEBUG, "%s: Proxy CFHTTPAuthenticationIsValid is FALSE", __FUNCTION__); 521 *err = EIO; 522 done = TRUE; 523 } 524 525 // Do we need proxy server credentials at this point? 526 if ( (done == FALSE) && (CFHTTPAuthenticationRequiresUserNameAndPassword(ctx->proxyAuth) == TRUE) ) { 527 // Were we given proxy server credentials? 528 if (CFDictionaryContainsKey(creds, kWebDAVLibProxyUserNameKey) == FALSE) { 529 // No proxyserver credentials, so just return WEBDAVLIB_ProxyAuth 530 syslog(LOG_DEBUG, "%s: No proxy server credentials in dictionary", __FUNCTION__); 531 done = TRUE; 532 } 533 534 // Check if credentials must be sent securely 535 if ( (done == FALSE) && (ctx->requireSecureLogin == TRUE) && (ctx->secureConnection == FALSE)) { 536 method = CFHTTPAuthenticationCopyMethod(ctx->proxyAuth); 537 if ( method != NULL ) { 538 if (CFEqual(method, CFSTR("Basic")) == TRUE) { 539 // game over 540 syslog(LOG_ERR, "%s: Proxy Server authentication (Basic) too weak", __FUNCTION__); 541 done = TRUE; 542 *err = EAUTH; 543 } 544 CFRelease(method); 545 } 546 } 547 } 548 } 549 else { 550 // game over 551 syslog(LOG_DEBUG, "%s: Server CFHTTPAuthenticationCreateFromResponse returned NULL", __FUNCTION__); 552 *err = EIO; 553 done = TRUE; 554 } 555 } 556 else { 557 // We have an auth abject for the proxy server, we need to update it and use it 558 if (CFHTTPAuthenticationIsValid(ctx->proxyAuth, NULL) == FALSE) { 559 CFRelease(ctx->proxyAuth); 560 ctx->proxyAuth = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, ctx->response); 561 562 if (ctx->proxyAuth != NULL) { 563 if (CFHTTPAuthenticationIsValid(ctx->proxyAuth, NULL) == FALSE) { 564 // game over 565 syslog(LOG_DEBUG, "%s: Proxy server CFHTTPAuthenticationIsValid is FALSE", __FUNCTION__); 566 *err = EIO; 567 done = TRUE; 568 } 569 570 // Do we need proxy server credentials at this point? 571 if ((done == FALSE) && (CFHTTPAuthenticationRequiresUserNameAndPassword(ctx->proxyAuth) == TRUE) ) { 572 // Were we given proxy server credentials? 573 if (CFDictionaryContainsKey(creds, kWebDAVLibProxyUserNameKey) == FALSE) { 574 // No proxyserver credentials, so just return WEBDAVLIB_ProxyAuth 575 syslog(LOG_DEBUG, "%s: No proxy server creds in dictionary", __FUNCTION__); 576 done = TRUE; 577 } 578 579 // Have we already tried proxy server credentials? 580 if ((done == FALSE) && (ctx->triedProxyServerCredentials == TRUE) ) { 581 // The proxy server credentials were rejected, just return WEBDAVLIB_ProxyAuth 582 syslog(LOG_DEBUG, "%s: Proxy server credentials were not accepted", __FUNCTION__); 583 done = TRUE; 584 } 585 // Check if credentials must be sent securely 586 if ( (done == FALSE) && (ctx->requireSecureLogin == TRUE) && (ctx->secureConnection == FALSE)) { 587 method = CFHTTPAuthenticationCopyMethod(ctx->proxyAuth); 588 if ( method != NULL ) { 589 if (CFEqual(method, CFSTR("Basic")) == TRUE) { 590 // game over 591 syslog(LOG_ERR, "%s: Proxy Server authentication (Basic) too weak", __FUNCTION__); 592 done = TRUE; 593 *err = EAUTH; 594 } 595 CFRelease(method); 596 } 597 } 598 } 599 } 600 else { 601 // game over 602 syslog(LOG_DEBUG, "%s: Proxy server CFHTTPAuthenticationCreateFromResponse returned NULL", __FUNCTION__); 603 *err = EIO; 604 done = TRUE; 605 } 606 } 607 608 } 609 } 610 else { 611 done = TRUE; 612 } 613 } 614 else if (ctx->status == CheckAuthRedirection) { 615 // Handle 3xx redirection 616 if(++ctx->againCount > WEBDAVLIB_MAX_AGAIN_COUNT) 617 { 618 // too many redirects 619 *err = EIO; 620 finalStatus = WEBDAVLIB_IOError; 621 done = TRUE; 622 } 623 else { 624 urlStr = CFHTTPMessageCopyHeaderFieldValue(ctx->response, CFSTR("Location")); 625 if (urlStr == NULL) { 626 *err = EIO; 627 finalStatus = WEBDAVLIB_IOError; 628 done = TRUE; 629 } 630 else { 631 myURL = CFURLCreateWithString(kCFAllocatorDefault, urlStr, NULL); 632 CFRelease(urlStr); 633 urlStr = NULL; 634 635 if (myURL == NULL) { 636 *err = EIO; 637 finalStatus = WEBDAVLIB_IOError; 638 done = TRUE; 639 } 640 641 syslog(LOG_DEBUG, "%s: Handling a redirection", __FUNCTION__); 642 } 643 } 644 } 645 else if (ctx->status == CheckAuthCallbackStreamError) { 646 *err = handleStreamError(ctx, &tryAgain); 647 648 if ((tryAgain == FALSE) || (++ctx->againCount > WEBDAVLIB_MAX_AGAIN_COUNT)) { 649 finalStatus = WEBDAVLIB_IOError; 650 done = TRUE; 651 } 652 } 653 654 // Unschedule the callback and close the read stream 655 CFReadStreamSetClient(rdStream, kCFStreamEventNone, NULL, NULL); 656 CFReadStreamUnscheduleFromRunLoop(rdStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); 657 CFReadStreamClose(rdStream); 658 CFRelease(rdStream); 659 rdStream = NULL; 660 661 CFRelease(ctx->theData); 662 ctx->theData = CFDataCreateMutable(NULL, 0); 663 } 664 665 if (myURL != NULL) 666 CFRelease(myURL); 667 668 return (finalStatus); 669} 670 671static void applyCredentialsToRequest(struct callback_ctx *ctx, CFDictionaryRef creds, CFHTTPMessageRef request) 672{ 673 CFStringRef user, password; 674 675 // Check for server credentials 676 if (ctx->serverAuth != NULL) { 677 user = CFDictionaryGetValue(creds, kWebDAVLibUserNameKey); 678 password = CFDictionaryGetValue(creds, kWebDAVLibPasswordKey); 679 680 // Note: To support NTLM, we need to obtain the NT Domain from the user. 681 CFHTTPMessageApplyCredentials(request, ctx->serverAuth, user, password, NULL); 682 ctx->triedServerCredentials = TRUE; 683 } 684 685 // Check for proxy server credentials 686 if (ctx->proxyAuth != NULL) { 687 user = CFDictionaryGetValue(creds, kWebDAVLibProxyUserNameKey); 688 password = CFDictionaryGetValue(creds, kWebDAVLibProxyPasswordKey); 689 690 // Note: To support NTLM, we need to obtain the NT Domain from the user. 691 CFHTTPMessageApplyCredentials(request, ctx->proxyAuth, user, password, NULL); 692 ctx->triedProxyServerCredentials = TRUE; 693 } 694} 695 696static enum WEBDAVLIBAuthStatus 697finalStatusFromStatusCode(struct callback_ctx *ctx, int *error) 698{ 699 enum WEBDAVLIBAuthStatus finalStatus; 700 *error = 0; 701 702 // Remember our main goal is to determine whether or not 703 // there exists an http/https proxy server to deal with. 704 // Since we only send an OPTIONS request, many http status codes 705 // may not make sense (such as 423 Lock Failed). 706 switch (ctx->statusCode / 100) { 707 case 2: // 2xx Successfull 708 finalStatus = WEBDAVLIB_Success; 709 break; 710 case 4: 711 if (ctx->statusCode == 401) { 712 finalStatus = WEBDAVLIB_ServerAuth; 713 *error = EAUTH; 714 } 715 else if (ctx->statusCode == 407) { 716 finalStatus = WEBDAVLIB_ProxyAuth; 717 *error = EAUTH; 718 } 719 else { 720 syslog(LOG_ERR, "%s: unexpected http status code %ld\n", __FUNCTION__, ctx->statusCode); 721 *error = EIO; 722 finalStatus = WEBDAVLIB_UnexpectedStatus; 723 } 724 break; 725 case 1: // Informational 1xx 726 case 3: // Redirection 3xx 727 case 5: // Server error 5xx 728 default: 729 syslog(LOG_ERR, "%s: unexpected http status code %ld\n", __FUNCTION__, ctx->statusCode); 730 finalStatus = WEBDAVLIB_UnexpectedStatus; 731 *error = EIO; 732 break; 733 } 734 return (finalStatus); 735} 736 737static int 738handleStreamError(struct callback_ctx *ctx, boolean_t *tryAgain) 739{ 740 int result = EIO; 741 742 // we only retry under certain error conditions 743 *tryAgain = FALSE; 744 745 if (ctx->streamError.domain == kCFStreamErrorDomainSSL) { 746 result = handleSSLErrors(ctx, tryAgain); 747 } 748 else if (ctx->streamError.domain == kCFStreamErrorDomainPOSIX) { 749 result = ctx->streamError.error; 750 751 if (result == EPIPE) { 752 // busy server, just try again 753 syslog(LOG_DEBUG, "%s: retrying stream error domain: posix error: EPIPE\n", __FUNCTION__); 754 *tryAgain = TRUE; 755 } 756 else 757 syslog(LOG_ERR, "%s: stream error domain: posix error: %d\n", __FUNCTION__, (int)ctx->streamError.error); 758 } 759 else if (ctx->streamError.domain == kCFStreamErrorDomainHTTP) { 760 if (ctx->streamError.error == kCFStreamErrorHTTPConnectionLost) { 761 // connection was dropped, we can try again 762 syslog(LOG_DEBUG, "%s: retrying, stream error domain: http error: kCFStreamErrorHTTPConnectionLost", __FUNCTION__); 763 *tryAgain = TRUE; 764 result = ECONNRESET; 765 } 766 else { 767 syslog(LOG_ERR, "%s: stream error domain: http error: %d", __FUNCTION__, (int)ctx->streamError.error); 768 result = EIO; 769 } 770 } 771 else if (ctx->streamError.domain == kCFStreamErrorDomainNetDB) { 772 switch (ctx->streamError.error) { 773 case EAI_NODATA: 774 // no address associated with host name 775 // the network interface was changed 776 // okay to try again 777 syslog(LOG_DEBUG, "%s: retrying, stream error domain: netdb error: EAI_NODATA", __FUNCTION__); 778 *tryAgain = TRUE; 779 result = EADDRNOTAVAIL; 780 break; 781 default: 782 syslog(LOG_ERR, "%s: stream error domain: netdb error: %d\n", __FUNCTION__, (int)ctx->streamError.error); 783 result = EIO; 784 break; 785 } 786 } 787 else { 788 syslog(LOG_ERR, "%s: stream error domain: %ld error: %d\n", __FUNCTION__, ctx->streamError.domain, (int)ctx->streamError.error); 789 result = EIO; 790 } 791 return (result); 792} 793 794static int 795handleSSLErrors(struct callback_ctx *ctx, boolean_t *tryAgain) 796{ 797 SInt32 error; 798 int result; 799 800 // no good errno to indicate ssl errors, so we just use EIO 801 result = EIO; 802 803 // in most cases we try again 804 *tryAgain = TRUE; 805 806 // indicate SSL connection 807 ctx->secureConnection = TRUE; 808 809 error = ctx->streamError.error; 810 811 syslog(LOG_DEBUG, "%s: stream error domain: ssl error: %d", __FUNCTION__, (int)ctx->streamError.error); 812 813 if (ctx->sslPropDict == NULL) 814 ctx->sslPropDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 815 0, 816 &kCFTypeDictionaryKeyCallBacks, 817 &kCFTypeDictionaryValueCallBacks); 818 if (ctx->sslPropDict == NULL) { 819 syslog(LOG_ERR, "%s: no memory for sslPropDictionary", __FUNCTION__); 820 return ENOMEM; 821 } 822 823 if ( (CFDictionaryGetValue(ctx->sslPropDict, kCFStreamSSLLevel) == NULL) && 824 (((error <= errSSLProtocol) && (error > errSSLXCertChainInvalid)) || 825 ((error <= errSSLCrypto) && (error > errSSLUnknownRootCert)) || 826 ((error <= errSSLClosedNoNotify) && (error > errSSLPeerBadCert)) || 827 (error == errSSLIllegalParam) || 828 ((error <= errSSLPeerAccessDenied) && (error > errSSLLast))) ) 829 { 830 // retry with fall back from TLS to SSL */ 831 CFDictionarySetValue(ctx->sslPropDict, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelSSLv3); 832 return (result); 833 } 834 835 switch ( ctx->streamError.error ) 836 { 837 case errSSLCertExpired: 838 case errSSLCertNotYetValid: 839 /* The certificate for this server has expired or is not yet valid */ 840 if ( (CFDictionaryGetValue(ctx->sslPropDict, kCFStreamSSLAllowsExpiredCertificates) == NULL) ) 841 { 842 // We are just trying to check for a proxy server, so it's okay to continue 843 CFDictionarySetValue(ctx->sslPropDict, kCFStreamSSLAllowsExpiredCertificates, kCFBooleanTrue); 844 CFDictionarySetValue(ctx->sslPropDict, kCFStreamSSLAllowsExpiredRoots, kCFBooleanTrue); 845 } 846 break; 847 848 case errSSLBadCert: 849 case errSSLXCertChainInvalid: 850 case errSSLHostNameMismatch: 851 /* The certificate for this server is invalid */ 852 if ( (CFDictionaryGetValue(ctx->sslPropDict, kCFStreamSSLValidatesCertificateChain) == NULL) ) 853 { 854 CFDictionarySetValue(ctx->sslPropDict, kCFStreamSSLValidatesCertificateChain, kCFBooleanFalse); 855 } 856 break; 857 858 case errSSLUnknownRootCert: 859 case errSSLNoRootCert: 860 /* The certificate for this server was signed by an unknown certifying authority */ 861 if ( (CFDictionaryGetValue(ctx->sslPropDict, kCFStreamSSLAllowsAnyRoot) == NULL) ) 862 { 863 CFDictionarySetValue(ctx->sslPropDict, kCFStreamSSLAllowsAnyRoot, kCFBooleanTrue); 864 } 865 break; 866 867 default: 868 syslog(LOG_ERR, "%s: stream error domain: ssl error: %d", __FUNCTION__, (int)ctx->streamError.error); 869 // no sense in retrying 870 *tryAgain = TRUE; 871 break; 872 } 873 874 return (result); 875} 876 877static void checkServerAuth_handleStreamEvent(CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo) 878{ 879 struct callback_ctx *ctx; 880 CFTypeRef theResponsePropertyRef; 881 CFIndex bytesRead; 882 CFStreamError streamError; 883 UInt8 buffer[STREAM_EVENT_BUFSIZE]; 884 885 ctx = (struct callback_ctx *)clientCallBackInfo; 886 887 switch (type) { 888 case kCFStreamEventHasBytesAvailable: 889 bytesRead = CFReadStreamRead(stream, buffer, STREAM_EVENT_BUFSIZE); 890 if (bytesRead > 0) { 891 CFDataAppendBytes(ctx->theData, buffer, bytesRead); 892 } 893 // Don't worry about bytesRead <= 0, because those will generate other events 894 break; 895 896 case kCFStreamEventEndEncountered: 897 theResponsePropertyRef = CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader); 898 ctx->response = *((CFHTTPMessageRef*)((void*)&theResponsePropertyRef)); 899 ctx->statusCode = CFHTTPMessageGetResponseStatusCode(ctx->response); 900 if ((ctx->statusCode / 100) == 3) { 901 // redirection 902 ctx->status = CheckAuthRedirection; 903 } 904 else 905 ctx->status = CheckAuthCallbackDone; 906 syslog(LOG_DEBUG, "%s: StreamEventEndEncountered, status code %ld\n", __FUNCTION__, ctx->statusCode); 907 break; 908 909 case kCFStreamEventErrorOccurred: 910 streamError = CFReadStreamGetError(stream); 911 syslog(LOG_DEBUG,"%s: EventHasErrorOccurred: domain %ld, error %d", 912 __FUNCTION__, streamError.domain, (int)streamError.error); 913 ctx->streamError = streamError; 914 ctx->status = CheckAuthCallbackStreamError; 915 break; 916 917 default: 918 syslog(LOG_DEBUG, "%s: Received unexpected stream event %lu\n", __FUNCTION__, type); 919 break; 920 } 921} 922 923static int updateNetworkProxies(struct callback_ctx *ctx) 924{ 925 CFNumberRef cf_enabled; 926 CFNumberRef cf_port; 927 int enabled; 928 int err; 929 930 if (ctx->proxyStore == NULL) { 931 ctx->proxyStore = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("WebDAVFSPlugin"), NULL, NULL); 932 if (ctx->proxyStore == NULL) 933 return ENOMEM; 934 } 935 936 // Reset all proxy info 937 if (ctx->proxyRealm != NULL) { 938 CFRelease(ctx->proxyRealm); 939 ctx->proxyRealm = NULL; 940 } 941 942 ctx->httpProxyEnabled = FALSE; 943 ctx->httpsProxyEnabled = FALSE; 944 945 if (ctx->httpProxyServer != NULL) 946 { 947 CFRelease(ctx->httpProxyServer); 948 ctx->httpProxyServer = NULL; 949 } 950 951 if (ctx->httpsProxyServer != NULL) 952 { 953 CFRelease(ctx->httpsProxyServer); 954 ctx->httpsProxyServer = NULL; 955 } 956 957 if (ctx->proxyDict != NULL) 958 { 959 CFRelease(ctx->proxyDict); 960 ctx->proxyDict = NULL; 961 } 962 963 ctx->httpProxyEnabled = FALSE; 964 ctx->httpsProxyEnabled = FALSE; 965 966 // fetch the current internet proxy dictionary 967 ctx->proxyDict = SCDynamicStoreCopyProxies(gProxyStore); 968 969 if (ctx->proxyDict != NULL) { 970 // ********************* 971 // handle HTTP proxies 972 // ********************* 973 974 // are HTTP proxies enabled? 975 cf_enabled = CFDictionaryGetValue(ctx->proxyDict, kSCPropNetProxiesHTTPEnable); 976 if ( (cf_enabled != NULL) && CFNumberGetValue(cf_enabled, kCFNumberIntType, &enabled) && enabled ) 977 { 978 // fetch the HTTP proxy host 979 ctx->httpProxyServer = CFDictionaryGetValue(ctx->proxyDict, kSCPropNetProxiesHTTPProxy); 980 if ( ctx->httpProxyServer != NULL ) 981 { 982 CFRetain(ctx->httpProxyServer); 983 984 // fetch the HTTP proxy port 985 cf_port = CFDictionaryGetValue(ctx->proxyDict, kSCPropNetProxiesHTTPPort); 986 if ( (cf_port != NULL) && CFNumberGetValue(cf_port, kCFNumberIntType, &ctx->httpProxyPort) ) 987 { 988 if ( ctx->httpProxyPort == 0 ) 989 { 990 //no port specified so use the default HTTP port 991 ctx->httpProxyPort = kHttpDefaultPort; 992 } 993 ctx->httpProxyEnabled = TRUE; 994 } 995 } 996 } 997 998 // ********************* 999 // handle HTTPS proxies 1000 // ********************* 1001 1002 // are HTTPS proxies enabled? 1003 cf_enabled = CFDictionaryGetValue(ctx->proxyDict, kSCPropNetProxiesHTTPSEnable); 1004 if ( (cf_enabled != NULL) && CFNumberGetValue(cf_enabled, kCFNumberIntType, &enabled) && enabled ) 1005 { 1006 // fetch the HTTPS proxy host 1007 ctx->httpsProxyServer = CFDictionaryGetValue(ctx->proxyDict, kSCPropNetProxiesHTTPSProxy); 1008 if ( ctx->httpsProxyServer != NULL ) 1009 { 1010 CFRetain(ctx->httpsProxyServer); 1011 1012 // fetch the HTTPS proxy port 1013 cf_port = CFDictionaryGetValue(ctx->proxyDict, kSCPropNetProxiesHTTPSPort); 1014 if ( (cf_port != NULL) && CFNumberGetValue(cf_port, kCFNumberIntType, &ctx->httpsProxyPort) ) 1015 { 1016 if ( ctx->httpsProxyPort == 0 ) 1017 { 1018 // no port specified so use the default HTTPS port 1019 ctx->httpsProxyPort = kHttpsDefaultPort; 1020 } 1021 ctx->httpsProxyEnabled = TRUE; 1022 } 1023 } 1024 } 1025 } 1026} 1027 1028static void initContext(struct callback_ctx *ctx) 1029{ 1030 ctx->theData = NULL; 1031 ctx->response = NULL; 1032 ctx->sslPropDict = NULL; 1033 ctx->serverAuth = NULL; 1034 ctx->againCount = 0; 1035 ctx->triedServerCredentials = FALSE; 1036 ctx->triedProxyServerCredentials = FALSE; 1037 ctx->requireSecureLogin = FALSE; 1038 ctx->secureConnection = FALSE; 1039 ctx->proxyRealm = NULL; 1040 ctx->httpProxyEnabled = FALSE; 1041 ctx->httpProxyServer = NULL; 1042 ctx->httpsProxyEnabled = FALSE; 1043 ctx->httpsProxyServer = NULL; 1044 ctx->proxyDict = NULL; 1045 ctx->proxyStore = NULL; 1046 ctx->proxyAuth = NULL; 1047} 1048 1049static void releaseContextItems(struct callback_ctx *ctx) 1050{ 1051 CFReleaseNull(ctx->theData); 1052 CFReleaseNull(ctx->response); 1053 CFReleaseNull(ctx->sslPropDict); 1054 CFReleaseNull(ctx->serverAuth); 1055 CFReleaseNull(ctx->proxyRealm); 1056 CFReleaseNull(ctx->httpProxyServer); 1057 CFReleaseNull(ctx->httpsProxyServer); 1058 CFReleaseNull(ctx->proxyDict); 1059 CFReleaseNull (ctx->proxyStore); 1060 CFReleaseNull(ctx->proxyAuth); 1061} 1062