-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy pathLaunchItems.m
executable file
·386 lines (317 loc) · 10.4 KB
/
LaunchItems.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
//
// LaunchItems.m
// KnockKnock
//
#import "File.h"
#import "Utilities.h"
#import "AppDelegate.h"
#import "LaunchItems.h"
//plugin name
#define PLUGIN_NAME @"Launch Items"
//plugin description
#define PLUGIN_DESCRIPTION NSLocalizedString(@"daemons and agents loaded by launchd", @"daemons and agents loaded by launchd")
//plugin icon
#define PLUGIN_ICON @"launchIcon"
//(base) directory that has overrides for launch* and apps
#define OVERRIDES_DIRECTORY @"/private/var/db/launchd.db/"
@implementation LaunchItems
@synthesize enabledItems;
@synthesize disabledItems;
//init
// ->set name, description, etc
-(id)init
{
//super
self = [super init];
if(self)
{
//set name
self.name = PLUGIN_NAME;
//set description
self.description = PLUGIN_DESCRIPTION;
//set icon
self.icon = PLUGIN_ICON;
}
return self;
}
//scan for login items
// note: keys in plist are all lower case'd for case-insensitive search
-(void)scan
{
//all launch items
NSArray* launchItems = nil;
//plist data
NSDictionary* plist = nil;
//processed plist
NSMutableDictionary* plistProcessed = nil;
//launch item binary
NSString* launchItemPath = nil;
//detected (auto-started) login item
File* fileObj = nil;
//get overriden enabled & disabled items
[self processOverrides];
//wait for shared item enumerator to complete enumeration of launch items
do
{
//nap
[NSThread sleepForTimeInterval:0.1f];
//try grab launch items
// ->will only !nil, when enumeration is complete
launchItems = sharedItemEnumerator.launchItems;
//keep trying until we get em!
} while(nil == launchItems);
//iterate over all launch items
// ->scan/process each
for(NSString* launchItemPlist in launchItems)
{
//reset
launchItemPath = nil;
//load plist contents
// ->skip any that error out
plist = [NSDictionary dictionaryWithContentsOfFile:launchItemPlist];
if(nil == plist)
{
//skip
continue;
}
//alloc
plistProcessed = [NSMutableDictionary dictionary];
//convert keys to lower case
for(NSString* key in plist)
{
//add lower-case'd
plistProcessed[key.lowercaseString] = plist[key];
}
//skip non-auto run items
if(YES != [self isAutoRun:plistProcessed])
{
//skip
continue;
}
//extract path to launch item
// first, check 'Program' key
if(nil != plistProcessed[@"program"])
{
//is it array?
if(YES == [plistProcessed[@"program"] isKindOfClass:[NSArray class]])
{
//extract path
launchItemPath = [plistProcessed[@"program"] firstObject];
}
//is it a string?
else if(YES == [plistProcessed[@"program"] isKindOfClass:[NSString class]])
{
//extract path
launchItemPath = plistProcessed[@"program"];
}
}
//extact path to launch item
// ->second, via 'ProgramArguments' (sometimes just has args)
else if(nil != plistProcessed[@"programarguments"])
{
//should (usually) be an array
// ->extract & grab first item
if(YES == [plistProcessed[@"programarguments"] isKindOfClass:[NSArray class]])
{
//extract path
launchItemPath = [plistProcessed[@"programarguments"] firstObject];
}
//sometime this is a string...
// ->just save as path (assumes no args)
else if(YES == [plistProcessed[@"programarguments"] isKindOfClass:[NSString class]])
{
//extract path
launchItemPath = plistProcessed[@"programarguments"];
}
}
//skip any that don't have a path
if(nil == launchItemPath)
{
//skip
continue;
}
//create File object for launch item
// ->skip those that err out for any reason
if(nil == (fileObj = [[File alloc] initWithParams:@{KEY_RESULT_PLUGIN:self, KEY_RESULT_PATH:launchItemPath, KEY_RESULT_PLIST:launchItemPlist}]))
{
//skip
continue;
}
//don't trust 'Apple' binaries that are persisted as launch items
if( (YES == fileObj.isTrusted) &&
((YES == [fileObj.plist hasPrefix:@"/Library/"]) || (YES == [fileObj.plist hasPrefix:@"/Users/"])) )
{
//don't trust
fileObj.isTrusted = NO;
}
//process item
// ->save and report to UI
[super processItem:fileObj];
}
return;
}
//get all overridden enabled/disabled launch items
// ->specified in various overrides.plist files
-(void)processOverrides
{
//override directories
NSArray* overrideDirectories = nil;
//override path
NSString* overridePath = nil;
//overrides user id
uid_t overridesUserID = 0;
//override contents
NSDictionary* overrideContents = nil;
//alloc enabled items array
enabledItems = [NSMutableArray array];
//alloc disabled items array
disabledItems = [NSMutableArray array];
//get all override directories
overrideDirectories = directoryContents(OVERRIDES_DIRECTORY, @"self BEGINSWITH 'com.apple.launchd'");
//iterate over all directories
// ->open/parse 'overrides.plist'
for(NSString* overrideDirectory in overrideDirectories)
{
//init full path
overridePath = [NSString stringWithFormat:@"%@%@%@", OVERRIDES_DIRECTORY, overrideDirectory, @"/overrides.plist"];
//skip files that don't exist/aren't accessible
// ->but first try to resolve via 'which()' to get long path
if(YES != [[NSFileManager defaultManager] fileExistsAtPath:overridePath])
{
//try resolve
overridePath = which(overridePath);
if( (nil == overridePath) ||
(YES != [[NSFileManager defaultManager] fileExistsAtPath:overridePath]))
{
//skip
continue;
}
}
//extract overrides UID from its directory name
// ->e.g. 'com.apple.launchd.peruser.501' -> 501
overridesUserID = [[overrideDirectory pathExtension] intValue];
//for override UID's over 500
// ->ignore unless it matches current users
if( (overridesUserID > 500) &&
(overridesUserID != getuid()) )
{
//skip
continue;
}
//load override plist
overrideContents = [NSDictionary dictionaryWithContentsOfFile:overridePath];
//iterate over all items in override plist file
// ->save any that are disabled
for(NSString* overrideItem in overrideContents)
{
//skip items that don't have 'Disabled' key
if(nil == overrideContents[overrideItem][@"Disabled"])
{
//skip
continue;
}
//add enabled item
if(YES == [overrideContents[overrideItem][@"Disabled"] boolValue])
{
//add
[self.enabledItems addObject:overrideItem];
}
//add disabled item
else
{
//add
[self.disabledItems addObject:overrideItem];
}
}
}
return;
}
//checks if an item will be automatically run by the OS
// note: all keys are lower-case, as we've converted them this way...
-(BOOL)isAutoRun:(NSDictionary*)plist
{
//flag
BOOL isAutoRun = NO;
//flag for 'RunAtLoad'
// ->default to -1 for not found
NSInteger runAtLoad = -1;
//flag for 'KeepAlive'
// ->default to -1 for not found
NSInteger keepAlive = -1;
//flag for 'OnDemand'
// ->default to -1 for not found
NSInteger onDemand = -1;
//flag for start interval
BOOL startInterval = NO;
//skip launch items overriden with 'Disable'
if(YES == [self.disabledItems containsObject:plist[@"label"]])
{
//bail
goto bail;
}
//skip directly disabled items
// ->unless its overridden w/ enabled
if( (YES == [plist[@"disabled"] isKindOfClass:[NSNumber class]]) &&
(YES == [plist[@"disabled"] boolValue]) )
{
//also make sure it's not enabled via an override
if(YES != [self.disabledItems containsObject:plist[@"label"]])
{
//bail
goto bail;
}
}
//set 'RunAtLoad' flag
if(YES == [plist[@"runatload"] isKindOfClass:[NSNumber class]])
{
//set
runAtLoad = [plist[@"runatload"] boolValue];
}
//set 'KeepAlive' flag
if(YES == [plist[@"keepalive"] isKindOfClass:[NSNumber class]])
{
//set
keepAlive = [plist[@"keepalive"] boolValue];
}
//set 'OnDemand' flag
if(YES == [plist[@"ondemand"] isKindOfClass:[NSNumber class]])
{
//set
onDemand = [plist[@"ondemand"] boolValue];
}
//set 'StartInterval' flag
// ->check both and 'StartInterval' and 'StartCalendarInterval'
if( (nil != plist[@"startinterval"]) ||
(nil != plist[@"startcalendarinterval"]) )
{
//set
startInterval = YES;
}
//CHECK 0x1: 'RunAtLoad' / 'KeepAlive'
// ->either of these set to ok, means auto run!
if( (YES == runAtLoad) ||
(YES == keepAlive) )
{
//auto
isAutoRun = YES;
}
//CHECK 0x2: 'StartInterval' / 'StartCalendarInterval'
// ->either set, means will auto run (at some point)
else if(YES == startInterval)
{
//auto
isAutoRun = YES;
}
//when neither 'RunAtLoad' and 'KeepAlive' not found
// ->check if 'OnDemand' is set to false (e.g. HackingTeam)
else if( ((-1 == runAtLoad) && (-1 == keepAlive)) &&
(NO == onDemand) )
{
//auto
isAutoRun = YES;
}
//bail
bail:
return isAutoRun;
}
@end