]> git.saurik.com Git - apple/security.git/blob - Analytics/SFAnalyticsLogger.m
Security-58286.20.16.tar.gz
[apple/security.git] / Analytics / SFAnalyticsLogger.m
1 /*
2 * Copyright (c) 2017 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 #if __OBJC2__
25
26 #import "SFAnalyticsLogger.h"
27 #import "SFSQLite.h"
28 #import "CKKSViewManager.h"
29 #import "debugging.h"
30 #import <objc/runtime.h>
31
32 NSString* const SFAnalyticsLoggerTableSuccessCount = @"success_count";
33 NSString* const SFAnalyticsLoggerColumnEventType = @"event_type";
34 NSString* const SFAnalyticsLoggerColumnSuccessCount = @"success_count";
35 NSString* const SFAnalyticsLoggerColumnHardFailureCount = @"hard_failure_count";
36 NSString* const SFAnalyticsLoggerColumnSoftFailureCount = @"soft_failure_count";
37
38 NSString* const SFAnalyticsLoggerTableHardFailures = @"hard_failures";
39 NSString* const SFAnalyticsLoggerTableSoftFailures = @"soft_failures";
40 NSString* const SFAnalyticsLoggerTableAllEvents = @"all_events";
41 NSString* const SFAnalyticsLoggerColumnDate = @"timestamp";
42 NSString* const SFAnalyticsLoggerColumnData = @"data";
43
44 NSString* const SFAnalyticsLoggerUploadDate = @"upload_date";
45
46 NSString* const SFAnalyticsLoggerSplunkTopic = @"topic";
47 NSString* const SFAnalyticsLoggerSplunkEventTime = @"eventTime";
48 NSString* const SFAnalyticsLoggerSplunkPostTime = @"postTime";
49 NSString* const SFAnalyticsLoggerSplunkEventType = @"eventType";
50 NSString* const SFAnalyticsLoggerMetricsBase = @"metricsBase";
51 NSString* const SFAnalyticsLoggerEventClassKey = @"eventClass";
52
53 NSString* const SFAnalyticsUserDefaultsSuite = @"com.apple.security.analytics";
54
55 static NSString* const SFAnalyticsLoggerTableSchema = @"CREATE TABLE IF NOT EXISTS hard_failures (\n"
56 @"id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
57 @"timestamp REAL,"
58 @"data BLOB\n"
59 @");\n"
60 @"CREATE TRIGGER IF NOT EXISTS maintain_ring_buffer_hard_failures AFTER INSERT ON hard_failures\n"
61 @"BEGIN\n"
62 @"DELETE FROM hard_failures WHERE id != NEW.id AND id % 999 = NEW.id % 999;\n"
63 @"END;\n"
64 @"CREATE TABLE IF NOT EXISTS soft_failures (\n"
65 @"id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
66 @"timestamp REAL,"
67 @"data BLOB\n"
68 @");\n"
69 @"CREATE TRIGGER IF NOT EXISTS maintain_ring_buffer_soft_failures AFTER INSERT ON soft_failures\n"
70 @"BEGIN\n"
71 @"DELETE FROM soft_failures WHERE id != NEW.id AND id % 999 = NEW.id % 999;\n"
72 @"END;\n"
73 @"CREATE TABLE IF NOT EXISTS all_events (\n"
74 @"id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
75 @"timestamp REAL,"
76 @"data BLOB\n"
77 @");\n"
78 @"CREATE TRIGGER IF NOT EXISTS maintain_ring_buffer_all_events AFTER INSERT ON all_events\n"
79 @"BEGIN\n"
80 @"DELETE FROM all_events WHERE id != NEW.id AND id % 10000 = NEW.id % 10000;\n"
81 @"END;\n"
82 @"CREATE TABLE IF NOT EXISTS success_count (\n"
83 @"event_type STRING PRIMARY KEY,\n"
84 @"success_count INTEGER,\n"
85 @"hard_failure_count INTEGER,\n"
86 @"soft_failure_count INTEGER\n"
87 @");\n";
88
89 #define SFANALYTICS_SPLUNK_DEV 0
90 #define SFANALYTICS_MAX_EVENTS_TO_REPORT 999
91
92 #if SFANALYTICS_SPLUNK_DEV
93 #define SECONDS_BETWEEN_UPLOADS 10
94 #else
95 // three days = 60 seconds times 60 minutes * 72 hours
96 #define SECONDS_BETWEEN_UPLOADS (60 * 60 * 72)
97 #endif
98
99 #define SECONDS_PER_DAY (60 * 60 * 24)
100
101 typedef NS_ENUM(NSInteger, SFAnalyticsEventClass) {
102 SFAnalyticsEventClassSuccess,
103 SFAnalyticsEventClassHardFailure,
104 SFAnalyticsEventClassSoftFailure,
105 SFAnalyticsEventClassNote
106 };
107
108 @interface SFAnalyticsLoggerSQLiteStore : SFSQLite
109
110 @property (readonly, strong) NSArray* failureRecords;
111 @property (readonly, strong) NSArray* allEvents;
112 @property (readwrite, strong) NSDate* uploadDate;
113
114 + (instancetype)storeWithPath:(NSString*)path schema:(NSString*)schema;
115
116 - (void)incrementSuccessCountForEventType:(NSString*)eventType;
117 - (void)incrementHardFailureCountForEventType:(NSString*)eventType;
118 - (void)incrementSoftFailureCountForEventType:(NSString*)eventType;
119 - (NSInteger)successCountForEventType:(NSString*)eventType;
120 - (NSInteger)hardFailureCountForEventType:(NSString*)eventType;
121 - (NSInteger)softFailureCountForEventType:(NSString*)eventType;
122 - (void)addEventDict:(NSDictionary*)eventDict toTable:(NSString*)table;
123 - (void)clearAllData;
124
125 - (NSDictionary*)summaryCounts;
126
127 @end
128
129 @implementation SFAnalyticsLogger {
130 SFAnalyticsLoggerSQLiteStore* _database;
131 NSURL* _splunkUploadURL;
132 NSString* _splunkTopicName;
133 NSURL* _splunkBagURL;
134 dispatch_queue_t _queue;
135 NSInteger _secondsBetweenUploads;
136 NSDictionary* _metricsBase; // data the server provides and wants us to send back
137 NSArray* _blacklistedFields;
138 NSArray* _blacklistedEvents;
139
140 unsigned int _allowInsecureSplunkCert:1;
141 unsigned int _disableLogging:1;
142 unsigned int _disableUploads:1;
143 unsigned int _ignoreServersMessagesTellingUsToGoAway:1;
144 }
145
146 @synthesize splunkUploadURL = _splunkUploadURL;
147 @synthesize splunkBagURL = _splunkBagURL;
148 @synthesize splunkTopicName = _splunkTopicName;
149 @synthesize splunkLoggingQueue = _queue;
150
151 + (instancetype)logger
152 {
153 #if TARGET_OS_SIMULATOR
154 return nil;
155 #else
156
157 if (self == [SFAnalyticsLogger class]) {
158 secerror("attempt to instatiate abstract class SFAnalyticsLogger");
159 return nil;
160 }
161
162 SFAnalyticsLogger* logger = nil;
163 @synchronized(self) {
164 logger = objc_getAssociatedObject(self, "SFAnalyticsLoggerInstance");
165 if (!logger) {
166 logger = [[self alloc] init];
167 objc_setAssociatedObject(self, "SFAnalyticsLoggerInstance", logger, OBJC_ASSOCIATION_RETAIN);
168 }
169 }
170 return logger;
171 #endif
172 }
173
174 + (NSString*)databasePath
175 {
176 return nil;
177 }
178
179 + (NSInteger)fuzzyDaysSinceDate:(NSDate*)date
180 {
181 NSTimeInterval timeIntervalSinceDate = [[NSDate date] timeIntervalSinceDate:date];
182 if (timeIntervalSinceDate < SECONDS_PER_DAY) {
183 return 0;
184 }
185 else if (timeIntervalSinceDate < (SECONDS_PER_DAY * 7)) {
186 return 1;
187 }
188 else if (timeIntervalSinceDate < (SECONDS_PER_DAY * 30)) {
189 return 7;
190 }
191 else if (timeIntervalSinceDate < (SECONDS_PER_DAY * 365)) {
192 return 30;
193 }
194 else {
195 return 365;
196 }
197 }
198
199 - (instancetype)init
200 {
201 if (self = [super init]) {
202 _database = [SFAnalyticsLoggerSQLiteStore storeWithPath:self.class.databasePath schema:SFAnalyticsLoggerTableSchema];
203 _queue = dispatch_queue_create("com.apple.security.analytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
204 _secondsBetweenUploads = SECONDS_BETWEEN_UPLOADS;
205
206 NSDictionary* systemDefaultValues = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle bundleWithPath:@"/System/Library/Frameworks/Security.framework"] pathForResource:@"SFAnalyticsLogging" ofType:@"plist"]];
207 _splunkTopicName = systemDefaultValues[@"splunk_topic"];
208 _splunkUploadURL = [NSURL URLWithString:systemDefaultValues[@"splunk_uploadURL"]];
209 _splunkBagURL = [NSURL URLWithString:systemDefaultValues[@"splunk_bagURL"]];
210 _allowInsecureSplunkCert = [[systemDefaultValues valueForKey:@"splunk_allowInsecureCertificate"] boolValue];
211 NSString* splunkEndpoint = systemDefaultValues[@"splunk_endpointDomain"];
212
213 NSUserDefaults* defaults = [[NSUserDefaults alloc] initWithSuiteName:SFAnalyticsUserDefaultsSuite];
214 NSString* userDefaultsSplunkTopic = [defaults stringForKey:@"splunk_topic"];
215 if (userDefaultsSplunkTopic) {
216 _splunkTopicName = userDefaultsSplunkTopic;
217 }
218
219 NSURL* userDefaultsSplunkUploadURL = [NSURL URLWithString:[defaults stringForKey:@"splunk_uploadURL"]];
220 if (userDefaultsSplunkUploadURL) {
221 _splunkUploadURL = userDefaultsSplunkUploadURL;
222 }
223
224 NSURL* userDefaultsSplunkBagURL = [NSURL URLWithString:[defaults stringForKey:@"splunk_bagURL"]];
225 if (userDefaultsSplunkUploadURL) {
226 _splunkBagURL = userDefaultsSplunkBagURL;
227 }
228
229 BOOL userDefaultsAllowInsecureSplunkCert = [defaults boolForKey:@"splunk_allowInsecureCertificate"];
230 _allowInsecureSplunkCert |= userDefaultsAllowInsecureSplunkCert;
231
232 NSString* userDefaultsSplunkEndpoint = [defaults stringForKey:@"splunk_endpointDomain"];
233 if (userDefaultsSplunkEndpoint) {
234 splunkEndpoint = userDefaultsSplunkEndpoint;
235 }
236
237 #if SFANALYTICS_SPLUNK_DEV
238 _ignoreServersMessagesTellingUsToGoAway = YES;
239
240 if (!_splunkUploadURL && splunkEndpoint) {
241 NSString* urlString = [NSString stringWithFormat:@"https://%@/report/2/%@", splunkEndpoint, _splunkTopicName];
242 _splunkUploadURL = [NSURL URLWithString:urlString];
243 }
244 #else
245 (void)splunkEndpoint;
246 #endif
247 }
248
249 return self;
250 }
251
252 - (void)logSuccessForEventNamed:(NSString*)eventName
253 {
254 [self logEventNamed:eventName class:SFAnalyticsEventClassSuccess attributes:nil];
255 }
256
257 - (void)logHardFailureForEventNamed:(NSString*)eventName withAttributes:(NSDictionary*)attributes
258 {
259 [self logEventNamed:eventName class:SFAnalyticsEventClassSoftFailure attributes:attributes];
260 }
261
262 - (void)logSoftFailureForEventNamed:(NSString*)eventName withAttributes:(NSDictionary*)attributes
263 {
264 [self logEventNamed:eventName class:SFAnalyticsEventClassHardFailure attributes:attributes];
265 }
266
267 - (void)noteEventNamed:(NSString*)eventName
268 {
269 [self logEventNamed:eventName class:SFAnalyticsEventClassNote attributes:nil];
270 }
271
272 - (void)logEventNamed:(NSString*)eventName class:(SFAnalyticsEventClass)class attributes:(NSDictionary*)attributes
273 {
274 if (!eventName) {
275 secinfo("SFAnalytics", "attempt to log an event with no name");
276 return;
277 }
278
279 __block NSDate* uploadDate = nil;
280 __weak __typeof(self) weakSelf = self;
281 dispatch_sync(_queue, ^{
282 __strong __typeof(self) strongSelf = weakSelf;
283 if (!strongSelf || strongSelf->_disableLogging || [strongSelf->_blacklistedEvents containsObject:eventName]) {
284 return;
285 }
286
287 NSDictionary* eventDict = [self eventDictForEventName:eventName withAttributes:attributes eventClass:class];
288 [strongSelf->_database addEventDict:eventDict toTable:SFAnalyticsLoggerTableAllEvents];
289
290 if (class == SFAnalyticsEventClassHardFailure) {
291 NSDictionary* strippedDict = [self eventDictWithBlacklistedFieldsStrippedFrom:eventDict];
292 [strongSelf->_database addEventDict:strippedDict toTable:SFAnalyticsLoggerTableHardFailures];
293 [strongSelf->_database incrementHardFailureCountForEventType:eventName];
294 }
295 else if (class == SFAnalyticsEventClassSoftFailure) {
296 NSDictionary* strippedDict = [self eventDictWithBlacklistedFieldsStrippedFrom:eventDict];
297 [strongSelf->_database addEventDict:strippedDict toTable:SFAnalyticsLoggerTableSoftFailures];
298 [strongSelf->_database incrementSoftFailureCountForEventType:eventName];
299 }
300 else if (class == SFAnalyticsEventClassSuccess || class == SFAnalyticsEventClassNote) {
301 [strongSelf->_database incrementSuccessCountForEventType:eventName];
302 }
303
304 uploadDate = strongSelf->_database.uploadDate;
305 });
306
307 NSDate* nowDate = [NSDate date];
308 if (uploadDate) {
309 if ([nowDate compare:uploadDate] == NSOrderedDescending) {
310 NSError* error = nil;
311 BOOL uploadSuccess = [self forceUploadWithError:&error];
312 if (uploadSuccess) {
313 secinfo("SFAnalytics", "uploaded sync health data");
314 [self resetUploadDate:YES];
315 }
316
317 if (error) {
318 secerror("SFAnalytics: failed to upload json to analytics server with error: %@", error);
319 }
320 }
321 }
322 else {
323 [self resetUploadDate:NO];
324 }
325 }
326
327 - (void)resetUploadDate:(BOOL)clearData
328 {
329 __weak __typeof(self) weakSelf = self;
330 dispatch_sync(_queue, ^{
331 __strong __typeof(self) strongSelf = weakSelf;
332 if (!strongSelf) {
333 return;
334 }
335
336 if (clearData) {
337 [strongSelf->_database clearAllData];
338 }
339 strongSelf->_database.uploadDate = [NSDate dateWithTimeIntervalSinceNow:strongSelf->_secondsBetweenUploads];
340 });
341 }
342
343 - (NSDictionary*)eventDictForEventName:(NSString*)eventName withAttributes:(NSDictionary*)attributes eventClass:(SFAnalyticsEventClass)eventClass
344 {
345 NSMutableDictionary* eventDict = attributes ? attributes.mutableCopy : [NSMutableDictionary dictionary];
346 eventDict[SFAnalyticsLoggerSplunkTopic] = _splunkTopicName;
347 eventDict[SFAnalyticsLoggerSplunkEventType] = eventName;
348 eventDict[SFAnalyticsLoggerSplunkEventTime] = @([[NSDate date] timeIntervalSince1970] * 1000);
349 eventDict[SFAnalyticsLoggerEventClassKey] = @(eventClass);
350
351 [_metricsBase enumerateKeysAndObjectsUsingBlock:^(NSString* key, id object, BOOL* stop) {
352 if (!eventDict[key]) {
353 eventDict[key] = object;
354 }
355 }];
356
357 return eventDict;
358 }
359
360 - (NSDictionary*)eventDictWithBlacklistedFieldsStrippedFrom:(NSDictionary*)eventDict
361 {
362 NSMutableDictionary* strippedDict = eventDict.mutableCopy;
363 for (NSString* blacklistedField in _blacklistedFields) {
364 [strippedDict removeObjectForKey:blacklistedField];
365 }
366 return strippedDict;
367 }
368
369 - (void)setDateProperty:(NSDate*)date forKey:(NSString*)key
370 {
371 dispatch_sync(_queue, ^{
372 [self->_database setDateProperty:date forKey:key];
373 });
374 }
375
376 - (NSDate*)datePropertyForKey:(NSString*)key
377 {
378 __block NSDate* result = nil;
379 dispatch_sync(_queue, ^{
380 result = [self->_database datePropertyForKey:key];
381 });
382 return result;
383 }
384
385 - (NSDictionary*)extraValuesToUploadToServer
386 {
387 return [NSDictionary dictionary];
388 }
389
390 // this method is kind of evil for the fact that it has side-effects in pulling other things besides the metricsURL from the server, and as such should NOT be memoized.
391 // TODO redo this, probably to return a dictionary.
392 - (NSURL*)splunkUploadURL
393 {
394 dispatch_assert_queue(_queue);
395
396 if (_splunkUploadURL) {
397 return _splunkUploadURL;
398 }
399
400 __weak __typeof(self) weakSelf = self;
401 dispatch_semaphore_t sem = dispatch_semaphore_create(0);
402
403 __block NSError* error = nil;
404 NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
405 NSURLSession* storeBagSession = [NSURLSession sessionWithConfiguration:defaultConfiguration
406 delegate:self
407 delegateQueue:nil];
408
409 NSURL* requestEndpoint = _splunkBagURL;
410 __block NSURL* result = nil;
411 NSURLSessionDataTask* storeBagTask = [storeBagSession dataTaskWithURL:requestEndpoint completionHandler:^(NSData * _Nullable data,
412 NSURLResponse * _Nullable __unused response,
413 NSError * _Nullable responseError) {
414
415 __strong __typeof(self) strongSelf = weakSelf;
416 if (!strongSelf) {
417 return;
418 }
419
420 if (data && !responseError) {
421 NSData *responseData = data; // shut up compiler
422 NSDictionary* responseDict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
423 if([responseDict isKindOfClass:NSDictionary.class] && !error) {
424 if (!self->_ignoreServersMessagesTellingUsToGoAway) {
425 strongSelf->_disableLogging = [[responseDict valueForKey:@"disabled"] boolValue];
426 if (strongSelf->_disableLogging || [[responseDict valueForKey:@"sendDisabled"] boolValue]) {
427 // then don't upload anything right now
428 secerror("not returning a splunk URL because uploads are disabled");
429 dispatch_semaphore_signal(sem);
430 return;
431 }
432
433 NSUInteger millisecondsBetweenUploads = [[responseDict valueForKey:@"postFrequency"] unsignedIntegerValue] / 1000;
434 if (millisecondsBetweenUploads > 0) {
435 strongSelf->_secondsBetweenUploads = millisecondsBetweenUploads;
436 }
437
438 strongSelf->_blacklistedEvents = responseDict[@"blacklistedEvents"];
439 strongSelf->_blacklistedFields = responseDict[@"blacklistedFields"];
440 }
441
442 strongSelf->_metricsBase = responseDict[@"metricsBase"];
443
444 NSString* metricsEndpoint = responseDict[@"metricsUrl"];
445 if([metricsEndpoint isKindOfClass:NSString.class]) {
446 /* Lives our URL */
447 NSString* endpoint = [metricsEndpoint stringByAppendingFormat:@"/2/%@", strongSelf->_splunkTopicName];
448 secnotice("ckks", "got metrics endpoint: %@", endpoint);
449 NSURL* endpointURL = [NSURL URLWithString:endpoint];
450 if([endpointURL.scheme isEqualToString:@"https"]) {
451 result = endpointURL;
452 }
453 }
454 }
455 }
456 else {
457 error = responseError;
458 }
459 if(error) {
460 secnotice("ckks", "Unable to fetch splunk endpoint at URL: %@ -- error: %@", requestEndpoint, error.description);
461 }
462 else if(!result) {
463 secnotice("ckks", "Malformed iTunes config payload!");
464 }
465
466 dispatch_semaphore_signal(sem);
467 }];
468
469 [storeBagTask resume];
470 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
471
472 return result;
473 }
474
475 - (BOOL)forceUploadWithError:(NSError**)error
476 {
477 __block BOOL result = NO;
478 NSData* json = [self getLoggingJSONWithError:error];
479 dispatch_sync(_queue, ^{
480 if (json && [self _onQueuePostJSON:json error:error]) {
481 secinfo("ckks", "uploading sync health data: %@", json);
482
483 [self->_database clearAllData];
484 self->_database.uploadDate = [NSDate dateWithTimeIntervalSinceNow:self->_secondsBetweenUploads];
485 result = YES;
486 }
487 else {
488 result = NO;
489 }
490 });
491
492 return result;
493 }
494
495 - (BOOL)_onQueuePostJSON:(NSData*)json error:(NSError**)error
496 {
497 dispatch_assert_queue(_queue);
498
499 /*
500 * Create the NSURLSession
501 * We use the ephemeral session config because we don't need cookies or cache
502 */
503 NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
504 NSURLSession* postSession = [NSURLSession sessionWithConfiguration:defaultConfiguration
505 delegate:self
506 delegateQueue:nil];
507
508 /*
509 * Create the request
510 */
511 NSURL* postEndpoint = self.splunkUploadURL;
512 if (!postEndpoint) {
513 secerror("failed to get a splunk upload endpoint - not uploading");
514 return NO;
515 }
516
517 NSMutableURLRequest* postRequest = [[NSMutableURLRequest alloc] init];
518 postRequest.URL = postEndpoint;
519 postRequest.HTTPMethod = @"POST";
520 postRequest.HTTPBody = json;
521
522 /*
523 * Create the upload task.
524 */
525 dispatch_semaphore_t sem = dispatch_semaphore_create(0);
526 __block BOOL uploadSuccess = NO;
527 NSURLSessionDataTask* uploadTask = [postSession dataTaskWithRequest:postRequest
528 completionHandler:^(NSData * _Nullable __unused data, NSURLResponse * _Nullable response, NSError * _Nullable requestError) {
529 if(requestError) {
530 secerror("Error in uploading the events to splunk: %@", requestError);
531 }
532 else if (![response isKindOfClass:NSHTTPURLResponse.class]){
533 Class class = response.class;
534 secerror("Received the wrong kind of response: %@", NSStringFromClass(class));
535 }
536 else {
537 NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
538 if(httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
539 /* Success */
540 uploadSuccess = YES;
541 secnotice("ckks", "Splunk upload success");
542 }
543 else {
544 secnotice("ckks", "Splunk upload unexpected status to URL: %@ -- status: %d", postEndpoint, (int)(httpResponse.statusCode));
545 }
546 }
547 dispatch_semaphore_signal(sem);
548 }];
549
550 secnotice("ckks", "Splunk upload start");
551 [uploadTask resume];
552 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
553 return uploadSuccess;
554 }
555
556 - (NSString*)stringForEventClass:(SFAnalyticsEventClass)eventClass
557 {
558 if (eventClass == SFAnalyticsEventClassNote) {
559 return @"EventNote";
560 }
561 else if (eventClass == SFAnalyticsEventClassSuccess) {
562 return @"EventSuccess";
563 }
564 else if (eventClass == SFAnalyticsEventClassHardFailure) {
565 return @"EventHardFailure";
566 }
567 else if (eventClass == SFAnalyticsEventClassSoftFailure) {
568 return @"EventSoftFailure";
569 }
570 else {
571 return @"EventUnknown";
572 }
573 }
574
575 - (NSString*)sysdiagnoseStringForEventRecord:(NSDictionary*)eventRecord
576 {
577 NSMutableDictionary* mutableEventRecord = eventRecord.mutableCopy;
578 [mutableEventRecord removeObjectForKey:SFAnalyticsLoggerSplunkTopic];
579
580 NSDate* eventDate = [NSDate dateWithTimeIntervalSince1970:[[eventRecord valueForKey:SFAnalyticsLoggerSplunkEventTime] doubleValue] / 1000];
581 [mutableEventRecord removeObjectForKey:SFAnalyticsLoggerSplunkEventTime];
582
583 NSString* eventName = eventRecord[SFAnalyticsLoggerSplunkEventType];
584 [mutableEventRecord removeObjectForKey:SFAnalyticsLoggerSplunkEventType];
585
586 SFAnalyticsEventClass eventClass = [[eventRecord valueForKey:SFAnalyticsLoggerEventClassKey] integerValue];
587 NSString* eventClassString = [self stringForEventClass:eventClass];
588 [mutableEventRecord removeObjectForKey:SFAnalyticsLoggerEventClassKey];
589
590 NSMutableString* additionalAttributesString = [NSMutableString string];
591 if (mutableEventRecord.count > 0) {
592 [additionalAttributesString appendString:@" - Attributes: {" ];
593 __block BOOL firstAttribute = YES;
594 [mutableEventRecord enumerateKeysAndObjectsUsingBlock:^(NSString* key, id object, BOOL* stop) {
595 NSString* openingString = firstAttribute ? @"" : @", ";
596 [additionalAttributesString appendString:[NSString stringWithFormat:@"%@%@ : %@", openingString, key, object]];
597 firstAttribute = NO;
598 }];
599 [additionalAttributesString appendString:@" }"];
600 }
601
602 return [NSString stringWithFormat:@"%@ %@: %@%@", eventDate, eventClassString, eventName, additionalAttributesString];
603 }
604
605 - (NSString*)getSysdiagnoseDumpWithError:(NSError**)error
606 {
607 NSMutableString* sysdiagnose = [[NSMutableString alloc] init];
608
609 NSDictionary* extraValues = self.extraValuesToUploadToServer;
610 [extraValues enumerateKeysAndObjectsUsingBlock:^(NSString* key, id object, BOOL* stop) {
611 [sysdiagnose appendFormat:@"Key: %@, Value: %@\n", key, object];
612 }];
613
614 [sysdiagnose appendString:@"\n"];
615
616 dispatch_sync(_queue, ^{
617 NSArray* allEvents = self->_database.allEvents;
618 for (NSDictionary* eventRecord in allEvents) {
619 [sysdiagnose appendFormat:@"%@\n", [self sysdiagnoseStringForEventRecord:eventRecord]];
620 }
621 });
622
623 return sysdiagnose;
624 }
625
626 - (NSData*)getLoggingJSONWithError:(NSError**)error
627 {
628 __block NSData* json = nil;
629 NSDictionary* extraValues = self.extraValuesToUploadToServer;
630 dispatch_sync(_queue, ^{
631 NSArray* failureRecords = self->_database.failureRecords;
632
633 NSDictionary* successCounts = self->_database.summaryCounts;
634 NSInteger totalSuccessCount = 0;
635 NSInteger totalHardFailureCount = 0;
636 NSInteger totalSoftFailureCount = 0;
637 for (NSDictionary* perEventTypeSuccessCounts in successCounts.objectEnumerator) {
638 totalSuccessCount += [perEventTypeSuccessCounts[SFAnalyticsLoggerColumnSuccessCount] integerValue];
639 totalHardFailureCount += [perEventTypeSuccessCounts[SFAnalyticsLoggerColumnHardFailureCount] integerValue];
640 totalSoftFailureCount += [perEventTypeSuccessCounts[SFAnalyticsLoggerColumnSoftFailureCount] integerValue];
641 }
642
643 NSDate* now = [NSDate date];
644
645 NSMutableDictionary* healthSummaryEvent = extraValues ? extraValues.mutableCopy : [[NSMutableDictionary alloc] init];
646 healthSummaryEvent[SFAnalyticsLoggerSplunkTopic] = self->_splunkTopicName ?: [NSNull null];
647 healthSummaryEvent[SFAnalyticsLoggerSplunkEventTime] = @([now timeIntervalSince1970] * 1000);
648 healthSummaryEvent[SFAnalyticsLoggerSplunkEventType] = @"ckksHealthSummary";
649 healthSummaryEvent[SFAnalyticsLoggerColumnSuccessCount] = @(totalSuccessCount);
650 healthSummaryEvent[SFAnalyticsLoggerColumnHardFailureCount] = @(totalHardFailureCount);
651 healthSummaryEvent[SFAnalyticsLoggerColumnSoftFailureCount] = @(totalSoftFailureCount);
652
653 NSMutableArray* splunkRecords = failureRecords.mutableCopy;
654 [splunkRecords addObject:healthSummaryEvent];
655
656 NSDictionary* jsonDict = @{SFAnalyticsLoggerSplunkPostTime : @([now timeIntervalSince1970] * 1000), @"events" : splunkRecords};
657
658 json = [NSJSONSerialization dataWithJSONObject:jsonDict options:NSJSONWritingPrettyPrinted error:error];
659 });
660
661 return json;
662 }
663
664 - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
665 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {
666 assert(completionHandler);
667 (void)session;
668 secnotice("ckks", "Splunk upload challenge");
669 NSURLCredential *cred = nil;
670 SecTrustResultType result = kSecTrustResultInvalid;
671
672 if ([challenge previousFailureCount] > 0) {
673 // Previous failures occurred, bail
674 completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
675
676 } else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
677 /*
678 * Evaluate trust for the certificate
679 */
680
681 SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
682 SecTrustEvaluate(serverTrust, &result);
683 if (_allowInsecureSplunkCert || (result == kSecTrustResultProceed) || (result == kSecTrustResultUnspecified)) {
684 /*
685 * All is well, accept the credentials
686 */
687 if(_allowInsecureSplunkCert) {
688 secnotice("ckks", "Force Accepting Splunk Credential");
689 }
690 cred = [NSURLCredential credentialForTrust:serverTrust];
691 completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
692
693 } else {
694 /*
695 * An error occurred in evaluating trust, bail
696 */
697 completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
698 }
699 } else {
700 /*
701 * Just perform the default handling
702 */
703 completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
704 }
705
706 }
707
708 - (BOOL)ignoreServerDisablingMessages
709 {
710 return _ignoreServersMessagesTellingUsToGoAway;
711 }
712
713 - (void)setIgnoreServerDisablingMessages:(BOOL)ignoreServer
714 {
715 _ignoreServersMessagesTellingUsToGoAway = ignoreServer ? YES : NO;
716 }
717
718 - (BOOL)allowsInsecureSplunkCert
719 {
720 return _allowInsecureSplunkCert;
721 }
722
723 - (void)setAllowsInsecureSplunkCert:(BOOL)allowsInsecureSplunkCert
724 {
725 _allowInsecureSplunkCert = allowsInsecureSplunkCert ? YES : NO;
726 }
727
728 @end
729
730 @implementation SFAnalyticsLoggerSQLiteStore
731
732 + (instancetype)storeWithPath:(NSString*)path schema:(NSString*)schema
733 {
734 SFAnalyticsLoggerSQLiteStore* store = nil;
735 @synchronized([SFAnalyticsLoggerSQLiteStore class]) {
736 static NSMutableDictionary* loggingStores = nil;
737 static dispatch_once_t onceToken;
738 dispatch_once(&onceToken, ^{
739 loggingStores = [[NSMutableDictionary alloc] init];
740 });
741
742 NSString* standardizedPath = path.stringByStandardizingPath;
743 store = loggingStores[standardizedPath];
744 if (!store) {
745 store = [[self alloc] initWithPath:standardizedPath schema:schema];
746 loggingStores[standardizedPath] = store;
747 }
748
749 [store open];
750 }
751
752 return store;
753 }
754
755 - (void)dealloc
756 {
757 [self close];
758 }
759
760 - (NSInteger)successCountForEventType:(NSString*)eventType
761 {
762 return [[[[self select:@[SFAnalyticsLoggerColumnSuccessCount] from:SFAnalyticsLoggerTableSuccessCount where:@"event_type = ?" bindings:@[eventType]] firstObject] valueForKey:SFAnalyticsLoggerColumnSuccessCount] integerValue];
763 }
764
765 - (void)incrementSuccessCountForEventType:(NSString*)eventType
766 {
767 NSInteger successCount = [self successCountForEventType:eventType];
768 NSInteger hardFailureCount = [self hardFailureCountForEventType:eventType];
769 NSInteger softFailureCount = [self softFailureCountForEventType:eventType];
770 [self insertOrReplaceInto:SFAnalyticsLoggerTableSuccessCount values:@{SFAnalyticsLoggerColumnEventType : eventType, SFAnalyticsLoggerColumnSuccessCount : @(successCount + 1), SFAnalyticsLoggerColumnHardFailureCount : @(hardFailureCount), SFAnalyticsLoggerColumnSoftFailureCount : @(softFailureCount)}];
771 }
772
773 - (NSInteger)hardFailureCountForEventType:(NSString*)eventType
774 {
775 return [[[[self select:@[SFAnalyticsLoggerColumnHardFailureCount] from:SFAnalyticsLoggerTableSuccessCount where:@"event_type = ?" bindings:@[eventType]] firstObject] valueForKey:SFAnalyticsLoggerColumnHardFailureCount] integerValue];
776 }
777
778 - (NSInteger)softFailureCountForEventType:(NSString*)eventType
779 {
780 return [[[[self select:@[SFAnalyticsLoggerColumnSoftFailureCount] from:SFAnalyticsLoggerTableSuccessCount where:@"event_type = ?" bindings:@[eventType]] firstObject] valueForKey:SFAnalyticsLoggerColumnSoftFailureCount] integerValue];
781 }
782
783 - (void)incrementHardFailureCountForEventType:(NSString*)eventType
784 {
785 NSInteger successCount = [self successCountForEventType:eventType];
786 NSInteger hardFailureCount = [self hardFailureCountForEventType:eventType];
787 NSInteger softFailureCount = [self softFailureCountForEventType:eventType];
788 [self insertOrReplaceInto:SFAnalyticsLoggerTableSuccessCount values:@{SFAnalyticsLoggerColumnEventType : eventType, SFAnalyticsLoggerColumnSuccessCount : @(successCount), SFAnalyticsLoggerColumnHardFailureCount : @(hardFailureCount + 1), SFAnalyticsLoggerColumnSoftFailureCount : @(softFailureCount)}];
789 }
790
791 - (void)incrementSoftFailureCountForEventType:(NSString*)eventType
792 {
793 NSInteger successCount = [self successCountForEventType:eventType];
794 NSInteger hardFailureCount = [self hardFailureCountForEventType:eventType];
795 NSInteger softFailureCount = [self softFailureCountForEventType:eventType];
796 [self insertOrReplaceInto:SFAnalyticsLoggerTableSuccessCount values:@{SFAnalyticsLoggerColumnEventType : eventType, SFAnalyticsLoggerColumnSuccessCount : @(successCount), SFAnalyticsLoggerColumnHardFailureCount : @(hardFailureCount), SFAnalyticsLoggerColumnSoftFailureCount : @(softFailureCount + 1)}];
797 }
798
799 - (NSDictionary*)summaryCounts
800 {
801 NSMutableDictionary* successCountsDict = [NSMutableDictionary dictionary];
802 NSArray* rows = [self selectAllFrom:SFAnalyticsLoggerTableSuccessCount where:nil bindings:nil];
803 for (NSDictionary* rowDict in rows) {
804 NSString* eventName = rowDict[SFAnalyticsLoggerColumnEventType];
805 if (!eventName) {
806 secinfo("SFAnalytics", "ignoring entry in success counts table without an event name");
807 continue;
808 }
809
810 successCountsDict[eventName] = @{SFAnalyticsLoggerTableSuccessCount : rowDict[SFAnalyticsLoggerColumnSuccessCount], SFAnalyticsLoggerColumnHardFailureCount : rowDict[SFAnalyticsLoggerColumnHardFailureCount], SFAnalyticsLoggerColumnSoftFailureCount : rowDict[SFAnalyticsLoggerColumnSoftFailureCount]};
811 }
812
813 return successCountsDict;
814 }
815
816 - (NSArray*)failureRecords
817 {
818 NSArray* recordBlobs = [self select:@[SFAnalyticsLoggerColumnData] from:SFAnalyticsLoggerTableHardFailures];
819 if (recordBlobs.count < SFANALYTICS_MAX_EVENTS_TO_REPORT) {
820 NSArray* softFailureBlobs = [self select:@[SFAnalyticsLoggerColumnData] from:SFAnalyticsLoggerTableSoftFailures];
821 if (softFailureBlobs.count > 0) {
822 NSInteger numSoftFailuresToReport = SFANALYTICS_MAX_EVENTS_TO_REPORT - recordBlobs.count;
823 recordBlobs = [recordBlobs arrayByAddingObjectsFromArray:[softFailureBlobs subarrayWithRange:NSMakeRange(softFailureBlobs.count - numSoftFailuresToReport, numSoftFailuresToReport)]];
824 }
825 }
826
827 NSMutableArray* failureRecords = [[NSMutableArray alloc] init];
828 for (NSDictionary* row in recordBlobs) {
829 NSDictionary* deserializedRecord = [NSPropertyListSerialization propertyListWithData:row[SFAnalyticsLoggerColumnData] options:0 format:nil error:nil];
830 [failureRecords addObject:deserializedRecord];
831 }
832
833 return failureRecords;
834 }
835
836 - (NSArray*)allEvents
837 {
838 NSArray* recordBlobs = [self select:@[SFAnalyticsLoggerColumnData] from:SFAnalyticsLoggerTableAllEvents];
839 NSMutableArray* records = [[NSMutableArray alloc] init];
840 for (NSDictionary* row in recordBlobs) {
841 NSDictionary* deserializedRecord = [NSPropertyListSerialization propertyListWithData:row[SFAnalyticsLoggerColumnData] options:0 format:nil error:nil];
842 [records addObject:deserializedRecord];
843 }
844 return records;
845 }
846
847 - (void)addEventDict:(NSDictionary*)eventDict toTable:(NSString*)table
848 {
849 NSError* error = nil;
850 NSData* serializedRecord = [NSPropertyListSerialization dataWithPropertyList:eventDict format:NSPropertyListBinaryFormat_v1_0 options:0 error:&error];
851 if(!error && serializedRecord) {
852 [self insertOrReplaceInto:table values:@{SFAnalyticsLoggerColumnDate : [NSDate date], SFAnalyticsLoggerColumnData : serializedRecord}];
853 }
854 if(error && !serializedRecord) {
855 secerror("Couldn't serialize failure record: %@", error);
856 }
857 }
858
859 - (NSDate*)uploadDate
860 {
861 return [self datePropertyForKey:SFAnalyticsLoggerUploadDate];
862 }
863
864 - (void)setUploadDate:(NSDate*)uploadDate
865 {
866 [self setDateProperty:uploadDate forKey:SFAnalyticsLoggerUploadDate];
867 }
868
869 - (void)clearAllData
870 {
871 [self deleteFrom:SFAnalyticsLoggerTableSuccessCount where:@"event_type like ?" bindings:@[@"%"]];
872 [self deleteFrom:SFAnalyticsLoggerTableHardFailures where:@"id >= 0" bindings:nil];
873 [self deleteFrom:SFAnalyticsLoggerTableSoftFailures where:@"id >= 0" bindings:nil];
874 }
875
876 @end
877
878 #endif // __OBJC2__