1/* Copyright (c) 2012 Apple Inc. All rights reserved. */
2
3#include "agent.h"
4#include "authitems.h"
5#include "authd_private.h"
6#include "process.h"
7#include "mechanism.h"
8#include "authutilities.h"
9#include "authtoken.h"
10#include "session.h"
11#include "debugging.h"
12#include "engine.h"
13
14#import <xpc/private.h>
15#include <Security/AuthorizationPlugin.h>
16
17#include <mach/mach.h>
18#include <servers/bootstrap.h>
19#include <bootstrap_priv.h>
20
21#define SECURITYAGENT_BOOTSTRAP_NAME_BASE       "com.apple.security.agentMain"
22#define SECURITYAGENT_STUB_BOOTSTRAP_NAME_BASE       "com.apple.security.agentStub"
23//#define SECURITYAGENT_LOGINWINDOW_BOOTSTRAP_NAME_BASE       "com.apple.security.agent.login"
24#define AUTHORIZATIONHOST_BOOTSTRAP_NAME_BASE   "com.apple.security.authhost"
25
26#define UUID_INITIALIZER_FROM_SESSIONID(sessionid) \
27{ 0,0,0,0, 0,0,0,0, 0,0,0,0, (unsigned char)((0xff000000 & (sessionid))>>24), (unsigned char)((0x00ff0000 & (sessionid))>>16), (unsigned char)((0x0000ff00 & (sessionid))>>8),  (unsigned char)((0x000000ff & (sessionid))) }
28
29struct _agent_s {
30    __AUTH_BASE_STRUCT_HEADER__;
31
32    auth_items_t hints;
33    auth_items_t context;
34    mechanism_t mech;
35    engine_t engine;
36    PluginState pluginState;
37    uint64_t status;
38
39    xpc_connection_t agentConnection;
40    xpc_connection_t agentStubConnection;
41    dispatch_queue_t eventQueue;
42    dispatch_queue_t actionQueue;
43
44    pid_t agentPid;
45};
46
47static void
48_agent_finalize(CFTypeRef value)
49{
50    agent_t agent = (agent_t)value;
51
52    // If this ever becomes a concurrent queue, then this would need to be a barrier sync
53    dispatch_sync(agent->eventQueue, ^{
54        // Mark the agent as dead
55        agent->pluginState = dead;
56    });
57
58    // We're going away, which means no outside references exist. It's safe to dispose of the XPC connection.
59    if (NULL != agent->agentConnection) {
60        xpc_release(agent->agentConnection);
61        agent->agentConnection = NULL;
62    }
63
64    if (NULL != agent->agentStubConnection) {
65        xpc_release(agent->agentStubConnection);
66        agent->agentStubConnection = NULL;
67    }
68
69    // Now that we've released any XPC connection that may (or may not) be present
70    // it's safe to go ahead and free our memory. This is provided that all other
71    // blocks that were added to the event queue before the axe came down on the
72    // xpc connection have been executed.
73
74    // If this ever becomes a concurrent queue, then this would need to be a barrier sync
75    dispatch_sync(agent->eventQueue, ^{
76        CFReleaseSafe(agent->hints);
77        CFReleaseSafe(agent->context);
78        CFReleaseSafe(agent->mech);
79        CFReleaseSafe(agent->engine);
80        dispatch_release(agent->actionQueue);
81    });
82
83    dispatch_release(agent->eventQueue);
84
85    LOGD("agent[%i]: _agent_finalize called", agent->agentPid);
86}
87
88AUTH_TYPE_INSTANCE(agent,
89                   .init = NULL,
90                   .copy = NULL,
91                   .finalize = _agent_finalize,
92                   .equal = NULL,
93                   .hash = NULL,
94                   .copyFormattingDesc = NULL,
95                   .copyDebugDesc = NULL
96                   );
97
98static CFTypeID agent_get_type_id() {
99    static CFTypeID type_id = _kCFRuntimeNotATypeID;
100    static dispatch_once_t onceToken;
101
102    dispatch_once(&onceToken, ^{
103        type_id = _CFRuntimeRegisterClass(&_auth_type_agent);
104    });
105
106    return type_id;
107}
108
109agent_t
110agent_create(engine_t engine, mechanism_t mech, auth_token_t auth, process_t proc, bool firstMech)
111{
112    bool doSwitchAudit = false;
113    bool doSwitchBootstrap = false;
114    agent_t agent = NULL;
115
116    agent = (agent_t)_CFRuntimeCreateInstance(kCFAllocatorDefault, agent_get_type_id(), AUTH_CLASS_SIZE(agent), NULL);
117    require(agent != NULL, done);
118
119    agent->mech = (mechanism_t)CFRetain(mech);
120    agent->engine = (engine_t)CFRetain(engine);
121    agent->hints = auth_items_create();
122    agent->context = auth_items_create();
123    agent->pluginState = init;
124    agent->agentPid = process_get_pid(proc);
125    agent->agentStubConnection = NULL;
126
127    const audit_info_s *audit_info = auth_token_get_audit_info(auth);
128
129    auditinfo_addr_t tempAddr;
130    tempAddr.ai_asid = audit_info->asid;
131    auditon(A_GETSINFO_ADDR, &tempAddr, sizeof(tempAddr));
132
133    LOGV("agent[%i]: Stored auid %d fetched auid %d", agent->agentPid, audit_info->auid, tempAddr.ai_auid);
134    uid_t auid = tempAddr.ai_auid;
135    uuid_t sessionUUID = UUID_INITIALIZER_FROM_SESSIONID((uint32_t)audit_info->asid);
136
137    agent->eventQueue = dispatch_queue_create("Agent Event Queue", 0);
138    agent->actionQueue = dispatch_queue_create("Agent Action Queue", 0);
139
140    mach_port_t bootstrapPort = process_get_bootstrap(proc);
141    if (!mechanism_is_privileged(mech)) {
142        if ((int32_t)auid != -1) {
143            agent->agentStubConnection = xpc_connection_create_mach_service(SECURITYAGENT_STUB_BOOTSTRAP_NAME_BASE, NULL, 0);
144            xpc_connection_set_target_uid(agent->agentStubConnection, auid);
145            LOGV("agent[%i]: Creating a security agent stub", agent->agentPid);
146            xpc_connection_set_event_handler(agent->agentStubConnection, ^(xpc_object_t object){}); // Yes, this is a dummy handler, we never ever care about any responses from the stub. It can die in a fire for all I care.
147            xpc_connection_resume(agent->agentStubConnection);
148
149            xpc_object_t wakeupMessage = xpc_dictionary_create(NULL, NULL, 0);
150            xpc_dictionary_set_data(wakeupMessage, AUTH_XPC_SESSION_UUID, sessionUUID, sizeof(uuid_t));
151            xpc_object_t responseMessage = xpc_connection_send_message_with_reply_sync(agent->agentStubConnection, wakeupMessage);
152            if (xpc_get_type(responseMessage) == XPC_TYPE_DICTIONARY) {
153                LOGV("agent[%i]: Valid response received from stub", agent->agentPid);
154            } else {
155                LOGV("agent[%i]: Error response received from stub", agent->agentPid);
156            }
157            xpc_release(wakeupMessage);
158            xpc_release(responseMessage);
159
160            mach_port_t newBootstrapPort = auth_token_get_creator_bootstrap(auth);
161            if (newBootstrapPort != MACH_PORT_NULL) {
162                bootstrapPort = newBootstrapPort;
163            }
164        }
165
166        agent->agentConnection = xpc_connection_create_mach_service(SECURITYAGENT_BOOTSTRAP_NAME_BASE, NULL,0);
167        xpc_connection_set_instance(agent->agentConnection, sessionUUID);
168        LOGV("agent[%i]: Creating a security agent", agent->agentPid);
169        doSwitchAudit = true;
170        doSwitchBootstrap = true;
171    } else {
172        agent->agentConnection = xpc_connection_create_mach_service(AUTHORIZATIONHOST_BOOTSTRAP_NAME_BASE, NULL, 0);
173        xpc_connection_set_instance(agent->agentConnection, sessionUUID);
174        LOGV("agent[%i]: Creating a standard authhost", agent->agentPid);
175        doSwitchAudit = true;
176        doSwitchBootstrap = true;
177    }
178
179    // **************** Here's the event handler, since I can never find it
180    xpc_connection_set_target_queue(agent->agentConnection, agent->eventQueue);
181    xpc_connection_set_event_handler(agent->agentConnection, ^(xpc_object_t object) {
182        char* objectDesc = xpc_copy_description(object);
183        LOGV("agent[%i]: global xpc message received %s", agent->agentPid, objectDesc);
184        free(objectDesc);
185
186        if (agent->pluginState == dead) {
187            // If the agent is dead for some reason, drop this message before we hurt ourselves.
188            return;
189        }
190
191        if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) {
192            const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY);
193            if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_INTERRUPT)) {
194                agent->pluginState = interrupting;
195                engine_interrupt_agent(agent->engine);
196            }
197        }
198    });
199
200    xpc_connection_resume(agent->agentConnection);
201
202    xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
203    xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_CREATE);
204    xpc_dictionary_set_string(requestObject, AUTH_XPC_PLUGIN_NAME, mechanism_get_plugin(mech));
205    xpc_dictionary_set_string(requestObject, AUTH_XPC_MECHANISM_NAME, mechanism_get_param(mech));
206	if (doSwitchAudit) {
207        mach_port_name_t jobPort;
208        if (audit_info != NULL) {
209            if (0 == audit_session_port(audit_info->asid, &jobPort)) {
210                LOGV("agent[%i]: attaching an audit session port", agent->agentPid);
211                xpc_dictionary_set_mach_send(requestObject, AUTH_XPC_AUDIT_SESSION_PORT, jobPort);
212            }
213        }
214    }
215
216	if (doSwitchBootstrap) {
217        LOGV("agent[%i]: attaching a bootstrap port", agent->agentPid);
218        xpc_dictionary_set_mach_send(requestObject, AUTH_XPC_BOOTSTRAP_PORT, bootstrapPort);
219    }
220
221    // This loop will be repeated until we can get ahold of a SecurityAgent, or get a fatal error
222    // This will cause us to retry any CONNECTION_INTERRUPTED status messages
223    do {
224        xpc_object_t object = xpc_connection_send_message_with_reply_sync(agent->agentConnection, requestObject);
225
226        if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) {
227            const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY);
228            if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_CREATE)) {
229                agent->status = xpc_dictionary_get_uint64(object, AUTH_XPC_REPLY_RESULT_VALUE);
230                if (agent->status == kAuthorizationResultAllow) {
231                    // Only go here if we have an "allow" from the SecurityAgent (the plugin create might have failed)
232                    // This is backwards compatible so that it goes gently into the build, as the default is "allow".
233                    agent->pluginState = created;
234                } else {
235                    agent->pluginState = dead;
236                }
237            }
238        } else if (xpc_get_type(object) == XPC_TYPE_ERROR) {
239            if (object == XPC_ERROR_CONNECTION_INVALID) {
240                agent->pluginState = dead;
241            }
242        }
243        xpc_release(object);
244    } while ((agent->pluginState == init) && (firstMech));
245
246    xpc_release(requestObject);
247
248    if (agent->pluginState != created) {
249        CFRelease(agent);
250        agent = NULL;
251    }
252done:
253    return agent;
254}
255
256uint64_t
257agent_run(agent_t agent, auth_items_t hints, auth_items_t context, auth_items_t immutable_hints)
258{
259    xpc_object_t hintsArray = auth_items_export_xpc(hints);
260    xpc_object_t contextArray = auth_items_export_xpc(context);
261    xpc_object_t immutableHintsArray = auth_items_export_xpc(immutable_hints);
262
263    dispatch_semaphore_t replyWaiter = dispatch_semaphore_create(0);
264    dispatch_sync(agent->actionQueue, ^{
265        if (agent->pluginState != mechinterrupting) {
266            agent->pluginState = current;
267            agent->status = kAuthorizationResultUndefined; // Set this while the plugin chews on the user input.
268
269            auth_items_clear(agent->hints);
270            auth_items_clear(agent->context);
271
272            xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
273            xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_INVOKE);
274            xpc_dictionary_set_value(requestObject, AUTH_XPC_HINTS_NAME, hintsArray);
275            xpc_dictionary_set_value(requestObject, AUTH_XPC_CONTEXT_NAME, contextArray);
276            xpc_dictionary_set_value(requestObject, AUTH_XPC_IMMUTABLE_HINTS_NAME, immutableHintsArray);
277
278            xpc_connection_send_message_with_reply(agent->agentConnection, requestObject, agent->actionQueue, ^(xpc_object_t object){
279                if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) {
280                    const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY);
281                    if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_RESULT)) {
282                        if (agent->pluginState == current) {
283                            xpc_object_t xpcContext = xpc_dictionary_get_value(object, AUTH_XPC_CONTEXT_NAME);
284                            xpc_object_t xpcHints = xpc_dictionary_get_value(object, AUTH_XPC_HINTS_NAME);
285                            auth_items_copy_xpc(agent->context, xpcContext);
286                            auth_items_copy_xpc(agent->hints, xpcHints);
287                            agent->status = xpc_dictionary_get_uint64(object, AUTH_XPC_REPLY_RESULT_VALUE);
288                            agent->pluginState = active;
289                        }
290                    }
291                } else if (xpc_get_type(object) == XPC_TYPE_ERROR) {
292                    if ((object == XPC_ERROR_CONNECTION_INVALID) || (object == XPC_ERROR_CONNECTION_INTERRUPTED)) {
293                        agent->pluginState = dead;
294                    }
295                }
296                dispatch_semaphore_signal(replyWaiter);
297            });
298            xpc_release(requestObject);
299        }
300    });
301
302    if (agent->pluginState == current) {
303        dispatch_semaphore_wait(replyWaiter, DISPATCH_TIME_FOREVER);
304    }
305    dispatch_release(replyWaiter);
306
307    LOGV("agent[%i]: Finished call to SecurityAgent", agent->agentPid);
308
309    xpc_release(hintsArray);
310    xpc_release(contextArray);
311    xpc_release(immutableHintsArray);
312
313    return agent->status;
314}
315
316auth_items_t
317agent_get_hints(agent_t agent)
318{
319    return agent->hints;
320}
321
322auth_items_t
323agent_get_context(agent_t agent)
324{
325    return agent->context;
326}
327
328mechanism_t
329agent_get_mechanism(agent_t agent)
330{
331    return agent->mech;
332}
333
334void
335agent_deactivate(agent_t agent)
336{
337    xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
338    xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_DEACTIVATE);
339
340    agent->pluginState = deactivating;
341
342    xpc_object_t object = xpc_connection_send_message_with_reply_sync(agent->agentConnection, requestObject);
343    if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) {
344        const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY);
345        if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_DEACTIVATE)) {
346            agent->pluginState = active; // This is strange, but true. We do actually want to set the state 'active'.
347        }
348    } else if (xpc_get_type(object) == XPC_TYPE_ERROR) {
349        if ((object == XPC_ERROR_CONNECTION_INVALID) || (object == XPC_ERROR_CONNECTION_INTERRUPTED)) {
350            agent->pluginState = dead;
351        }
352    }
353    xpc_release(object);
354    xpc_release(requestObject);
355}
356
357void agent_destroy(agent_t agent)
358{
359    xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
360    xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_DESTROY);
361    xpc_connection_send_message(agent->agentConnection, requestObject);
362    xpc_release(requestObject);
363}
364
365PluginState
366agent_get_state(agent_t agent)
367{
368    return agent->pluginState;
369}
370
371void
372agent_notify_interrupt(agent_t agent)
373{
374    dispatch_sync(agent->actionQueue, ^{
375        if (agent->pluginState == current) {
376            xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
377            xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_INTERRUPT);
378            xpc_connection_send_message(agent->agentConnection, requestObject);
379            xpc_release(requestObject);
380        } else if (agent->pluginState == active) {
381            agent->pluginState = mechinterrupting;
382        }
383    });
384}
385
386void
387agent_clear_interrupt(agent_t agent)
388{
389    dispatch_sync(agent->actionQueue, ^{
390        if (agent->pluginState == mechinterrupting) {
391            agent->pluginState = active;
392        }
393    });
394}
395
396void
397agent_recieve(agent_t agent)
398{
399}
400