2 * Copyright (c) 2012,2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
25 #include "timestampclient.h"
27 #include <Security/SecTask.h>
28 #include <xpc/private.h>
30 struct connection_info {
31 xpc_connection_t peer;
36 xpc_object_t keychain_prefs_path = NULL;
37 xpc_object_t home = NULL;
40 xpc_create_reply_with_format(xpc_object_t original, const char * format, ...);
42 void finalize_connection(void *not_used);
43 void handle_connection_event(const xpc_connection_t peer);
44 void handle_request_event(struct connection_info *info, xpc_object_t event);
47 #define xpctsa_secinfo(format...) \
49 syslog(LOG_WARNING, format); \
51 #define xpctsaNSLog(format...) \
58 #define xpctsa_secinfo(format...)
59 #define xpctsaNSLog(format...)
61 #define xpctsaDebug(args...) xpctsa_secinfo(args)
66 #include <OSServices/NetworkUtilities.h>
68 I have no idea why they aren't more accessible.
71 #define kHTTPResponseCodeContinue 100
72 #define kHTTPResponseCodeOK 200
73 #define kHTTPResponseCodeNoContent 204
74 #define kHTTPResponseCodeBadRequest 400
75 #define kHTTPResponseCodeUnauthorized 401
76 #define kHTTPResponseCodeForbidden 403
77 #define kHTTPResponseCodeConflict 409
78 #define kHTTPResponseCodeExpectationFailed 417
79 #define kHTTPResponseCodeServFail 500
80 #define kHTTPResponseCodeInsufficientStorage 507
83 // Turn a CFString into a UTF8-encoded C string.
85 static char *cfStringToCString(CFStringRef inStr)
89 CFRetain(inStr); // compensate for release on exit
91 // need to extract into buffer
92 CFIndex length = CFStringGetLength(inStr); // in 16-bit character units
93 size_t len = 6 * length + 1;
94 char *buffer = malloc(len); // pessimistic
95 if (!CFStringGetCString(inStr, buffer, len, kCFStringEncodingUTF8))
102 static void debugShowTSAResponseInfo(NSURLResponse *response, NSData *data, NSError *err)
107 NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
108 NSInteger statusCode = [httpResponse statusCode];
109 NSDictionary *headers = [httpResponse allHeaderFields];
110 NSString *errStr2 = [NSHTTPURLResponse localizedStringForStatusCode:(NSInteger)statusCode];
112 xpctsaNSLog(@"TSA Response: %d, %@, %@", (int)statusCode, errStr2, headers);
116 { xpctsaNSLog(@"TSARequestCompletionBlock error: %@", err); }
120 xpctsaDebug("TSARequestCompletionBlock: Data (%lu bytes)",(unsigned long)[data length]);
121 xpctsaNSLog(@"TSARequestCompletionBlock: Data (%lu bytes)",(unsigned long)[data length]);
123 NSString *path = @"/tmp/tsaresp.rsp";
124 NSDataWritingOptions writeOptionsMask = 0;
125 NSError *errorPtr = NULL;
126 [data writeToFile:path options:writeOptionsMask error:&errorPtr];
129 xpctsaNSLog(@"TSA Response error dumping response: %@", errorPtr);
137 * Check whether the caller can access the network. Currently, this applies
138 * only to applications running under App Sandbox.
140 static bool callerHasNetworkEntitlement(audit_token_t auditToken)
142 bool result = true; /* until proven otherwise */
143 SecTaskRef task = SecTaskCreateWithAuditToken(NULL, auditToken);
145 CFTypeRef appSandboxValue = SecTaskCopyValueForEntitlement(task,
146 CFSTR("com.apple.security.app-sandbox"),
148 if(appSandboxValue != NULL) {
149 if(!CFEqual(kCFBooleanFalse, appSandboxValue)) {
150 CFTypeRef networkClientValue = SecTaskCopyValueForEntitlement(task,
151 CFSTR("com.apple.security.network.client"),
153 if(networkClientValue != NULL) {
154 result = (!CFEqual(kCFBooleanFalse, networkClientValue));
155 CFRelease(networkClientValue);
160 CFRelease(appSandboxValue);
167 static void communicateWithTimeStampingServer(xpc_object_t event, const char *requestData, size_t requestLength, const char *tsaURL)
169 if ((requestLength==0) || !tsaURL)
172 xpctsaDebug("Request Length: %ld, URL: %s", requestLength, tsaURL);
174 __block CFDataRef tsaReq = CFDataCreate(kCFAllocatorDefault, (const unsigned char *)requestData, requestLength);
176 // The completion block is called when we have a response
177 TSARequestCompletionBlock reqCompletionBlock =
178 ^(NSURLResponse *response, NSData *data, NSError *err)
180 xpc_object_t tsaError = NULL;
181 xpc_object_t tsaStatusCode = NULL;
182 NSString *errStr = NULL;
184 debugShowTSAResponseInfo(response, data, err);
187 Handle errors first. The bad responses seen so far tend to
188 return a bad HTTP status code rather than setting the "err"
189 parameter. In this case, the "data" parameter contains the
190 HTML of the error response, which we do not want to try to
196 NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
197 NSInteger statusCode = [httpResponse statusCode];
198 if (statusCode != kHTTPResponseCodeOK)
200 errStr = [NSHTTPURLResponse localizedStringForStatusCode:(NSInteger)statusCode];
201 tsaStatusCode = xpc_int64_create((int64_t)statusCode);
202 xpctsaNSLog(@"TSA Response-b: %d, %@", (int)statusCode, errStr);
208 errStr = [err description];
211 NSInteger statusCode = [err code];
212 tsaStatusCode = xpc_int64_create((int64_t)statusCode);
218 const char *cerrstr = cfStringToCString((CFStringRef)errStr);
219 tsaError = xpc_string_create(cerrstr);
220 xpctsaNSLog(@"TSA Response-c: %@", errStr);
223 size_t length = (errStr || !data) ? 0 : [data length];
224 xpc_object_t tsaReply = data ? xpc_data_create([data bytes], length) : NULL;
225 xpc_connection_t peer = xpc_dictionary_get_remote_connection(event);
226 xpc_object_t reply = NULL;
227 if (tsaError && tsaReply && tsaStatusCode) {
228 reply = xpc_create_reply_with_format(event, "{TimeStampReply: %value, TimeStampError: %value, TimeStampStatus: %value}",
229 tsaReply, tsaError, tsaStatusCode);
230 } else if (tsaReply) {
231 reply = xpc_create_reply_with_format(event, "{TimeStampReply: %value}", tsaReply);
233 reply = xpc_create_reply_with_format(event, "No TimeStampReply or TimeStampError");
235 xpc_connection_send_message(peer, reply);
241 xpc_release(tsaReply);
243 xpc_release(tsaError);
245 xpc_release(tsaStatusCode);
248 sendTSARequest(tsaReq, tsaURL, reqCompletionBlock);
251 void handle_request_event(struct connection_info *info, xpc_object_t event)
253 xpc_connection_t peer = xpc_dictionary_get_remote_connection(event);
254 xpc_type_t xtype = xpc_get_type(event);
258 xpctsaDebug("event %p while done", event);
261 if (xtype == XPC_TYPE_ERROR)
263 if (event == XPC_ERROR_TERMINATION_IMMINENT) {
264 // launchd would like us to die, but we have open transactions. When we finish with them xpc_service_main
265 // will exit for us, so there is nothing for us to do here.
271 xpc_release(info->peer);
273 if (peer == NULL && XPC_ERROR_CONNECTION_INVALID == event && 0 != info->processed) {
274 // this is a normal shutdown on a connection that has processed at least
275 // one request. Nothing intresting to log.
278 xpctsaDebug("listener event error (connection %p): %s", peer, xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
281 if (xtype == XPC_TYPE_DICTIONARY)
284 const char *operation = xpc_dictionary_get_string(event, "operation");
285 audit_token_t auditToken = {};
286 xpc_connection_get_audit_token(peer, &auditToken);
288 if (operation && !strcmp(operation, "TimeStampRequest"))
290 if (callerHasNetworkEntitlement(auditToken)) {
291 xpctsaDebug("Handling TimeStampRequest event");
292 const void *requestData = xpc_dictionary_get_data(event, "TimeStampRequest", &length);
293 const char *url = xpc_dictionary_get_string(event, "ServerURL");
295 communicateWithTimeStampingServer(event, requestData, length, url);
298 xpctsaDebug("No network entitlement for pid %d", xpc_connection_get_pid(peer));
301 xpctsaDebug("Unknown op=%s request from pid %d", operation, xpc_connection_get_pid(peer));
304 xpctsaDebug("Unhandled request event=%p type=%p", event, xtype);
307 void finalize_connection(void *not_used)
309 xpc_transaction_end();
312 void handle_connection_event(const xpc_connection_t peer)
314 __block struct connection_info info;
319 xpc_connection_set_event_handler(peer, ^(xpc_object_t event)
321 handle_request_event(&info, event);
324 // unlike dispatch objects xpc objects don't need a context set in order to run a finalizer. (we use our finalizer to
325 // end the transaction we are about to begin...this keeps xpc from idle exiting us while we have a live connection)
326 xpc_connection_set_finalizer_f(peer, finalize_connection);
327 xpc_transaction_begin();
329 // enable the peer connection to receive messages
330 xpc_connection_resume(peer);
334 int main(int argc, const char *argv[])
336 char *wait4debugger = getenv("WAIT4DEBUGGER");
337 if (wait4debugger && !strcasecmp("YES", wait4debugger))
339 syslog(LOG_ERR, "Waiting for debugger");
340 kill(getpid(), SIGSTOP);
343 xpc_main(handle_connection_event);