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 = xpc_data_create([data bytes], length);
225 xpc_connection_t peer = xpc_dictionary_get_remote_connection(event);
226 xpc_object_t reply = (tsaError)?
227 xpc_create_reply_with_format(event,
228 "{TimeStampReply: %value, TimeStampError: %value, TimeStampStatus: %value}", tsaReply, tsaError, tsaStatusCode) :
229 xpc_create_reply_with_format(event, "{TimeStampReply: %value}", tsaReply);
230 xpc_connection_send_message(peer, reply);
236 xpc_release(tsaReply);
238 xpc_release(tsaError);
240 xpc_release(tsaStatusCode);
243 sendTSARequest(tsaReq, tsaURL, reqCompletionBlock);
246 void handle_request_event(struct connection_info *info, xpc_object_t event)
248 xpc_connection_t peer = xpc_dictionary_get_remote_connection(event);
249 xpc_type_t xtype = xpc_get_type(event);
253 xpctsaDebug("event %p while done", event);
256 if (xtype == XPC_TYPE_ERROR)
258 if (event == XPC_ERROR_TERMINATION_IMMINENT) {
259 // launchd would like us to die, but we have open transactions. When we finish with them xpc_service_main
260 // will exit for us, so there is nothing for us to do here.
266 xpc_release(info->peer);
268 if (peer == NULL && XPC_ERROR_CONNECTION_INVALID == event && 0 != info->processed) {
269 // this is a normal shutdown on a connection that has processed at least
270 // one request. Nothing intresting to log.
273 xpctsaDebug("listener event error (connection %p): %s", peer, xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
276 if (xtype == XPC_TYPE_DICTIONARY)
279 const char *operation = xpc_dictionary_get_string(event, "operation");
280 audit_token_t auditToken = {};
281 xpc_connection_get_audit_token(peer, &auditToken);
283 if (operation && !strcmp(operation, "TimeStampRequest"))
285 if (callerHasNetworkEntitlement(auditToken)) {
286 xpctsaDebug("Handling TimeStampRequest event");
287 const void *requestData = xpc_dictionary_get_data(event, "TimeStampRequest", &length);
288 const char *url = xpc_dictionary_get_string(event, "ServerURL");
290 communicateWithTimeStampingServer(event, requestData, length, url);
293 xpctsaDebug("No network entitlement for pid %d", xpc_connection_get_pid(peer));
296 xpctsaDebug("Unknown op=%s request from pid %d", operation, xpc_connection_get_pid(peer));
299 xpctsaDebug("Unhandled request event=%p type=%p", event, xtype);
302 void finalize_connection(void *not_used)
304 xpc_transaction_end();
307 void handle_connection_event(const xpc_connection_t peer)
309 __block struct connection_info info;
314 xpc_connection_set_event_handler(peer, ^(xpc_object_t event)
316 handle_request_event(&info, event);
319 // unlike dispatch objects xpc objects don't need a context set in order to run a finalizer. (we use our finalizer to
320 // end the transaction we are about to begin...this keeps xpc from idle exiting us while we have a live connection)
321 xpc_connection_set_finalizer_f(peer, finalize_connection);
322 xpc_transaction_begin();
324 // enable the peer connection to receive messages
325 xpc_connection_resume(peer);
329 int main(int argc, const char *argv[])
331 char *wait4debugger = getenv("WAIT4DEBUGGER");
332 if (wait4debugger && !strcasecmp("YES", wait4debugger))
334 syslog(LOG_ERR, "Waiting for debugger");
335 kill(getpid(), SIGSTOP);
338 xpc_main(handle_connection_event);