forked from SelfControlApp/selfcontrol
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathHelperCommon.m
475 lines (403 loc) · 19.7 KB
/
HelperCommon.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
/*
* HelperCommonFunctions.c
* SelfControl
*
* Created by Charlie Stigler on 7/13/10.
* Copyright 2010 Harvard-Westlake Student. All rights reserved.
*
*/
#include "HelperCommon.h"
void addRulesToFirewall(signed long long int controllingUID) {
// Note all arrays in the host blocking code were changed to sets to easily stop duplicates
NSMutableSet* hostsToBlock = [NSMutableSet set];
[NSUserDefaults resetStandardUserDefaults];
seteuid(controllingUID);
defaults = [NSUserDefaults standardUserDefaults];
[defaults addSuiteNamed:@"org.eyebeam.SelfControl"];
BOOL shouldEvaluateCommonSubdomains = [[defaults objectForKey: @"EvaluateCommonSubdomains"] boolValue];
[defaults synchronize];
[NSUserDefaults resetStandardUserDefaults];
seteuid(0);
for(int i = 0; i < [domainList count]; i++) {
NSString* hostName;
int portNum;
int maskLen;
parseHost([domainList objectAtIndex: i], &hostName, &maskLen, &portNum);
if([hostName isEqualToString: @"*"]) {
[hostsToBlock addObject: [domainList objectAtIndex: i]];
}
NSString* ipValidationRegex = @"^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$";
NSPredicate *regexTester = [NSPredicate
predicateWithFormat:@"SELF MATCHES %@",
ipValidationRegex];
if ([regexTester evaluateWithObject: hostName])
[hostsToBlock addObject: [domainList objectAtIndex: i]];
else {
// We have a domain name, we need to resolve it first
NSHost* host = [NSHost hostWithName: hostName];
if(host) {
NSArray* addresses = [host addresses];
for(int j = 0; j < [addresses count]; j++) {
if(portNum != -1)
[hostsToBlock addObject: [NSString stringWithFormat: @"%@:%d", [addresses objectAtIndex: j], portNum]];
else [hostsToBlock addObject: [addresses objectAtIndex: j]];
}
}
if(shouldEvaluateCommonSubdomains) {
// Get the evaluated hostnames and union (combine) them with our current set
NSSet* evaluatedHosts = getEvaluatedHostNamesFromCommonSubdomains(hostName, portNum);
[hostsToBlock unionSet: evaluatedHosts];
}
}
}
// This section is broken and plus seems to slow down parsing too much to be
// useful. Consider reintroduction later, possibly with modifications?
/*
// OpenDNS, the very popular DNS provider, doesn't return NXDOMAIN. Instead,
// all nonexistent DNS requests are pointed to hit-nxdomain.opendns.com. We
// don't want to accidentally block that if one of our DNS resolutions fails,
// so we'll filter for those addresses.
NSHost* openDNSNXDomain = [NSHost hostWithName: @"hit-nxdomain.opendns.com"];
if(openDNSNXDomain) {
NSArray* addresses = [openDNSNXDomain addresses];
for(int j = 0; j < [addresses count]; j++) {
NSPredicate* openDNSFilter = [NSPredicate predicateWithFormat: @"NOT SELF beginswith '%@'", [addresses objectAtIndex: j]];
[hostsToBlock filterUsingPredicate: openDNSFilter];
}
}
*/
BOOL blockAsWhitelist;
NSDictionary* curDictionary = [NSDictionary dictionaryWithContentsOfFile: SelfControlLockFilePath];
if(curDictionary == nil || [curDictionary objectForKey: @"BlockAsWhitelist"] == nil) {
[NSUserDefaults resetStandardUserDefaults];
seteuid(controllingUID);
defaults = [NSUserDefaults standardUserDefaults];
[defaults addSuiteNamed:@"org.eyebeam.SelfControl"];
blockAsWhitelist = [defaults boolForKey: @"BlockAsWhitelist"];
[defaults synchronize];
[NSUserDefaults resetStandardUserDefaults];
seteuid(0);
}
else
blockAsWhitelist = [[curDictionary objectForKey: @"BlockAsWhitelist"] boolValue];
// /etc/hosts blocking
if(!blockAsWhitelist) {
HostFileBlocker* hostFileBlocker = [[[HostFileBlocker alloc] init] autorelease];
if(![hostFileBlocker containsSelfControlBlock] && [hostFileBlocker createBackupHostsFile]) {
[hostFileBlocker addSelfControlBlockHeader];
for(int i = 0; i < [domainList count]; i++) {
NSString* hostName;
int portNum;
int maskLen;
parseHost([domainList objectAtIndex: i], &hostName, &maskLen, &portNum);
if([hostName isEqualToString: @"*"]) continue;
if(portNum == -1) {
NSString* ipValidationRegex = @"^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$";
NSPredicate *regexTester = [NSPredicate
predicateWithFormat:@"SELF MATCHES %@",
ipValidationRegex];
if ([regexTester evaluateWithObject: hostName] != YES) {
// It's not an IP, so we'll add it to the /etc/hosts block as well
[hostFileBlocker addRuleBlockingDomain: hostName];
// If we're supposed to evaluate common subdomains, block www subdomain also
if(shouldEvaluateCommonSubdomains) {
// Block the normal domain if www. was added
if([hostName rangeOfString: @"www."].location == 0)
[hostFileBlocker addRuleBlockingDomain: [hostName substringFromIndex: 4]];
// Or block www.domain otherwise
else
[hostFileBlocker addRuleBlockingDomain: [@"www." stringByAppendingString: hostName]];
}
}
}
}
[hostFileBlocker addSelfControlBlockFooter];
[hostFileBlocker writeNewFileContents];
} else if([hostFileBlocker containsSelfControlBlock]) {
[hostFileBlocker removeSelfControlBlock];
[hostFileBlocker writeNewFileContents];
} else {
NSLog(@"WARNING: Could not create backup file. Giving up on host file blocking.");
}
}
IPFirewall* firewall = [[IPFirewall alloc] init];
[firewall clearSelfControlBlockRuleSet];
[firewall addSelfControlBlockHeader];
if(blockAsWhitelist) {
[NSUserDefaults resetStandardUserDefaults];
seteuid(controllingUID);
defaults = [NSUserDefaults standardUserDefaults];
[defaults addSuiteNamed:@"org.eyebeam.SelfControl"];
BOOL allowLocalNetworks = [defaults boolForKey: @"AllowLocalNetworks"];
[defaults synchronize];
[NSUserDefaults resetStandardUserDefaults];
seteuid(0);
if(allowLocalNetworks) {
[firewall addSelfControlBlockRuleAllowingIP: @"10.0.0.0" maskLength: 8];
[firewall addSelfControlBlockRuleAllowingIP: @"172.16.0.0" maskLength: 12];
[firewall addSelfControlBlockRuleAllowingIP: @"192.168.0.0" maskLength: 16];
}
}
// Iterate through the host list to add a block rule for each
NSEnumerator* hostEnumerator = [hostsToBlock objectEnumerator];
NSString* hostString;
while(hostString = [hostEnumerator nextObject]) {
NSString* hostName;
int portNum;
int maskLen;
parseHost(hostString, &hostName, &maskLen, &portNum);
if(blockAsWhitelist) {
if([hostName isEqualToString: @"*"])
[firewall addSelfControlBlockRuleAllowingPort: portNum];
else if(portNum != -1 && maskLen != -1)
[firewall addSelfControlBlockRuleAllowingIP: hostName port: portNum maskLength: maskLen];
else if(portNum != -1)
[firewall addSelfControlBlockRuleAllowingIP: hostName port: portNum];
else if(maskLen != -1)
[firewall addSelfControlBlockRuleAllowingIP: hostName maskLength: maskLen];
else
[firewall addSelfControlBlockRuleAllowingIP: hostName];
} else {
if([hostName isEqualToString: @"*"])
[firewall addSelfControlBlockRuleBlockingPort: portNum];
else if(portNum != -1 && maskLen != -1)
[firewall addSelfControlBlockRuleBlockingIP: hostName port: portNum maskLength: maskLen];
else if(portNum != -1)
[firewall addSelfControlBlockRuleBlockingIP: hostName port: portNum];
else if(maskLen != -1)
[firewall addSelfControlBlockRuleBlockingIP: hostName maskLength: maskLen];
else
[firewall addSelfControlBlockRuleBlockingIP: hostName];
}
}
if(blockAsWhitelist)
[firewall addWhitelistFooter];
[firewall addSelfControlBlockFooter];
}
void removeRulesFromFirewall(signed long long int controllingUID) {
IPFirewall* firewall = [[IPFirewall alloc] init];
if(![firewall containsSelfControlBlockSet])
NSLog(@"WARNING: SelfControl rules do not appear to be loaded into ipfw.");
HostFileBlocker* hostFileBlocker = [[[HostFileBlocker alloc] init] autorelease];
[hostFileBlocker removeSelfControlBlock];
BOOL hostSuccess = [hostFileBlocker writeNewFileContents];
// Revert the host file blocker's file contents to disk so we can check
// whether or not it still contains the block (aka we messed up).
[hostFileBlocker revertFileContentsToDisk];
// We use ! (NOT) of the method as success because it returns a shell termination status, so 0 is the success code
BOOL ipfwSuccess = ![firewall clearSelfControlBlockRuleSet];
if(hostSuccess && ipfwSuccess && ![hostFileBlocker containsSelfControlBlock] && ![firewall containsSelfControlBlockSet])
NSLog(@"INFO: Hostfile block successfully cleared.");
else {
NSLog(@"WARNING: Error removing hostfile block. Attempting to restore host file backup.");
[firewall clearSelfControlBlockRuleSet];
if([hostFileBlocker restoreBackupHostsFile])
NSLog(@"INFO: Host file backup restored.");
else if([hostFileBlocker containsSelfControlBlock])
NSLog(@"ERROR: Host file backup could not be restored. This may result in a permanent block.");
else if([firewall containsSelfControlBlockSet])
NSLog(@"ERROR: Firewall rules could not be cleared. This may result in a permanent block.");
else
NSLog(@"INFO: Firewall rules successfully cleared.");
}
[hostFileBlocker deleteBackupHostsFile];
// We'll play the sound now rather than putting it in the "defaults block"
// a few lines ago, because it is important that the UI get updated (by
// the posted notification) before we sleep to play the sound. Otherwise,
// the app seems unresponsive and slow.
[NSUserDefaults resetStandardUserDefaults];
seteuid(controllingUID);
defaults = [NSUserDefaults standardUserDefaults];
[defaults addSuiteNamed:@"org.eyebeam.SelfControl"];
if([defaults boolForKey: @"BlockSoundShouldPlay"]) {
// Map the tags used in interface builder to the sound
NSArray* systemSoundNames = [NSArray arrayWithObjects:
@"Basso",
@"Blow",
@"Bottle",
@"Frog",
@"Funk",
@"Glass",
@"Hero",
@"Morse",
@"Ping",
@"Pop",
@"Purr",
@"Sosumi",
@"Submarine",
@"Tink",
nil
];
NSSound* alertSound = [NSSound soundNamed: [systemSoundNames objectAtIndex: [defaults integerForKey: @"BlockSound"]]];
if(!alertSound)
NSLog(@"WARNING: Alert sound not found.");
else {
[alertSound play];
// Sleeping a second is a messy way of doing this, but otherwise the
// sound is killed along with this process when it is unloaded in just
// a few lines.
sleep(1);
}
}
[defaults synchronize];
[NSUserDefaults resetStandardUserDefaults];
seteuid(0);
// } else
// NSLog(@"WARNING: SelfControl rules do not appear to be loaded into ipfw.");
}
NSSet* getEvaluatedHostNamesFromCommonSubdomains(NSString* hostName, int port) {
NSMutableSet* evaluatedAddresses = [NSMutableSet set];
// If the domain ends in facebook.com... Special case for Facebook because
// users will often forget to block some of its many mirror subdomains that resolve
// to different IPs, i.e. hs.facebook.com. Thanks to Danielle for raising this issue.
if([hostName rangeOfString: @"facebook.com"].location == ([hostName length] - 12)) {
[evaluatedAddresses addObject: @"69.63.176.0/20"];
}
// Block the domain with no subdomains, if www.domain is blocked
else if([hostName rangeOfString: @"www."].location == 0) {
NSHost* modifiedHost = [NSHost hostWithName: [hostName substringFromIndex: 4]];
if(modifiedHost) {
NSArray* addresses = [modifiedHost addresses];
for(int j = 0; j < [addresses count]; j++) {
if(port != -1)
[evaluatedAddresses addObject: [NSString stringWithFormat: @"%@:%d", [addresses objectAtIndex: j], port]];
else [evaluatedAddresses addObject: [addresses objectAtIndex: j]];
}
}
}
// Or block www.domain otherwise
else {
NSHost* modifiedHost = [NSHost hostWithName: [@"www." stringByAppendingString: hostName]];
if(modifiedHost) {
NSArray* addresses = [modifiedHost addresses];
for(int j = 0; j < [addresses count]; j++) {
if(port != -1)
[evaluatedAddresses addObject: [NSString stringWithFormat: @"%@:%d", [addresses objectAtIndex: j], port]];
else [evaluatedAddresses addObject: [addresses objectAtIndex: j]];
}
}
}
return evaluatedAddresses;
}
void clearCachesIfRequested(signed long long int controllingUID) {
[NSUserDefaults resetStandardUserDefaults];
seteuid(controllingUID);
defaults = [NSUserDefaults standardUserDefaults];
[defaults addSuiteNamed:@"org.eyebeam.SelfControl"];
if([defaults boolForKey: @"ClearCaches"]) {
NSFileManager* fileManager = [NSFileManager defaultManager];
unsigned int major, minor, bugfix;
[SelfControlUtilities getSystemVersionMajor: &major minor: &minor bugFix: &bugfix];
// We've got to check if we're on 10.5 or not, because earlier systems don't
// have the DARWIN_USER_CACHE_DIR caches that we're about to remove. This is
// also why we have to spawn a task to get the directory path, the specific
// API to get this path is Leopard-only and we need to have a single version
// that works on Tiger and Leopard.
if(major >= 10 && minor >= 5) {
NSTask* task = [[[NSTask alloc] init] autorelease];
[task setLaunchPath: @"/usr/bin/getconf"];
[task setArguments: [NSArray arrayWithObject:
@"DARWIN_USER_CACHE_DIR"
]];
NSPipe* inPipe = [[[NSPipe alloc] init] autorelease];
NSFileHandle* readHandle = [inPipe fileHandleForReading];
[task setStandardOutput: inPipe];
[task launch];
NSString* leopardCacheDirectory = [[[NSString alloc] initWithData:[readHandle readDataToEndOfFile]
encoding: NSUTF8StringEncoding] autorelease];
close([readHandle fileDescriptor]);
[task waitUntilExit];
leopardCacheDirectory = [leopardCacheDirectory stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
if([task terminationStatus] == 0 && [leopardCacheDirectory length] > 0) {
NSMutableArray* leopardCacheDirs = [NSMutableArray arrayWithObjects:
@"org.mozilla.firefox",
@"com.apple.Safari",
@"jp.hmdt.shiira",
@"org.mozilla.camino",
nil];
for(int i = 0; i < [leopardCacheDirs count]; i++) {
NSString* cacheDir = [leopardCacheDirectory stringByAppendingPathComponent: [leopardCacheDirs objectAtIndex: i]];
if([fileManager isDeletableFileAtPath: cacheDir]) {
[fileManager removeFileAtPath: cacheDir handler: nil];
}
}
}
// NSArray* userCacheDirectories = NSSearchPathForDirectoriesInDomain(NSCachesDirectory, NSUserDomainMask, NO);
// I have no clue why this doesn't compile, I'm #importing properly I believe.
// We'll have to do it the messy way...
NSString* userLibraryDirectory = [@"~/Library" stringByExpandingTildeInPath];
NSMutableArray* cacheDirs = [NSMutableArray arrayWithObjects:
@"Caches/Camino",
@"Caches/com.apple.Safari",
@"Caches/Firefox",
@"Caches/Flock",
@"Caches/Opera",
@"Caches/Unison",
@"Caches/com.omnigroup.OmniWeb5",
@"Preferences/iCab Preferences/iCab Cache",
@"Preferences/com.omnigroup.OmniWeb5",
nil];
for(int i = 0; i < [cacheDirs count]; i++) {
NSString* cacheDir = [userLibraryDirectory stringByAppendingPathComponent: [cacheDirs objectAtIndex: i]];
if([fileManager isDeletableFileAtPath: cacheDir]) {
[fileManager removeFileAtPath: cacheDir handler: nil];
}
}
}
}
[NSUserDefaults resetStandardUserDefaults];
seteuid(0);
}
void printStatus(int status) {
printf("%d", status);
}
void parseHost(NSString* hostName, NSString** baseName, int* maskLength, int* portNumber) {
int maskLen = -1;
int portNum = -1;
NSArray* splitString = [hostName componentsSeparatedByString: @"/"];
hostName = [splitString objectAtIndex: 0];
NSString* stringToSearchForPort = hostName;
if([splitString count] >= 2) {
maskLen = [[splitString objectAtIndex: 1] intValue];
// If the int value is 0, we couldn't find a valid integer representation
// in the split off string
if(maskLen == 0)
maskLen = -1;
stringToSearchForPort = [splitString objectAtIndex: 1];
}
splitString = [stringToSearchForPort componentsSeparatedByString: @":"];
if([stringToSearchForPort isEqualToString: hostName])
hostName = [splitString objectAtIndex: 0];
if([splitString count] >= 2) {
portNum = [[splitString objectAtIndex: 1] intValue];
// If the int value is 0, we couldn't find a valid integer representation
// in the split off string
if(portNum == 0)
portNum = -1;
}
if([hostName isEqualToString: @""])
hostName = @"*";
if(baseName) *baseName = hostName;
if(portNumber) *portNumber = portNum;
if(maskLength) *maskLength = maskLen;
}
void removeBlock(signed long long int controllingUID) {
[NSUserDefaults resetStandardUserDefaults];
seteuid(controllingUID);
defaults = [NSUserDefaults standardUserDefaults];
[defaults addSuiteNamed:@"org.eyebeam.SelfControl"];
[defaults setObject: [NSDate distantFuture] forKey: @"BlockStartedDate"];
[defaults synchronize];
[NSUserDefaults resetStandardUserDefaults];
seteuid(0);
removeRulesFromFirewall(controllingUID);
if(![[NSFileManager defaultManager] removeFileAtPath: SelfControlLockFilePath handler: nil] && [[NSFileManager defaultManager] fileExistsAtPath: SelfControlLockFilePath]) {
NSLog(@"ERROR: Could not remove SelfControl lock file.");
printStatus(-218);
}
[[NSDistributedNotificationCenter defaultCenter] postNotificationName: @"SCConfigurationChangedNotification"
object: nil];
clearCachesIfRequested(controllingUID);
NSLog(@"INFO: Block cleared.");
[LaunchctlHelper unloadLaunchdJobWithPlistAt:@"/Library/LaunchDaemons/org.eyebeam.SelfControl.plist"];
}