-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathSSYIOKit.m
314 lines (263 loc) · 13.2 KB
/
SSYIOKit.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
/*
File: GetPrimaryMACAddress.c
Description: This sample application demonstrates how to do retrieve the Ethernet MAC
address of the built-in Ethernet interface from the I/O Registry on macOS.
Techniques shown include finding the primary (built-in) Ethernet interface,
finding the parent Ethernet controller, and retrieving properties from the
controller's I/O Registry entry.
Change History (most recent first):
<3> 09/15/05 Updated to produce a universal binary. Use kIOMasterPortDefault
instead of older IOMasterPort function. Print the MAC address
to stdout in response to <rdar://problem/4021220>.
<2> 04/30/02 Fix bug in creating the matching dictionary that caused the
kIOPrimaryInterface property to be ignored. Clean up comments and add
additional comments about how IOServiceGetMatchingServices operates.
<1> 06/07/01 New sample.
*/
#import "SSYIOKit.h"
#include <IOKit/network/IOEthernetInterface.h>
#include <IOKit/network/IONetworkInterface.h>
#include <IOKit/network/IOEthernetController.h>
#import "NSData+SSYCryptoDigest.h"
static kern_return_t FindEthernetInterfaces(io_iterator_t *matchingServices);
CFDataRef CreateMACAddress(io_iterator_t intfIterator);
// Returns an iterator containing the primary (built-in) Ethernet interface. The caller is responsible for
// releasing the iterator after the caller is done with it.
static kern_return_t FindEthernetInterfaces(io_iterator_t *matchingServices)
{
kern_return_t kernResult;
CFMutableDictionaryRef matchingDict;
CFMutableDictionaryRef propertyMatchDict;
// Ethernet interfaces are instances of class kIOEthernetInterfaceClass.
// IOServiceMatching is a convenience function to create a dictionary with the key kIOProviderClassKey and
// the specified value.
matchingDict = IOServiceMatching(kIOEthernetInterfaceClass);
// Note that another option here would be:
// matchingDict = IOBSDMatching("en0");
if (NULL == matchingDict) {
NSLog(@"FindEthernetInterfaces: IOServiceMatching returned a NULL dictionary.");
}
else {
// Each IONetworkInterface object has a Boolean property with the key kIOPrimaryInterface. Only the
// primary (built-in) interface has this property set to TRUE.
// IOServiceGetMatchingServices uses the default matching criteria defined by IOService. This considers
// only the following properties plus any family-specific matching in this order of precedence
// (see IOService::passiveMatch):
//
// kIOProviderClassKey (IOServiceMatching)
// kIONameMatchKey (IOServiceNameMatching)
// kIOPropertyMatchKey
// kIOPathMatchKey
// kIOMatchedServiceCountKey
// family-specific matching
// kIOBSDNameKey (IOBSDNameMatching)
// kIOLocationMatchKey
// The IONetworkingFamily does not define any family-specific matching. This means that in
// order to have IOServiceGetMatchingServices consider the kIOPrimaryInterface property, we must
// add that property to a separate dictionary and then add that to our matching dictionary
// specifying kIOPropertyMatchKey.
propertyMatchDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (NULL == propertyMatchDict) {
NSLog(@"FindEthernetInterfaces: CFDictionaryCreateMutable returned a NULL dictionary.");
}
else {
// Set the value in the dictionary of the property with the given key, or add the key
// to the dictionary if it doesn't exist. This call retains the value object passed in.
CFDictionarySetValue(propertyMatchDict, CFSTR(kIOPrimaryInterface), kCFBooleanTrue);
// Now add the dictionary containing the matching value for kIOPrimaryInterface to our main
// matching dictionary. This call will retain propertyMatchDict, so we can release our reference
// on propertyMatchDict after adding it to matchingDict.
CFDictionarySetValue(matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatchDict);
CFRelease(propertyMatchDict);
}
}
// IOServiceGetMatchingServices retains the returned iterator, so release the iterator when we're done with it.
// IOServiceGetMatchingServices also consumes a reference on the matching dictionary so we don't need to release
// the dictionary explicitly.
kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, matchingServices);
if (KERN_SUCCESS != kernResult) {
}
return kernResult;
}
// Given an iterator across a set of Ethernet interfaces, return the MAC address of the last one.
// If no interfaces are found the MAC address is set to an empty string.
// In this sample the iterator should contain just the primary interface.
CFDataRef CreateMACAddress(io_iterator_t intfIterator)
{
io_object_t intfService;
io_object_t controllerService;
kern_return_t kernResult = KERN_FAILURE;
UInt8 MACAddress[kIOEthernetAddressSize] ;
// Initialize the returned address
bzero(MACAddress, kIOEthernetAddressSize);
CFTypeRef MACAddressAsCFData = NULL ;
// = NULL was not in the original Apple code. I added it after getting a report from a user
// of a Hackintosh that +hashedMACAddress was crashing. After getting a detailed log from
// him using GetMACAddress, it confirmed that the following loop was not running; hence
// MACAddressAsCFData was garbage, hence the crash later.
// IOIteratorNext retains the returned object, so release it when we're done with it.
while ((intfService = IOIteratorNext(intfIterator))) {
// Memory leak fixed by Jerry Krinock in BookMacster 1.12:
if (MACAddressAsCFData != NULL) {
CFRelease(MACAddressAsCFData) ;
}
// IONetworkControllers can't be found directly by the IOServiceGetMatchingServices call,
// since they are hardware nubs and do not participate in driver matching. In other words,
// registerService() is never called on them. So we've found the IONetworkInterface and will
// get its parent controller by asking for it specifically.
// IORegistryEntryGetParentEntry retains the returned object, so release it when we're done with it.
kernResult = IORegistryEntryGetParentEntry(intfService,
kIOServicePlane,
&controllerService);
if (KERN_SUCCESS != kernResult) {
MACAddressAsCFData = NULL ;
}
else {
// Retrieve the MAC address property from the I/O Registry in the form of a CFData
MACAddressAsCFData = IORegistryEntryCreateCFProperty(controllerService,
CFSTR(kIOMACAddress),
kCFAllocatorDefault,
0);
// Done with the parent Ethernet controller object so we release it.
(void) IOObjectRelease(controllerService);
}
// Done with the Ethernet interface object so we release it.
(void) IOObjectRelease(intfService);
}
return (MACAddressAsCFData) ;
}
@implementation SSYIOKit
+ (NSData*)primaryMACAddressOrMachineSerialNumberData {
kern_return_t kernResult = KERN_SUCCESS; // on PowerPC this is an int (4 bytes)
/*
* error number layout as follows (see mach/error.h and IOKit/IOReturn.h):
*
* hi lo
* | system(6) | subsystem(12) | code(14) |
*/
io_iterator_t intfIterator;
kernResult = FindEthernetInterfaces(&intfIterator);
CFDataRef MACAddressData = NULL ;
if (KERN_SUCCESS != kernResult) {
NSLog(@"+[SSYIOKit primaryMACAddressOrMachineSerialNumberData]: FindEthernetInterfaces returned error, 0x%08lx", (long)kernResult) ;
}
else {
MACAddressData = CreateMACAddress(intfIterator);
}
(void) IOObjectRelease(intfIterator); // Release the iterator.
if (MACAddressData == nil) {
/* May be a Hackintosh. Use email instead. Until 2020-Apr-17 I used
the email address of the primary user:
ABPerson* me = [[ABAddressBook sharedAddressBook] me] ;
ABMultiValue *emails = [me valueForProperty:kABEmailProperty];
NSString* email = [emails valueAtIndex:[emails indexForIdentifier:[emails primaryIdentifier]]];
if ((email != nil) && ([email length] > 7)) {
MACAddressData = (CFDataRef)[[email dataUsingEncoding:NSUTF8StringEncoding] retain] ;
}
But then I started to get depracation warnings on AddressBook … use
Contacts instead. But after 15 minutes of research I cannot find
any equivalent method in Contacts which would get the Contacts record
of the logged-in user. And even if there was such a method, I am sure
it would be protected by a bunch of security hoops and/or roadblocks.
So I decided to do this instead – get the machine serial number. I
remember reading somewhere that this was not a good idea for some
reason – maybe if the motherboard is replaced. But as a fallback
in extreme edge cases, I think it is good enough.
Instead of using two NSTasks as below, the following code could read
in the whole ioRegTask output and filter for the desired line using
Cocoa methods. I tried that, and [task launch] hung. This is
probably too much data got put into the pipe (the output of ioReg -l
is maybe thousnds of lines), and the pipe plugged up and stalled – I
forget what the exact terminology is. There is probably a way to deal
with it, as I recall doing in Chromessenger, but it makes the
following two-task method simpler: */
NSTask* ioRegTask = [[NSTask alloc] init];
NSTask* grepTask = [[NSTask alloc] init];
[ioRegTask setLaunchPath: @"/usr/sbin/ioreg"];
[grepTask setLaunchPath: @"/usr/bin/grep"];
[ioRegTask setArguments: [NSArray arrayWithObjects: @"-l", nil]];
[grepTask setArguments: [NSArray arrayWithObjects: @"IOPlatformSerialNumber", nil]];
/* Connect the pipes */
NSPipe *pipeBetween = [NSPipe pipe];
[ioRegTask setStandardOutput: pipeBetween];
[grepTask setStandardInput: pipeBetween];
NSPipe *pipeToMe = [NSPipe pipe];
[grepTask setStandardOutput: pipeToMe];
NSFileHandle *grepOutput = [pipeToMe fileHandleForReading];
[ioRegTask launch];
[grepTask launch];
[grepTask waitUntilExit];
NSData *data = [grepOutput readDataToEndOfFile];
[ioRegTask release];
[grepTask release];
NSString* targetLine = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
if (targetLine.length > 0) {
NSScanner* scanner = [[NSScanner alloc] initWithString:targetLine];
NSString* serialString = nil;
[scanner scanUpToString:@"=" intoString:NULL];
[scanner scanUpToString:@"\"" intoString:NULL];
scanner.scanLocation = scanner.scanLocation + 1;
[scanner scanUpToString:@"\"" intoString:&serialString];
[scanner release];
NSData* data = [serialString dataUsingEncoding:NSUTF8StringEncoding];
[data retain];
MACAddressData = (CFDataRef)data;
}
[targetLine release];
}
return [(NSData*)MACAddressData autorelease] ;
}
+ (NSData*)hashedMACAddress {
NSData* macAddress = [SSYIOKit primaryMACAddressOrMachineSerialNumberData] ;
NSData* hashedMACAddress = [macAddress sha1Digest] ;
return hashedMACAddress ;
}
+ (NSData*)hashedMACAddressAndShortUserName {
NSData* macAddress = [SSYIOKit primaryMACAddressOrMachineSerialNumberData];
NSData* userNameData = [NSUserName() dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData* data = [macAddress mutableCopy];
[data appendData:userNameData];
NSData* hash = [data sha256Digest];
#if !__has_feature(objc_arc)
[data release];
#endif
return hash;
}
/*
+ (NSData*)machineSerialNumberData {
NSString *result = @"";
mach_port_t masterPort;
kern_return_t kr = noErr;
io_registry_entry_t entry;
CFDataRef propData;
CFTypeRef prop;
CFTypeID propID;
UInt8 *data;
unsigned int i, bufSize;
char *s, *t;
char firstPart[64], secondPart[64];
kr = IOMasterPort(MACH_PORT_NULL, &masterPort);
if (kr == noErr) {
entry = IORegistryGetRootEntry(masterPort);
if (entry != MACH_PORT_NULL) {
prop = IORegistryEntrySearchCFProperty(entry, kIODeviceTreePlane,
CFSTR("serial-number"), NULL, kIORegistryIterateRecursively);
propID = CFGetTypeID(prop);
if (propID == CFDataGetTypeID()) {
propData = (CFDataRef)prop;
}
else {
propData = NULL ;
}
}
mach_port_deallocate(mach_task_self(), masterPort);
}
// Documentation for IORegistryEntrySearchCFProperty() says that caller should
// release the returned result. So, I autorelease prop before returning.
return [(NSData*)propData autorelease] ;
}
*/
@end