1/*
2 * Copyright (c) 2012 Apple Computer, 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 <sysexits.h>
25#include "timestampclient.h"
26#include <syslog.h>
27
28struct connection_info {
29    xpc_connection_t peer;
30    int processed;
31    int done;
32};
33
34xpc_object_t keychain_prefs_path = NULL;
35xpc_object_t home = NULL;
36
37extern xpc_object_t
38xpc_create_reply_with_format(xpc_object_t original, const char * format, ...);
39
40void finalize_connection(void *not_used);
41void handle_connection_event(const xpc_connection_t peer);
42void handle_request_event(struct connection_info *info, xpc_object_t event);
43
44#ifndef NDEBUG
45    #define xpctsa_secdebug(format...) \
46        do {  \
47            syslog(LOG_WARNING, format);  \
48        } while (0)
49    #define xpctsaNSLog(format...) \
50        do {  \
51            NSLog(format);  \
52        } while (0)
53
54#else
55    //empty
56    #define xpctsa_secdebug(format...)
57    #define xpctsaNSLog(format...)
58#endif
59#define xpctsaDebug(args...)			xpctsa_secdebug(args)
60
61/*
62    These came from:
63
64#include <OSServices/NetworkUtilities.h>
65
66    I have no idea why they aren't more accessible.
67*/
68
69#define kHTTPResponseCodeContinue             100
70#define kHTTPResponseCodeOK                   200
71#define kHTTPResponseCodeNoContent            204
72#define kHTTPResponseCodeBadRequest           400
73#define kHTTPResponseCodeUnauthorized         401
74#define kHTTPResponseCodeForbidden            403
75#define kHTTPResponseCodeConflict             409
76#define kHTTPResponseCodeExpectationFailed    417
77#define kHTTPResponseCodeServFail             500
78#define kHTTPResponseCodeInsufficientStorage  507
79
80//
81// Turn a CFString into a UTF8-encoded C string.
82//
83static char *cfStringToCString(CFStringRef inStr)
84{
85	if (!inStr)
86		return "";
87	CFRetain(inStr);	// compensate for release on exit
88
89	// need to extract into buffer
90	CFIndex length = CFStringGetLength(inStr);  // in 16-bit character units
91    size_t len = 6 * length + 1;
92	char *buffer = malloc(len);                 // pessimistic
93	if (!CFStringGetCString(inStr, buffer, len, kCFStringEncodingUTF8))
94        buffer[0]=0;
95
96    CFRelease(inStr);
97	return buffer;
98}
99
100static void debugShowTSAResponseInfo(NSURLResponse *response, NSData *data, NSError *err)
101{
102#ifndef NDEBUG
103    if (response)
104    {
105        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
106        NSInteger statusCode = [httpResponse statusCode];
107        NSDictionary *headers = [httpResponse allHeaderFields];
108        NSString *errStr2 = [NSHTTPURLResponse localizedStringForStatusCode:(NSInteger)statusCode];
109
110        xpctsaNSLog(@"TSA Response: %d, %@, %@", (int)statusCode, errStr2, headers);
111    }
112
113    if (err)
114    {   xpctsaNSLog(@"TSARequestCompletionBlock error: %@", err); }
115
116    if (data)
117    {
118        xpctsaDebug("TSARequestCompletionBlock: Data (%lu bytes)",(unsigned long)[data length]);
119        xpctsaNSLog(@"TSARequestCompletionBlock: Data (%lu bytes)",(unsigned long)[data length]);
120
121        NSString *path = @"/tmp/tsaresp.rsp";
122        NSDataWritingOptions writeOptionsMask = 0;
123        NSError *errorPtr = NULL;
124        [data writeToFile:path options:writeOptionsMask error:&errorPtr];
125        if (errorPtr)
126        {
127            xpctsaNSLog(@"TSA Response error dumping response: %@", errorPtr);
128            [errorPtr release];
129        }
130    }
131#endif
132}
133
134static void communicateWithTimeStampingServer(xpc_object_t event, const char *requestData, size_t requestLength, const char *tsaURL)
135{
136    if ((requestLength==0) || !tsaURL)
137        return;
138
139    xpctsaDebug("Request Length: %ld, URL: %s", requestLength, tsaURL);
140
141    __block CFDataRef tsaReq = CFDataCreate(kCFAllocatorDefault, (const unsigned char *)requestData, requestLength);
142
143    // The completion block is called when we have a response
144    TSARequestCompletionBlock reqCompletionBlock =
145    ^(NSURLResponse *response, NSData *data, NSError *err)
146    {
147        xpc_object_t tsaError = NULL;
148        xpc_object_t tsaStatusCode = NULL;
149        NSString *errStr = NULL;
150
151        debugShowTSAResponseInfo(response, data, err);
152
153        /*
154            Handle errors first. The bad responses seen so far tend to
155            return a bad HTTP status code rather than setting the "err"
156            parameter. In this case, the "data" parameter contains the
157            HTML of the error response, which we do not want to try to
158            parse as ASN.1 data.
159        */
160
161        if (response)
162        {
163            NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
164            NSInteger statusCode = [httpResponse statusCode];
165            if (statusCode != kHTTPResponseCodeOK)
166            {
167                errStr = [NSHTTPURLResponse localizedStringForStatusCode:(NSInteger)statusCode];
168                tsaStatusCode = xpc_int64_create((int64_t)statusCode);
169                xpctsaNSLog(@"TSA Response-b: %d, %@", (int)statusCode, errStr);
170            }
171        }
172
173        if (err && !errStr)
174        {
175            errStr = [err description];
176            if (!tsaStatusCode)
177            {
178                NSInteger statusCode = [err code];
179                tsaStatusCode = xpc_int64_create((int64_t)statusCode);
180            }
181        }
182
183        if (errStr)
184        {
185            const char *cerrstr = cfStringToCString((CFStringRef)errStr);
186            tsaError = xpc_string_create(cerrstr);
187            xpctsaNSLog(@"TSA Response-c: %@", errStr);
188        }
189
190        size_t length = (errStr || !data) ? 0 : [data length];
191        xpc_object_t tsaReply = xpc_data_create([data bytes], length);
192        xpc_connection_t peer = xpc_dictionary_get_remote_connection(event);
193        xpc_object_t reply = (tsaError)?
194            xpc_create_reply_with_format(event,
195                "{TimeStampReply: %value, TimeStampError: %value, TimeStampStatus: %value}", tsaReply, tsaError, tsaStatusCode) :
196            xpc_create_reply_with_format(event, "{TimeStampReply: %value}", tsaReply);
197        xpc_connection_send_message(peer, reply);
198        xpc_release(reply);
199
200        if (tsaReq)
201            CFRelease(tsaReq);
202        if (tsaReply)
203            xpc_release(tsaReply);
204        if (tsaError)
205            xpc_release(tsaError);
206        if (tsaStatusCode)
207            xpc_release(tsaStatusCode);
208    };
209
210    sendTSARequest(tsaReq, tsaURL, reqCompletionBlock);
211}
212
213void handle_request_event(struct connection_info *info, xpc_object_t event)
214{
215    xpc_connection_t peer = xpc_dictionary_get_remote_connection(event);
216    xpc_type_t xtype = xpc_get_type(event);
217
218    if (info->done)
219    {
220        xpctsaDebug("event %p while done", event);
221        return;
222    }
223	if (xtype == XPC_TYPE_ERROR)
224    {
225		if (event == XPC_ERROR_TERMINATION_IMMINENT) {
226			// launchd would like us to die, but we have open transactions.   When we finish with them xpc_service_main
227			// will exit for us, so there is nothing for us to do here.
228			return;
229		}
230
231        if (!info->done) {
232            info->done = true;
233            xpc_release(info->peer);
234        }
235        if (peer == NULL && XPC_ERROR_CONNECTION_INVALID == event && 0 != info->processed) {
236            // this is a normal shutdown on a connection that has processed at least
237            // one request.   Nothing intresting to log.
238            return;
239        }
240		xpctsaDebug("listener event error (connection %p): %s", peer, xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
241	}
242    else
243    if (xtype == XPC_TYPE_DICTIONARY)
244    {
245        size_t length = 0;
246        const char *operation = xpc_dictionary_get_string(event, "operation");
247        if (operation && !strcmp(operation, "TimeStampRequest"))
248        {
249            xpctsaDebug("Handling TimeStampRequest event");
250            const void *requestData = xpc_dictionary_get_data(event, "TimeStampRequest", &length);
251            const char *url = xpc_dictionary_get_string(event, "ServerURL");
252
253            communicateWithTimeStampingServer(event, requestData, length, url);
254        }
255        else
256            xpctsaDebug("Unknown op=%s request from pid %d", operation, xpc_connection_get_pid(peer));
257    }
258    else
259        xpctsaDebug("Unhandled request event=%p type=%p", event, xtype);
260}
261
262void finalize_connection(void *not_used)
263{
264	xpc_transaction_end();
265}
266
267void handle_connection_event(const xpc_connection_t peer)
268{
269    __block struct connection_info info;
270    info.peer = peer;
271    info.processed = 0;
272    info.done = false;
273
274    xpc_connection_set_event_handler(peer, ^(xpc_object_t event)
275    {
276        handle_request_event(&info, event);
277    });
278
279    //  unlike dispatch objects xpc objects don't need a context set in order to run a finalizer.   (we use our finalizer to
280    // end the transaction we are about to begin...this keeps xpc from idle exiting us while we have a live connection)
281    xpc_connection_set_finalizer_f(peer, finalize_connection);
282    xpc_transaction_begin();
283
284    // enable the peer connection to receive messages
285    xpc_connection_resume(peer);
286    xpc_retain(peer);
287}
288
289int main(int argc, const char *argv[])
290{
291    char *wait4debugger = getenv("WAIT4DEBUGGER");
292    if (wait4debugger && !strcasecmp("YES", wait4debugger))
293    {
294        syslog(LOG_ERR, "Waiting for debugger");
295        kill(getpid(), SIGSTOP);
296    }
297
298    xpc_main(handle_connection_event);
299
300    return EX_OSERR;
301}
302
303