1/*
2 * Copyright (c) 2014 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#include "authorize.h"
25
26#include "application.h"
27#include "device.h"
28#include "preferences.h"
29
30#include <CoreFoundation/CFBundlePriv.h>
31#include <IOKit/IOKitLibPrivate.h>
32
33static IOReturn __Authorize( CFDictionaryRef deviceID, pid_t processID )
34{
35    CFStringRef device;
36    IOReturn status;
37
38    device = _DeviceCopyName( deviceID );
39
40    if ( device )
41    {
42        CFBundleRef bundle;
43
44        bundle = _ApplicationCopyBundle( processID );
45
46        if ( bundle )
47        {
48            CFStringRef application;
49
50            application = CFBundleGetValueForInfoDictionaryKey( bundle, _kCFBundleDisplayNameKey );
51
52            if ( application == 0 )
53            {
54                application = CFBundleGetValueForInfoDictionaryKey( bundle, kCFBundleNameKey );
55            }
56
57            if ( application )
58            {
59                CFMutableDictionaryRef dictionary;
60
61                dictionary = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
62
63                if ( dictionary )
64                {
65                    CFStringRef string;
66                    CFStringRef header;
67
68                    string = CFCopyLocalizedString( CFSTR( "%@ wants to access \"%@\"." ), 0 );
69                    header = CFStringCreateWithFormat( kCFAllocatorDefault, 0, string, application, device );
70                    CFRelease( string );
71
72                    if ( header )
73                    {
74                        CFURLRef path;
75
76                        path = CFBundleCopyBundleURL( CFBundleGetMainBundle( ) );
77
78                        if ( path )
79                        {
80                            CFUserNotificationRef notification;
81
82                            CFDictionarySetValue( dictionary, kCFUserNotificationAlertHeaderKey, header );
83                            CFDictionarySetValue( dictionary, kCFUserNotificationAlertMessageKey, CFSTR( "Do you want to allow access to this device?" ) );
84                            CFDictionarySetValue( dictionary, kCFUserNotificationAlternateButtonTitleKey, CFSTR( "Always Allow" ) );
85                            CFDictionarySetValue( dictionary, kCFUserNotificationDefaultButtonTitleKey, CFSTR( "Allow" ) );
86                            CFDictionarySetValue( dictionary, kCFUserNotificationLocalizationURLKey, path );
87                            CFDictionarySetValue( dictionary, kCFUserNotificationOtherButtonTitleKey, CFSTR( "Deny" ) );
88
89                            notification = CFUserNotificationCreate( kCFAllocatorDefault, 0, kCFUserNotificationCautionAlertLevel, 0, dictionary );
90
91                            if ( notification )
92                            {
93                                int err;
94                                CFOptionFlags response;
95
96                                err = CFUserNotificationReceiveResponse( notification, 0, &response );
97
98                                if ( err == 0 )
99                                {
100                                    switch ( ( response & 0x3 ) )
101                                    {
102                                        case kCFUserNotificationAlternateResponse:
103                                        {
104                                            status = kIOReturnNotFound;
105
106                                            break;
107                                        }
108
109                                        case kCFUserNotificationDefaultResponse:
110                                        {
111                                            status = kIOReturnSuccess;
112
113                                            break;
114                                        }
115
116                                        default:
117                                        {
118                                            status = kIOReturnNotPermitted;
119
120                                            break;
121                                        }
122                                    }
123                                }
124                                else
125                                {
126                                    status = kIOReturnNoResources;
127                                }
128
129                                CFRelease( notification );
130                            }
131                            else
132                            {
133                                status = kIOReturnNoResources;
134                            }
135
136                            CFRelease( path );
137                        }
138                        else
139                        {
140                            status = kIOReturnNoMemory;
141                        }
142
143                        CFRelease( header );
144                    }
145                    else
146                    {
147                        status = kIOReturnNoMemory;
148                    }
149
150                    CFRelease( dictionary );
151                }
152                else
153                {
154                    status = kIOReturnNoMemory;
155                }
156            }
157            else
158            {
159                status = kIOReturnAborted;
160            }
161
162            CFRelease( bundle );
163        }
164        else
165        {
166            status = kIOReturnAborted;
167        }
168
169        CFRelease( device );
170    }
171    else
172    {
173        status = kIOReturnUnsupported;
174    }
175
176    return status;
177}
178
179IOReturn _Authorize( io_service_t service, uint64_t options, pid_t processID, uint64_t authorizationID )
180{
181    CFDictionaryRef deviceID;
182    IOReturn status;
183
184    deviceID = _DeviceCopyIdentifier( service );
185
186    if ( deviceID )
187    {
188        CFStringRef applicationID;
189
190        applicationID = _ApplicationCopyIdentifier( processID );
191
192        if ( applicationID )
193        {
194            CFArrayRef deviceIDs;
195
196            status = kIOReturnNotFound;
197
198            deviceIDs = _PreferencesCopyValue( applicationID );
199
200            if ( deviceIDs )
201            {
202                CFIndex count;
203                CFIndex index;
204
205                count = CFArrayGetCount( deviceIDs );
206
207                for ( index = 0; index < count; index++ )
208                {
209                    CFDictionaryRef compare;
210
211                    compare = ( void * ) CFArrayGetValueAtIndex( deviceIDs, index );
212
213                    if ( _DeviceIsEqual( deviceID, compare ) )
214                    {
215                        status = kIOReturnSuccess;
216
217                        break;
218                    }
219                }
220            }
221
222            if ( status )
223            {
224                if ( ( options & kIOServiceInteractionAllowed ) )
225                {
226                    status = __Authorize( deviceID, processID );
227
228                    if ( status == kIOReturnNotFound )
229                    {
230                        _PreferencesAppendArrayValue( applicationID, deviceID );
231
232                        status = kIOReturnSuccess;
233                    }
234                }
235                else
236                {
237                    status = kIOReturnNotPermitted;
238                }
239            }
240
241            if ( status == kIOReturnSuccess )
242            {
243                status = _IOServiceSetAuthorizationID( service, authorizationID );
244            }
245
246            if ( deviceIDs )
247            {
248                CFRelease( deviceIDs );
249            }
250
251            CFRelease( applicationID );
252        }
253        else
254        {
255            status = kIOReturnAborted;
256        }
257
258        CFRelease( deviceID );
259    }
260    else
261    {
262        status = kIOReturnUnsupported;
263    }
264
265    return status;
266}
267