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