1/*
2 * Copyright (c) 2013 Apple Inc.
3 * All rights reserved.
4 */
5
6#include <syslog.h>
7#include <stdio.h>
8#include <dns_sd.h>
9#include <sys/socket.h>
10#include <CoreFoundation/CoreFoundation.h>
11#import <Foundation/Foundation.h>
12#include <CFNetwork/CFSocketStreamPriv.h>
13
14#define dns_timeout		10
15#define probe_timeout   30
16#define HTTP_RESPONSE_CODE_OK	200
17
18static void probe_callback(CFReadStreamRef readStream, CFStreamEventType type, void * context);
19static void timer_callback( CFRunLoopTimerRef timerref, void *context);
20
21static void
22probe_callback(CFReadStreamRef readStream, CFStreamEventType type, void * context)
23{
24    int readbytes = 0;
25    int probe_OK = 0;
26    CFErrorRef   error;
27    CFStringRef   errorstr = NULL;
28    CFRunLoopTimerRef   timerref = context;
29	CFHTTPMessageRef resp = NULL;
30	CFIndex responseCode = 0, bufferLength = 0;
31	char* buffer;
32
33    CFRunLoopTimerInvalidate(timerref);
34    CFRelease(timerref);
35
36	resp = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
37
38	responseCode = CFHTTPMessageGetResponseStatusCode(resp);
39	syslog(LOG_DEBUG, "sbslauncher response code is %ld", responseCode);
40
41	if (responseCode != HTTP_RESPONSE_CODE_OK)
42		goto done;
43
44	switch (type) {
45        case kCFStreamEventOpenCompleted:
46            probe_OK = 1;
47            break;
48        case kCFStreamEventHasBytesAvailable:
49            probe_OK = 1;
50            break;
51        case kCFStreamEventErrorOccurred:
52            error = CFReadStreamCopyError(readStream);
53            errorstr = CFErrorCopyDescription(error);
54			bufferLength = CFStringGetLength(errorstr) + 1;
55			buffer = (char*)malloc(bufferLength*sizeof(char));
56			CFStringGetCString(errorstr, buffer, (bufferLength), CFStringGetSystemEncoding());
57            syslog(LOG_ERR, "sbslauncher probe_callback stream, errorstr: %s", buffer);
58
59			free(buffer);
60			CFRelease(errorstr);
61            break;
62        default:
63            break;
64    }
65
66done:
67    syslog(LOG_DEBUG, "sbslauncher: probe result: %d", probe_OK);
68	if(resp)
69		CFRelease(resp);
70    CFRelease(readStream);
71    exit(probe_OK);
72}
73
74static void
75timer_callback( CFRunLoopTimerRef timerref, void *context)
76{
77    syslog(LOG_DEBUG, "timer_callback: timer expires");
78    CFRunLoopTimerInvalidate(timerref);
79    CFRelease(timerref);
80    /* release read stream */
81    if (context)
82        CFRelease(context);
83    /* timeout, no response from server */
84    exit(0);
85}
86
87int launch_http_probe(int argc, const char * argv[]) {
88    CFStringRef cfprobe_server = NULL;
89    CFStringRef cfinterface = NULL;
90	CFURLRef url = NULL;
91    CFRunLoopTimerRef   timerref = NULL;
92    CFRunLoopTimerContext   timer_context = { 0, NULL, NULL, NULL, NULL };
93    NSURLRequest *request = NULL;
94    NSError *error;
95    NSURLResponse *response;
96    NSData *result;
97    int proberesult = 0;
98
99    CFHTTPMessageRef httpRequest = NULL;
100    CFReadStreamRef httpStream = NULL;
101    CFStreamClientContext  stream_context = { 0, NULL, NULL, NULL, NULL };
102
103    /* set up URL */
104
105    if ((cfprobe_server = CFStringCreateWithCString(NULL, (char*)argv[2], kCFStringEncodingMacRoman)) == NULL)
106        goto done;
107
108    if ((cfinterface = CFStringCreateWithCString(NULL, (char*)argv[3], kCFStringEncodingMacRoman)) == NULL)
109        goto done;
110
111    if ((url = CFURLCreateWithString(NULL, cfprobe_server, NULL))==NULL){
112        CFRelease(cfprobe_server);
113        goto done;
114    }
115
116    httpRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), url, kCFHTTPVersion1_1);
117    if (httpRequest == NULL)
118        goto done;
119
120    httpStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, httpRequest);
121    if (httpStream == NULL)
122        goto done;
123
124    static const CFOptionFlags	eventMask = kCFStreamEventHasBytesAvailable |
125    kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
126
127    timer_context.info = (void*)httpStream;
128
129    CFReadStreamSetProperty(httpStream, kCFStreamPropertyBoundInterfaceIdentifier, cfinterface);
130
131    /* set timer */
132    timerref = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + probe_timeout, 0, 0, 0, timer_callback, &timer_context);
133    if (timerref == NULL)
134        goto done;
135
136    stream_context.info = (void*)timerref;
137
138	if (!CFReadStreamSetClient(httpStream, eventMask, probe_callback, &stream_context))
139	{
140        CFRelease(timerref);
141		goto done;
142	}
143
144	// Schedule with the run loop
145	CFReadStreamScheduleWithRunLoop(httpStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
146
147    if ( !CFReadStreamOpen(httpStream)){
148        syslog(LOG_ERR, "sbslauncher CFReadStreamOpen err, read stream status %ld", CFReadStreamGetStatus(httpStream));
149    }
150
151    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timerref, kCFRunLoopCommonModes);
152    CFRunLoopRun();
153
154done:
155    if (cfprobe_server)
156        CFRelease(cfprobe_server);
157    if (cfinterface)
158        CFRelease(cfinterface);
159    if (url)
160        CFRelease(url);
161    if (httpRequest)
162        CFRelease(httpRequest);
163
164    return(proberesult);
165}
166
167static DNSServiceRef dnsRedirectQuery = NULL;
168static int dnsRedirectDetected = 0;
169static int dnsRedirectWriteFD = -1;
170
171static void
172dns_redirect_callback(DNSServiceRef			sdRef,
173					DNSServiceFlags			flags,
174					uint32_t				interfaceIndex,
175					DNSServiceErrorType		errorCode,
176					const char				*hostname,
177					const struct sockaddr	*address,
178					uint32_t				ttl,
179					void					*context)
180{
181	/* If we got back an answer, we detected a liar! */
182	if ((errorCode == kDNSServiceErr_NoError) && (address != NULL)) {
183		dnsRedirectDetected = 1;
184
185		if (dnsRedirectWriteFD != -1) {
186			if (write(dnsRedirectWriteFD, address, address->sa_len) < 0) {
187				syslog(LOG_ERR, "sbslauncher write address home to socket %d fails, error: %s (tried to write %d bytes)", dnsRedirectWriteFD, strerror(errno), address->sa_len);
188				dnsRedirectDetected = 0;
189			}
190
191		}
192	}
193
194	if (!(flags & kDNSServiceFlagsMoreComing)) {
195		/* Clear query ref */
196		if (dnsRedirectQuery != NULL) {
197			DNSServiceRefDeallocate(dnsRedirectQuery);
198			dnsRedirectQuery = NULL;
199		}
200
201		exit(dnsRedirectDetected);
202	}
203}
204
205#define HOSTNAME_CHARSET                "abcdefghijklmnopqrstuvwxyz0123456789-"
206#define HOSTNAME_CHARSET_LENGTH         (sizeof(HOSTNAME_CHARSET) - 1)
207#define BASE_RANDOM_SEGMENT_LENGTH		8
208#define VARIABLE_RANDOM_SEGMENT_LENGTH	20
209#define RANDOM_SEGMENT_MAX_LENGTH		(BASE_RANDOM_SEGMENT_LENGTH + VARIABLE_RANDOM_SEGMENT_LENGTH + 1)
210
211int detect_dns_redirect (int argc, const char * argv[])
212{
213	Boolean success = FALSE;
214	char random_host[256];
215	int character_index = 0;
216	int firstSegmentLength = (arc4random() % VARIABLE_RANDOM_SEGMENT_LENGTH) + BASE_RANDOM_SEGMENT_LENGTH;
217	int secondSegmentLength = (arc4random() % VARIABLE_RANDOM_SEGMENT_LENGTH) + BASE_RANDOM_SEGMENT_LENGTH;
218	int i;
219
220	/* Need a socket to write back on */
221	if (argc >= 3  && argv[2]) {
222		dnsRedirectWriteFD = atoi(argv[2]);
223		/*int buffersize = sizeof(struct sockaddr_storage);
224		if (setsockopt(dnsRedirectWriteFD, SOL_SOCKET, SO_SNDBUF, &buffersize, sizeof(buffersize)) < 0) {
225			syslog(LOG_ERR, "Cannot set SO_SNDBUF for socket %d, error: %s\n", dnsRedirectWriteFD, strerror(errno));
226		}*/
227	}
228
229	/* Create random host xxxxxxxxxxxx.xxxxxxxxxxx.com */
230	for (i = 0; i < firstSegmentLength; i++) {
231		random_host[character_index++] = HOSTNAME_CHARSET[(arc4random() % HOSTNAME_CHARSET_LENGTH)];
232	}
233
234	random_host[character_index++] = '.';
235
236	for (i = 0; i < secondSegmentLength; i++) {
237		random_host[character_index++] = HOSTNAME_CHARSET[(arc4random() % HOSTNAME_CHARSET_LENGTH)];
238	}
239
240	strncpy((char*)random_host + character_index, ".com", sizeof(random_host) - character_index);
241
242	DNSServiceErrorType error = DNSServiceGetAddrInfo(&dnsRedirectQuery, kDNSServiceFlagsReturnIntermediates, 0, kDNSServiceProtocol_IPv4, random_host, dns_redirect_callback, NULL);
243	if (error != kDNSServiceErr_NoError) {
244		goto done;
245	}
246
247	error = DNSServiceSetDispatchQueue(dnsRedirectQuery, dispatch_get_main_queue());
248	if (error != kDNSServiceErr_NoError) {
249		goto done;
250	}
251
252	/* set timer */
253	CFRunLoopTimerRef   timerref = NULL;
254	CFRunLoopTimerContext   timer_context = { 0, NULL, NULL, NULL, NULL };
255	timerref = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + dns_timeout, 0, 0, 0, timer_callback, &timer_context);
256	if (timerref == NULL)
257		goto done;
258	CFRunLoopAddTimer(CFRunLoopGetCurrent(), timerref, kCFRunLoopCommonModes);
259
260	CFRunLoopRun();
261	success = TRUE;
262
263done:
264	return success;
265}
266