1/*
2 * Copyright (c) 2000-2013 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//
25// passphrases - canonical code to obtain passphrases
26//
27#include "agentquery.h"
28#include "authority.h"
29#include "ccaudit_extensions.h"
30
31#include <Security/AuthorizationTags.h>
32#include <Security/AuthorizationTagsPriv.h>
33#include <Security/checkpw.h>
34#include <Security/Security.h>
35#include <System/sys/fileport.h>
36#include <bsm/audit.h>
37#include <bsm/audit_uevents.h>      // AUE_ssauthint
38#include <security_utilities/logging.h>
39#include <security_utilities/mach++.h>
40#include <stdlib.h>
41#include <xpc/xpc.h>
42#include <xpc/private.h>
43#include "securityd_service/securityd_service/securityd_service_client.h"
44
45#define SECURITYAGENT_BOOTSTRAP_NAME_BASE       "com.apple.security.agentMain"
46#define SECURITYAGENT_STUB_BOOTSTRAP_NAME_BASE       "com.apple.security.agentStub"
47#define AUTHORIZATIONHOST_BOOTSTRAP_NAME_BASE   "com.apple.security.authhost"
48
49#define AUTH_XPC_ITEM_NAME  "_item_name"
50#define AUTH_XPC_ITEM_FLAGS "_item_flags"
51#define AUTH_XPC_ITEM_VALUE "_item_value"
52#define AUTH_XPC_ITEM_TYPE  "_item_type"
53
54#define AUTH_XPC_REQUEST_METHOD_KEY "_agent_request_key"
55#define AUTH_XPC_REQUEST_METHOD_CREATE "_agent_request_create"
56#define AUTH_XPC_REQUEST_METHOD_INVOKE "_agent_request_invoke"
57#define AUTH_XPC_REQUEST_METHOD_DEACTIVATE "_agent_request_deactivate"
58#define AUTH_XPC_REQUEST_METHOD_DESTROY "_agent_request_destroy"
59#define AUTH_XPC_REPLY_METHOD_KEY "_agent_reply_key"
60#define AUTH_XPC_REPLY_METHOD_RESULT "_agent_reply_result"
61#define AUTH_XPC_REPLY_METHOD_INTERRUPT "_agent_reply_interrupt"
62#define AUTH_XPC_REPLY_METHOD_CREATE "_agent_reply_create"
63#define AUTH_XPC_REPLY_METHOD_DEACTIVATE "_agent_reply_deactivate"
64#define AUTH_XPC_PLUGIN_NAME "_agent_plugin"
65#define AUTH_XPC_MECHANISM_NAME "_agent_mechanism"
66#define AUTH_XPC_HINTS_NAME "_agent_hints"
67#define AUTH_XPC_CONTEXT_NAME "_agent_context"
68#define AUTH_XPC_IMMUTABLE_HINTS_NAME "_agent_immutable_hints"
69#define AUTH_XPC_REQUEST_INSTANCE "_agent_instance"
70#define AUTH_XPC_REPLY_RESULT_VALUE "_agent_reply_result_value"
71#define AUTH_XPC_AUDIT_SESSION_PORT "_agent_audit_session_port"
72#define AUTH_XPC_BOOTSTRAP_PORT "_agent_bootstrap_port"
73#define AUTH_XPC_SESSION_UUID "_agent_session_uuid"
74#define AUTH_XPC_SESSION_PREFS "_agent_session_prefs"
75#define AUTH_XPC_SESSION_INPUT_METHOD "_agent_session_inputMethod"
76
77#define UUID_INITIALIZER_FROM_SESSIONID(sessionid) \
78{ 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))) }
79
80//
81// NOSA support functions. This is a test mode where the SecurityAgent
82// is simulated via stdio in the client. Good for running automated tests
83// of client programs. Only available if -DNOSA when compiling.
84//
85#if defined(NOSA)
86
87#include <cstdarg>
88
89static void getNoSA(char *buffer, size_t bufferSize, const char *fmt, ...)
90{
91	// write prompt
92	va_list args;
93	va_start(args, fmt);
94	vfprintf(stdout, fmt, args);
95	va_end(args);
96
97	// read reply
98	memset(buffer, 0, bufferSize);
99	const char *nosa = getenv("NOSA");
100	if (!strcmp(nosa, "-")) {
101		if (fgets(buffer, bufferSize-1, stdin) == NULL)
102			CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
103		buffer[strlen(buffer)-1] = '\0';	// remove trailing newline
104		if (!isatty(fileno(stdin)))
105			printf("%s\n", buffer);			// echo to output if input not terminal
106	} else {
107		strncpy(buffer, nosa, bufferSize-1);
108		printf("%s\n", buffer);
109	}
110	if (buffer[0] == '\0')				// empty input -> cancellation
111		CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
112}
113
114#endif //NOSA
115
116
117// SecurityAgentConnection
118
119SecurityAgentConnection::SecurityAgentConnection(const AuthHostType type, Session &session)
120: mAuthHostType(type),
121mHostInstance(session.authhost(mAuthHostType)),
122mConnection(&Server::connection()),
123mAuditToken(Server::connection().auditToken())
124{
125	// this may take a while
126	Server::active().longTermActivity();
127    secdebug("SecurityAgentConnection", "new SecurityAgentConnection(%p)", this);
128}
129
130SecurityAgentConnection::~SecurityAgentConnection()
131{
132    secdebug("SecurityAgentConnection", "SecurityAgentConnection(%p) dying", this);
133	mConnection->useAgent(NULL);
134}
135
136void
137SecurityAgentConnection::activate()
138{
139    secdebug("SecurityAgentConnection", "activate(%p)", this);
140
141    Session &session = mHostInstance->session();
142    SessionId targetSessionId = session.sessionId();
143    MachPlusPlus::Bootstrap processBootstrap = Server::process().taskPort().bootstrap();
144    fileport_t userPrefsFP = MACH_PORT_NULL;
145
146    // send the the userPrefs to SecurityAgent
147    if (mAuthHostType == securityAgent || mAuthHostType == userAuthHost) {
148		CFRef<CFDataRef> userPrefs(mHostInstance->session().copyUserPrefs());
149		if (0 != userPrefs)
150		{
151			FILE *mbox = NULL;
152			int fd = 0;
153			mbox = tmpfile();
154			if (NULL != mbox)
155			{
156				fd = dup(fileno(mbox));
157				fclose(mbox);
158				if (fd != -1)
159				{
160					CFIndex length = CFDataGetLength(userPrefs);
161					if (write(fd, CFDataGetBytePtr(userPrefs), length) != length)
162						Syslog::error("could not write userPrefs");
163					else
164					{
165						if (0 == fileport_makeport(fd, &userPrefsFP))
166							secdebug("SecurityAgentConnection", "stashed the userPrefs file descriptor");
167						else
168							Syslog::error("failed to stash the userPrefs file descriptor");
169					}
170					close(fd);
171				}
172			}
173		}
174		if (MACH_PORT_NULL == userPrefsFP)
175		{
176			secdebug("SecurityAgentConnection", "could not read userPrefs");
177		}
178    }
179
180	mConnection->useAgent(this);
181	try
182    {
183        StLock<Mutex> _(*mHostInstance);
184
185        mach_port_t lookupPort = mHostInstance->lookup(targetSessionId);
186        if (MACH_PORT_NULL == lookupPort)
187        {
188			Syslog::error("could not find real service, bailing");
189			MacOSError::throwMe(CSSM_ERRCODE_SERVICE_NOT_AVAILABLE);
190        }
191        // reset Client contact info
192        mPort = lookupPort;
193        SecurityAgent::Client::activate(mPort);
194
195        secdebug("SecurityAgentConnection", "%p activated", this);
196	}
197    catch (MacOSError &err)
198    {
199		mConnection->useAgent(NULL);	// guess not
200        Syslog::error("SecurityAgentConnection: error activating %s instance %p",
201                      mAuthHostType == privilegedAuthHost
202                      ? "authorizationhost"
203                      : "SecurityAgent", this);
204		throw;
205	}
206
207    secdebug("SecurityAgentConnection", "contacting service (%p)", this);
208	mach_port_name_t jobPort;
209	if (0 > audit_session_port(session.sessionId(), &jobPort))
210		Syslog::error("audit_session_port failed: %m");
211    MacOSError::check(SecurityAgent::Client::contact(jobPort, processBootstrap, userPrefsFP));
212    secdebug("SecurityAgentConnection", "contact didn't throw (%p)", this);
213
214    if (userPrefsFP != MACH_PORT_NULL)
215        mach_port_deallocate(mach_task_self(), userPrefsFP);
216}
217
218void
219SecurityAgentConnection::reconnect()
220{
221    // if !mHostInstance throw()?
222    if (mHostInstance)
223    {
224        activate();
225    }
226}
227
228void
229SecurityAgentConnection::terminate()
230{
231	activate();
232
233    // @@@ This happens already in the destructor; presumably we do this to tear things down orderly
234	mConnection->useAgent(NULL);
235}
236
237
238// SecurityAgentConnection
239
240SecurityAgentXPCConnection::SecurityAgentXPCConnection(const AuthHostType type, Session &session)
241: mAuthHostType(type),
242mHostInstance(session.authhost(mAuthHostType)),
243mSession(session),
244mConnection(&Server::connection()),
245mAuditToken(Server::connection().auditToken())
246{
247	// this may take a while
248	Server::active().longTermActivity();
249    secdebug("SecurityAgentConnection", "new SecurityAgentConnection(%p)", this);
250    mXPCConnection = NULL;
251    mXPCStubConnection = NULL;
252    mNobodyUID = -2;
253    struct passwd *pw = getpwnam("nobody");
254    if (NULL != pw) {
255        mNobodyUID = pw->pw_uid;
256    }
257}
258
259SecurityAgentXPCConnection::~SecurityAgentXPCConnection()
260{
261    secdebug("SecurityAgentConnection", "SecurityAgentConnection(%p) dying", this);
262	mConnection->useAgent(NULL);
263
264    // If a connection has been established, we need to tear it down.
265    if (NULL != mXPCConnection) {
266        // Tearing this down is a multi-step process. First, request a cancellation.
267        // This is safe even if the connection is already in the cancelled state.
268        xpc_connection_cancel(mXPCConnection);
269
270        // Then release the XPC connection
271        xpc_release(mXPCConnection);
272        mXPCConnection = NULL;
273
274        if (NULL != mXPCStubConnection) {
275            // We may or may not have one of these
276            xpc_release(mXPCStubConnection);
277            mXPCStubConnection = NULL;
278        }
279    }
280}
281
282bool SecurityAgentXPCConnection::inDarkWake()
283{
284	return mSession.server().inDarkWake();
285}
286
287void
288SecurityAgentXPCConnection::activate(bool ignoreUid)
289{
290    secdebug("SecurityAgentConnection", "activate(%p)", this);
291
292	mConnection->useAgent(this);
293    if (mXPCConnection != NULL) {
294        // If we already have an XPC connection, there's nothing to do.
295        return;
296    }
297	try
298    {
299        if (mAuthHostType == securityAgent) {
300            uuid_t sessionUUID = UUID_INITIALIZER_FROM_SESSIONID(mSession.sessionId());
301            // Yes, these need to be throws, as we're still in securityd, and thus still have to do flow control with exceptions.
302            if (!(mSession.attributes() & sessionHasGraphicAccess))
303                CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
304            if (inDarkWake())
305                CssmError::throwMe(CSSM_ERRCODE_IN_DARK_WAKE);
306            uid_t targetUid = mHostInstance->session().originatorUid();
307            secdebug("SecurityAgentXPCConnection","Retrieved UID %d for this session", targetUid);
308            if ((int32_t)targetUid != -1) {
309                mXPCStubConnection = xpc_connection_create_mach_service(SECURITYAGENT_STUB_BOOTSTRAP_NAME_BASE, NULL, 0);
310                xpc_connection_set_target_uid(mXPCStubConnection, targetUid);
311                secdebug("SecurityAgentXPCConnection", "Creating a security agent stub");
312                xpc_connection_set_event_handler(mXPCStubConnection, ^(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.
313                xpc_connection_resume(mXPCStubConnection);
314
315                xpc_object_t wakeupMessage = xpc_dictionary_create(NULL, NULL, 0);
316                xpc_dictionary_set_data(wakeupMessage, AUTH_XPC_SESSION_UUID, sessionUUID, sizeof(uuid_t));
317                xpc_object_t responseMessage = xpc_connection_send_message_with_reply_sync(mXPCStubConnection, wakeupMessage);
318                if (xpc_get_type(responseMessage) == XPC_TYPE_DICTIONARY) {
319                    secdebug("SecurityAgentXPCConnection", "Valid response received from stub");
320                } else {
321                    secdebug("SecurityAgentXPCConnection", "Error response received from stub");
322                }
323                xpc_release(wakeupMessage);
324                xpc_release(responseMessage);
325            }
326
327            mXPCConnection = xpc_connection_create_mach_service(SECURITYAGENT_BOOTSTRAP_NAME_BASE, NULL,0);
328            xpc_connection_set_instance(mXPCConnection, sessionUUID);
329            secdebug("SecurityAgentXPCConnection", "Creating a security agent");
330        } else {
331            mXPCConnection = xpc_connection_create_mach_service(AUTHORIZATIONHOST_BOOTSTRAP_NAME_BASE, NULL, 0);
332            secdebug("SecurityAgentXPCConnection", "Creating a standard authhost");
333        }
334
335        xpc_connection_set_event_handler(mXPCConnection, ^(xpc_object_t object) {
336            if (xpc_get_type(object) == XPC_TYPE_ERROR) {
337                secdebug("SecurityAgentXPCConnection", "error during xpc: %s", xpc_dictionary_get_string(object, XPC_ERROR_KEY_DESCRIPTION));
338            }
339        });
340
341        xpc_connection_resume(mXPCConnection);
342
343        secdebug("SecurityAgentXPCConnection", "%p activated", this);
344	}
345    catch (MacOSError &err)
346    {
347		mConnection->useAgent(NULL);	// guess not
348        Syslog::error("SecurityAgentConnection: error activating %s instance %p",
349                      mAuthHostType == privilegedAuthHost
350                      ? "authorizationhost"
351                      : "SecurityAgent", this);
352		throw;
353	}
354
355    secdebug("SecurityAgentXPCConnection", "contact didn't throw (%p)", this);
356}
357
358void
359SecurityAgentXPCConnection::reconnect()
360{
361}
362
363void
364SecurityAgentXPCConnection::terminate()
365{
366	activate(false);
367
368    // @@@ This happens already in the destructor; presumably we do this to tear things down orderly
369	mConnection->useAgent(NULL);
370}
371
372
373using SecurityAgent::Reason;
374using namespace Authorization;
375
376SecurityAgentQuery::SecurityAgentQuery(const AuthHostType type, Session &session)
377: SecurityAgentConnection(type, session)
378{
379    secdebug("SecurityAgentQuery", "new SecurityAgentQuery(%p)", this);
380}
381
382SecurityAgentQuery::~SecurityAgentQuery()
383{
384    secdebug("SecurityAgentQuery", "SecurityAgentQuery(%p) dying", this);
385
386#if defined(NOSA)
387	if (getenv("NOSA")) {
388		printf(" [query done]\n");
389		return;
390	}
391#endif
392
393    if (SecurityAgent::Client::state() != SecurityAgent::Client::dead)
394        destroy();
395}
396
397void
398SecurityAgentQuery::inferHints(Process &thisProcess)
399{
400    string guestPath;
401	{
402		StLock<Mutex> _(thisProcess);
403		if (SecCodeRef clientCode = thisProcess.currentGuest())
404			guestPath = codePath(clientCode);
405	}
406	AuthItemSet processHints = clientHints(SecurityAgent::bundle, guestPath,
407                                           thisProcess.pid(), thisProcess.uid());
408	mClientHints.insert(processHints.begin(), processHints.end());
409}
410
411void SecurityAgentQuery::addHint(const char *name, const void *value, UInt32 valueLen, UInt32 flags)
412{
413    AuthorizationItem item = { name, valueLen, const_cast<void *>(value), flags };
414    mClientHints.insert(AuthItemRef(item));
415}
416
417
418void
419SecurityAgentQuery::readChoice()
420{
421    allow = false;
422    remember = false;
423
424	AuthItem *allowAction = outContext().find(AGENT_CONTEXT_ALLOW);
425	if (allowAction)
426	{
427        string allowString;
428		if (allowAction->getString(allowString)
429            && (allowString == "YES"))
430            allow = true;
431	}
432
433	AuthItem *rememberAction = outContext().find(AGENT_CONTEXT_REMEMBER_ACTION);
434	if (rememberAction)
435	{
436        string rememberString;
437        if (rememberAction->getString(rememberString)
438            && (rememberString == "YES"))
439            remember = true;
440	}
441}
442
443void
444SecurityAgentQuery::disconnect()
445{
446    SecurityAgent::Client::destroy();
447}
448
449void
450SecurityAgentQuery::terminate()
451{
452    // you might think these are called in the wrong order, but you'd be wrong
453    SecurityAgentConnection::terminate();
454	SecurityAgent::Client::terminate();
455}
456
457void
458SecurityAgentQuery::create(const char *pluginId, const char *mechanismId, const SessionId inSessionId)
459{
460	activate();
461	OSStatus status = SecurityAgent::Client::create(pluginId, mechanismId, inSessionId);
462	if (status)
463	{
464		secdebug("SecurityAgentQuery", "agent went walkabout, restarting");
465        reconnect();
466		status = SecurityAgent::Client::create(pluginId, mechanismId, inSessionId);
467	}
468	if (status) MacOSError::throwMe(status);
469}
470
471ModuleNexus<RecursiveMutex> gAllXPCClientsMutex;
472ModuleNexus<set<SecurityAgentXPCQuery*> > allXPCClients;
473
474void
475SecurityAgentXPCQuery::killAllXPCClients()
476{
477    // grab the lock for the client list -- we need to make sure no one modifies the structure while we are iterating it.
478    StLock<Mutex> _(gAllXPCClientsMutex());
479
480    set<SecurityAgentXPCQuery*>::iterator clientIterator = allXPCClients().begin();
481    while (clientIterator != allXPCClients().end())
482    {
483        set<SecurityAgentXPCQuery*>::iterator thisClient = clientIterator++;
484        if ((*thisClient)->getTerminateOnSleep())
485        {
486            (*thisClient)->terminate();
487        }
488    }
489}
490
491
492SecurityAgentXPCQuery::SecurityAgentXPCQuery(const AuthHostType type, Session &session)
493: SecurityAgentXPCConnection(type, session), mAgentConnected(false), mTerminateOnSleep(false)
494{
495    secdebug("SecurityAgentXPCQuery", "new SecurityAgentXPCQuery(%p)", this);
496}
497
498SecurityAgentXPCQuery::~SecurityAgentXPCQuery()
499{
500    secdebug("SecurityAgentXPCQuery", "SecurityAgentXPCQuery(%p) dying", this);
501    if (mAgentConnected) {
502        this->disconnect();
503    }
504}
505
506void
507SecurityAgentXPCQuery::inferHints(Process &thisProcess)
508{
509    string guestPath;
510	if (SecCodeRef clientCode = thisProcess.currentGuest())
511		guestPath = codePath(clientCode);
512
513    AuthItemSet clientHints;
514    SecurityAgent::RequestorType type = SecurityAgent::bundle;
515    pid_t clientPid = thisProcess.pid();
516    uid_t clientUid = thisProcess.uid();
517
518	clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_TYPE, AuthValueOverlay(sizeof(type), &type)));
519	clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PATH, AuthValueOverlay(guestPath)));
520	clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PID, AuthValueOverlay(sizeof(clientPid), &clientPid)));
521	clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_UID, AuthValueOverlay(sizeof(clientUid), &clientUid)));
522
523
524	mClientHints.insert(clientHints.begin(), clientHints.end());
525
526    bool validSignature = thisProcess.checkAppleSigned();
527    AuthItemSet clientImmutableHints;
528
529	clientImmutableHints.insert(AuthItemRef(AGENT_HINT_PROCESS_SIGNED, AuthValueOverlay(sizeof(validSignature), &validSignature)));
530
531	mImmutableHints.insert(clientImmutableHints.begin(), clientImmutableHints.end());
532}
533
534void SecurityAgentXPCQuery::addHint(const char *name, const void *value, UInt32 valueLen, UInt32 flags)
535{
536    AuthorizationItem item = { name, valueLen, const_cast<void *>(value), flags };
537    mClientHints.insert(AuthItemRef(item));
538}
539
540
541void
542SecurityAgentXPCQuery::readChoice()
543{
544    allow = false;
545    remember = false;
546
547	AuthItem *allowAction = mOutContext.find(AGENT_CONTEXT_ALLOW);
548	if (allowAction)
549	{
550        string allowString;
551		if (allowAction->getString(allowString)
552            && (allowString == "YES"))
553            allow = true;
554	}
555
556	AuthItem *rememberAction = mOutContext.find(AGENT_CONTEXT_REMEMBER_ACTION);
557	if (rememberAction)
558	{
559        string rememberString;
560        if (rememberAction->getString(rememberString)
561            && (rememberString == "YES"))
562            remember = true;
563	}
564}
565
566void
567SecurityAgentXPCQuery::disconnect()
568{
569    if (NULL != mXPCConnection) {
570        xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
571        xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_DESTROY);
572        xpc_connection_send_message(mXPCConnection, requestObject);
573        xpc_release(requestObject);
574    }
575
576    StLock<Mutex> _(gAllXPCClientsMutex());
577    allXPCClients().erase(this);
578}
579
580void
581SecurityAgentXPCQuery::terminate()
582{
583    this->disconnect();
584}
585
586static void xpcArrayToAuthItemSet(AuthItemSet *setToBuild, xpc_object_t input) {
587    setToBuild->clear();
588
589    xpc_array_apply(input,  ^bool(size_t index, xpc_object_t item) {
590        const char *name = xpc_dictionary_get_string(item, AUTH_XPC_ITEM_NAME);
591
592        size_t length;
593        const void *data = xpc_dictionary_get_data(item, AUTH_XPC_ITEM_VALUE, &length);
594        void *dataCopy = malloc(length);
595        memcpy(dataCopy, data, length);
596
597        uint64_t flags = xpc_dictionary_get_uint64(item, AUTH_XPC_ITEM_FLAGS);
598        AuthItemRef nextItem(name, AuthValueOverlay((uint32_t)length, dataCopy), (uint32_t)flags);
599        setToBuild->insert(nextItem);
600        memset(dataCopy, 0, length); // The authorization items contain things like passwords, so wiping clean is important.
601        free(dataCopy);
602        return true;
603    });
604}
605
606void
607SecurityAgentXPCQuery::create(const char *pluginId, const char *mechanismId, const SessionId inSessionId)
608{
609    bool ignoreUid = false;
610
611    do {
612        activate(ignoreUid);
613
614        mAgentConnected = false;
615
616        xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
617        xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_CREATE);
618        xpc_dictionary_set_string(requestObject, AUTH_XPC_PLUGIN_NAME, pluginId);
619        xpc_dictionary_set_string(requestObject, AUTH_XPC_MECHANISM_NAME, mechanismId);
620
621        uid_t targetUid = Server::process().uid();
622        bool doSwitchAudit =  true; // (ignoreUid) || ((targetUid == 0) || (targetUid == mNobodyUID));
623        bool doSwitchBootstrap = true; // (ignoreUid) || ((targetUid == 0) || (targetUid == mNobodyUID));
624
625        if (doSwitchAudit) {
626            mach_port_name_t jobPort;
627            if (0 == audit_session_port(mSession.sessionId(), &jobPort)) {
628                secdebug("SecurityAgentXPCQuery", "attaching an audit session port because the uid was %d", targetUid);
629                xpc_dictionary_set_mach_send(requestObject, AUTH_XPC_AUDIT_SESSION_PORT, jobPort);
630                if (mach_port_mod_refs(mach_task_self(), jobPort, MACH_PORT_RIGHT_SEND, -1) != KERN_SUCCESS) {
631                    secdebug("SecurityAgentXPCQuery", "unable to release send right for audit session, leaking");
632                }
633            }
634        }
635
636        if (doSwitchBootstrap) {
637            secdebug("SecurityAgentXPCQuery", "attaching a bootstrap port because the uid was %d", targetUid);
638            MachPlusPlus::Bootstrap processBootstrap = Server::process().taskPort().bootstrap();
639            xpc_dictionary_set_mach_send(requestObject, AUTH_XPC_BOOTSTRAP_PORT, processBootstrap);
640        }
641
642        xpc_object_t object = xpc_connection_send_message_with_reply_sync(mXPCConnection, requestObject);
643        if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) {
644            const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY);
645            if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_CREATE)) {
646                uint64_t status = xpc_dictionary_get_uint64(object, AUTH_XPC_REPLY_RESULT_VALUE);
647                if (status == kAuthorizationResultAllow) {
648                    mAgentConnected = true;
649                } else {
650                    secdebug("SecurityAgentXPCQuery", "plugin create failed in SecurityAgent");
651                    MacOSError::throwMe(errAuthorizationInternal);
652                }
653            }
654        } else if (xpc_get_type(object) == XPC_TYPE_ERROR) {
655            if (XPC_ERROR_CONNECTION_INVALID == object) {
656                // If we get an error before getting the create response, try again without the UID
657                if (ignoreUid) {
658                    secdebug("SecurityAgentXPCQuery", "failed to establish connection, no retries left");
659                    xpc_release(object);
660                    MacOSError::throwMe(errAuthorizationInternal);
661                } else {
662                    secdebug("SecurityAgentXPCQuery", "failed to establish connection, retrying with no UID");
663                    ignoreUid = true;
664                    xpc_release(mXPCConnection);
665                    mXPCConnection = NULL;
666                }
667            } else if (XPC_ERROR_CONNECTION_INTERRUPTED == object) {
668                // If we get an error before getting the create response, try again
669            }
670        }
671        xpc_release(object);
672        xpc_release(requestObject);
673    } while (!mAgentConnected);
674
675    StLock<Mutex> _(gAllXPCClientsMutex());
676    allXPCClients().insert(this);
677}
678
679static xpc_object_t authItemSetToXPCArray(AuthItemSet input) {
680    xpc_object_t outputArray = xpc_array_create(NULL, 0);
681    for (AuthItemSet::iterator i = input.begin(); i != input.end(); i++) {
682        AuthItemRef item = *i;
683
684        xpc_object_t xpc_data = xpc_dictionary_create(NULL, NULL, 0);
685        xpc_dictionary_set_string(xpc_data, AUTH_XPC_ITEM_NAME, item->name());
686        AuthorizationValue value = item->value();
687        if (value.data != NULL) {
688            xpc_dictionary_set_data(xpc_data, AUTH_XPC_ITEM_VALUE, value.data, value.length);
689        }
690        xpc_dictionary_set_uint64(xpc_data, AUTH_XPC_ITEM_FLAGS, item->flags());
691        xpc_array_append_value(outputArray, xpc_data);
692        xpc_release(xpc_data);
693    }
694    return outputArray;
695}
696
697OSStatus
698SecurityAgentXPCQuery::invoke() {
699    __block OSStatus status = kAuthorizationResultUndefined;
700
701    xpc_object_t hintsArray = authItemSetToXPCArray(mInHints);
702    xpc_object_t contextArray = authItemSetToXPCArray(mInContext);
703    xpc_object_t immutableHintsArray = authItemSetToXPCArray(mImmutableHints);
704
705    xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
706    xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_INVOKE);
707    xpc_dictionary_set_value(requestObject, AUTH_XPC_HINTS_NAME, hintsArray);
708    xpc_dictionary_set_value(requestObject, AUTH_XPC_CONTEXT_NAME, contextArray);
709    xpc_dictionary_set_value(requestObject, AUTH_XPC_IMMUTABLE_HINTS_NAME, immutableHintsArray);
710
711    xpc_object_t object = xpc_connection_send_message_with_reply_sync(mXPCConnection, requestObject);
712    if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) {
713        const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY);
714        if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_RESULT)) {
715            xpc_object_t xpcHints = xpc_dictionary_get_value(object, AUTH_XPC_HINTS_NAME);
716            xpc_object_t xpcContext = xpc_dictionary_get_value(object, AUTH_XPC_CONTEXT_NAME);
717            AuthItemSet tempHints, tempContext;
718            xpcArrayToAuthItemSet(&tempHints, xpcHints);
719            xpcArrayToAuthItemSet(&tempContext, xpcContext);
720            mOutHints = tempHints;
721            mOutContext = tempContext;
722            mLastResult = xpc_dictionary_get_uint64(object, AUTH_XPC_REPLY_RESULT_VALUE);
723        }
724    } else if (xpc_get_type(object) == XPC_TYPE_ERROR) {
725        if (XPC_ERROR_CONNECTION_INVALID == object) {
726            // If the connection drops, return an "auth undefined" result, because we cannot continue
727        } else if (XPC_ERROR_CONNECTION_INTERRUPTED == object) {
728            // If the agent dies, return an "auth undefined" result, because we cannot continue
729        }
730    }
731    xpc_release(object);
732
733    xpc_release(hintsArray);
734    xpc_release(contextArray);
735    xpc_release(immutableHintsArray);
736    xpc_release(requestObject);
737
738    return status;
739}
740
741void SecurityAgentXPCQuery::checkResult()
742{
743    // now check the OSStatus return from the server side
744    switch (mLastResult) {
745        case kAuthorizationResultAllow: return;
746        case kAuthorizationResultDeny:
747        case kAuthorizationResultUserCanceled: CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
748        default: MacOSError::throwMe(errAuthorizationInternal);
749    }
750}
751
752//
753// Perform the "rogue app" access query dialog
754//
755QueryKeychainUse::QueryKeychainUse(bool needPass, const Database *db)
756	: mPassphraseCheck(NULL)
757{
758	// if passphrase checking requested, save KeychainDatabase reference
759	// (will quietly disable check if db isn't a keychain)
760	if (needPass)
761		mPassphraseCheck = dynamic_cast<const KeychainDatabase *>(db);
762
763    setTerminateOnSleep(true);
764}
765
766Reason QueryKeychainUse::queryUser (const char *database, const char *description, AclAuthorization action)
767{
768    Reason reason = SecurityAgent::noReason;
769    int retryCount = 0;
770	OSStatus status;
771	AuthValueVector arguments;
772	AuthItemSet hints, context;
773
774#if defined(NOSA)
775	if (getenv("NOSA")) {
776		char answer[maxPassphraseLength+10];
777
778        string applicationPath;
779        AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
780		if (applicationPathItem)
781		  applicationPathItem->getString(applicationPath);
782
783		getNoSA(answer, sizeof(answer), "Allow %s to do %d on %s in %s? [yn][g]%s ",
784			applicationPath.c_str(), int(action), (description ? description : "[NULL item]"),
785			(database ? database : "[NULL database]"),
786			mPassphraseCheck ? ":passphrase" : "");
787		// turn passphrase (no ':') into y:passphrase
788		if (mPassphraseCheck && !strchr(answer, ':')) {
789			memmove(answer+2, answer, strlen(answer)+1);
790			memcpy(answer, "y:", 2);
791		}
792
793		allow = answer[0] == 'y';
794		remember = answer[1] == 'g';
795		return SecurityAgent::noReason;
796	}
797#endif
798
799	// prepopulate with client hints
800	hints.insert(mClientHints.begin(), mClientHints.end());
801
802	// put action/operation (sint32) into hints
803	hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast<sint32*>(&action))));
804
805	// item name into hints
806
807	hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? (uint32_t)strlen(description) : 0, const_cast<char*>(description))));
808
809	// keychain name into hints
810	hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? (uint32_t)strlen(database) : 0, const_cast<char*>(database))));
811
812	if (mPassphraseCheck)
813	{
814		create("builtin", "confirm-access-password", noSecuritySession);
815
816		CssmAutoData data(Allocator::standard(Allocator::sensitive));
817
818		do
819		{
820
821            AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
822            hints.erase(triesHint); hints.insert(triesHint); // replace
823
824            if (retryCount++ > kMaximumAuthorizationTries)
825			{
826                reason = SecurityAgent::tooManyTries;
827			}
828
829            AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
830            hints.erase(retryHint); hints.insert(retryHint); // replace
831
832            setInput(hints, context);
833			status = invoke();
834
835            if (retryCount > kMaximumAuthorizationTries)
836			{
837                return reason;
838			}
839
840			checkResult();
841
842			AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
843			if (!passwordItem)
844				continue;
845
846			passwordItem->getCssmData(data);
847		}
848		while ((reason = (const_cast<KeychainDatabase*>(mPassphraseCheck)->decode(data) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase)));
849	}
850	else
851	{
852		create("builtin", "confirm-access", noSecuritySession);
853        setInput(hints, context);
854		invoke();
855	}
856
857    readChoice();
858
859	return reason;
860}
861
862//
863// Perform code signature ACL access adjustment dialogs
864//
865bool QueryCodeCheck::operator () (const char *aclPath)
866{
867	OSStatus status;
868	AuthValueVector arguments;
869	AuthItemSet hints, context;
870
871#if defined(NOSA)
872	if (getenv("NOSA")) {
873		char answer[10];
874
875        string applicationPath;
876        AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
877		if (applicationPathItem)
878		  applicationPathItem->getString(applicationPath);
879
880		getNoSA(answer, sizeof(answer),
881				"Allow %s to match an ACL for %s [yn][g]? ",
882				applicationPath.c_str(), aclPath ? aclPath : "(unknown)");
883		allow = answer[0] == 'y';
884		remember = answer[1] == 'g';
885		return;
886	}
887#endif
888
889	// prepopulate with client hints
890	hints.insert(mClientHints.begin(), mClientHints.end());
891
892	hints.insert(AuthItemRef(AGENT_HINT_APPLICATION_PATH, AuthValueOverlay((uint32_t)strlen(aclPath), const_cast<char*>(aclPath))));
893
894	create("builtin", "code-identity", noSecuritySession);
895
896    setInput(hints, context);
897	status = invoke();
898
899    checkResult();
900
901//	MacOSError::check(status);
902
903    return kAuthorizationResultAllow == mLastResult;
904}
905
906
907//
908// Obtain passphrases and submit them to the accept() method until it is accepted
909// or we can't get another passphrase. Accept() should consume the passphrase
910// if it is accepted. If no passphrase is acceptable, throw out of here.
911//
912Reason QueryOld::query()
913{
914	Reason reason = SecurityAgent::noReason;
915	OSStatus status;
916	AuthValueVector arguments;
917	AuthItemSet hints, context;
918	CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
919	int retryCount = 0;
920
921#if defined(NOSA)
922    // return the passphrase
923	if (getenv("NOSA")) {
924        char passphrase_[maxPassphraseLength];
925		getNoSA(passphrase, maxPassphraseLength, "Unlock %s [<CR> to cancel]: ", database.dbName());
926	passphrase.copy(passphrase_, strlen(passphrase_));
927    	return database.decode(passphrase) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase;
928	}
929#endif
930
931	// prepopulate with client hints
932
933    const char *keychainPath = database.dbName();
934    hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
935
936	hints.insert(mClientHints.begin(), mClientHints.end());
937
938	create("builtin", "unlock-keychain", noSecuritySession);
939
940	do
941	{
942        AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
943        hints.erase(triesHint); hints.insert(triesHint); // replace
944
945        ++retryCount;
946
947        if (retryCount > maxTries)
948		{
949			reason = SecurityAgent::tooManyTries;
950        }
951
952        AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
953        hints.erase(retryHint); hints.insert(retryHint); // replace
954
955        setInput(hints, context);
956        status = invoke();
957
958        if (retryCount > maxTries)
959        {
960			return reason;
961		}
962
963        checkResult();
964
965		AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
966		if (!passwordItem)
967			continue;
968
969		passwordItem->getCssmData(passphrase);
970
971	}
972	while ((reason = accept(passphrase)));
973
974	return SecurityAgent::noReason;
975}
976
977
978//
979// Get existing passphrase (unlock) Query
980//
981Reason QueryOld::operator () ()
982{
983	return query();
984}
985
986
987//
988// End-classes for old secrets
989//
990Reason QueryUnlock::accept(CssmManagedData &passphrase)
991{
992	if (safer_cast<KeychainDatabase &>(database).decode(passphrase))
993		return SecurityAgent::noReason;
994	else
995		return SecurityAgent::invalidPassphrase;
996}
997
998Reason QueryUnlock::retrievePassword(CssmOwnedData &passphrase) {
999    CssmAutoData pass(Allocator::standard(Allocator::sensitive));
1000
1001    AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
1002    if (!passwordItem)
1003        return SecurityAgent::invalidPassphrase;
1004
1005    passwordItem->getCssmData(pass);
1006
1007    passphrase = pass;
1008
1009   return SecurityAgent::noReason;
1010}
1011
1012QueryKeybagPassphrase::QueryKeybagPassphrase(Session & session, int32_t tries) : mSession(session), mContext(), mRetries(tries)
1013{
1014    setTerminateOnSleep(true);
1015    mContext = mSession.get_current_service_context();
1016}
1017
1018Reason QueryKeybagPassphrase::query()
1019{
1020	Reason reason = SecurityAgent::noReason;
1021	OSStatus status;
1022	AuthValueVector arguments;
1023	AuthItemSet hints, context;
1024	CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
1025	int retryCount = 0;
1026
1027	// prepopulate with client hints
1028
1029    const char *keychainPath = "iCloud";
1030    hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
1031
1032	hints.insert(mClientHints.begin(), mClientHints.end());
1033
1034	create("builtin", "unlock-keychain", noSecuritySession);
1035
1036	do
1037	{
1038        if (retryCount > mRetries)
1039		{
1040			return SecurityAgent::tooManyTries;
1041        }
1042
1043        AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
1044        hints.erase(triesHint); hints.insert(triesHint); // replace
1045
1046        AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
1047        hints.erase(retryHint); hints.insert(retryHint); // replace
1048
1049        setInput(hints, context);
1050        status = invoke();
1051
1052        checkResult();
1053
1054		AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
1055		if (!passwordItem)
1056			continue;
1057
1058		passwordItem->getCssmData(passphrase);
1059
1060        ++retryCount;
1061	}
1062	while ((reason = accept(passphrase)));
1063
1064	return SecurityAgent::noReason;
1065}
1066
1067Reason QueryKeybagPassphrase::accept(Security::CssmManagedData & password)
1068{
1069	if (service_client_kb_unlock(&mContext, password.data(), (int)password.length()) == 0) {
1070		mSession.keybagSetState(session_keybag_unlocked);
1071        return SecurityAgent::noReason;
1072    } else
1073		return SecurityAgent::invalidPassphrase;
1074}
1075
1076QueryKeybagNewPassphrase::QueryKeybagNewPassphrase(Session & session) : QueryKeybagPassphrase(session) {}
1077
1078Reason QueryKeybagNewPassphrase::query(CssmOwnedData &oldPassphrase, CssmOwnedData &passphrase)
1079{
1080    CssmAutoData pass(Allocator::standard(Allocator::sensitive));
1081    CssmAutoData oldPass(Allocator::standard(Allocator::sensitive));
1082    Reason reason = SecurityAgent::noReason;
1083	OSStatus status;
1084	AuthValueVector arguments;
1085	AuthItemSet hints, context;
1086	int retryCount = 0;
1087
1088	// prepopulate with client hints
1089
1090    const char *keychainPath = "iCloud";
1091    hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
1092
1093    const char *showResetString = "YES";
1094    hints.insert(AuthItemRef(AGENT_HINT_SHOW_RESET, AuthValueOverlay((uint32_t)strlen(showResetString), const_cast<char*>(showResetString))));
1095
1096	hints.insert(mClientHints.begin(), mClientHints.end());
1097
1098	create("builtin", "change-passphrase", noSecuritySession);
1099
1100    AuthItem *resetPassword = NULL;
1101	do
1102	{
1103        if (retryCount > mRetries)
1104		{
1105			return SecurityAgent::tooManyTries;
1106        }
1107
1108        AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
1109        hints.erase(triesHint); hints.insert(triesHint); // replace
1110
1111        AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
1112        hints.erase(retryHint); hints.insert(retryHint); // replace
1113
1114        setInput(hints, context);
1115        status = invoke();
1116
1117        checkResult();
1118
1119        resetPassword = mOutContext.find(AGENT_CONTEXT_RESET_PASSWORD);
1120        if (resetPassword != NULL) {
1121            return SecurityAgent::resettingPassword;
1122        }
1123
1124        AuthItem *oldPasswordItem = mOutContext.find(AGENT_PASSWORD);
1125        if (!oldPasswordItem)
1126            continue;
1127
1128        oldPasswordItem->getCssmData(oldPass);
1129
1130        ++retryCount;
1131	}
1132	while ((reason = accept(oldPass)));
1133
1134    if (reason == SecurityAgent::noReason) {
1135		AuthItem *passwordItem = mOutContext.find(AGENT_CONTEXT_NEW_PASSWORD);
1136		if (!passwordItem)
1137            return SecurityAgent::invalidPassphrase;
1138
1139		passwordItem->getCssmData(pass);
1140
1141        oldPassphrase = oldPass;
1142        passphrase = pass;
1143    }
1144
1145	return SecurityAgent::noReason;
1146}
1147
1148QueryPIN::QueryPIN(Database &db)
1149	: QueryOld(db), mPin(Allocator::standard())
1150{
1151	this->inferHints(Server::process());
1152}
1153
1154
1155Reason QueryPIN::accept(CssmManagedData &pin)
1156{
1157	// no retries for now
1158	mPin = pin;
1159	return SecurityAgent::noReason;
1160}
1161
1162
1163//
1164// Obtain passphrases and submit them to the accept() method until it is accepted
1165// or we can't get another passphrase. Accept() should consume the passphrase
1166// if it is accepted. If no passphrase is acceptable, throw out of here.
1167//
1168Reason QueryNewPassphrase::query()
1169{
1170	Reason reason = initialReason;
1171	CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
1172	CssmAutoData oldPassphrase(Allocator::standard(Allocator::sensitive));
1173
1174    OSStatus status;
1175	AuthValueVector arguments;
1176	AuthItemSet hints, context;
1177
1178	int retryCount = 0;
1179
1180#if defined(NOSA)
1181	if (getenv("NOSA")) {
1182        char passphrase_[maxPassphraseLength];
1183		getNoSA(passphrase_, maxPassphraseLength,
1184			"New passphrase for %s (reason %d) [<CR> to cancel]: ",
1185			database.dbName(), reason);
1186		return SecurityAgent::noReason;
1187	}
1188#endif
1189
1190	// prepopulate with client hints
1191	hints.insert(mClientHints.begin(), mClientHints.end());
1192
1193	// keychain name into hints
1194	hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database.dbName())));
1195
1196    switch (initialReason)
1197    {
1198        case SecurityAgent::newDatabase:
1199            create("builtin", "new-passphrase", noSecuritySession);
1200            break;
1201        case SecurityAgent::changePassphrase:
1202            create("builtin", "change-passphrase", noSecuritySession);
1203            break;
1204        default:
1205            assert(false);
1206    }
1207
1208	do
1209	{
1210        AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
1211        hints.erase(triesHint); hints.insert(triesHint); // replace
1212
1213		if (++retryCount > maxTries)
1214		{
1215			reason = SecurityAgent::tooManyTries;
1216		}
1217
1218        AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
1219        hints.erase(retryHint); hints.insert(retryHint); // replace
1220
1221        setInput(hints, context);
1222		status = invoke();
1223
1224		if (retryCount > maxTries)
1225		{
1226            return reason;
1227        }
1228
1229        checkResult();
1230
1231		if (SecurityAgent::changePassphrase == initialReason)
1232        {
1233            AuthItem *oldPasswordItem = mOutContext.find(AGENT_PASSWORD);
1234            if (!oldPasswordItem)
1235                continue;
1236
1237            oldPasswordItem->getCssmData(oldPassphrase);
1238        }
1239
1240		AuthItem *passwordItem = mOutContext.find(AGENT_CONTEXT_NEW_PASSWORD);
1241		if (!passwordItem)
1242			continue;
1243
1244		passwordItem->getCssmData(passphrase);
1245
1246    }
1247	while ((reason = accept(passphrase, (initialReason == SecurityAgent::changePassphrase) ? &oldPassphrase.get() : NULL)));
1248
1249	return SecurityAgent::noReason;
1250}
1251
1252
1253//
1254// Get new passphrase Query
1255//
1256Reason QueryNewPassphrase::operator () (CssmOwnedData &oldPassphrase, CssmOwnedData &passphrase)
1257{
1258	if (Reason result = query())
1259		return result;	// failed
1260	passphrase = mPassphrase;
1261    oldPassphrase = mOldPassphrase;
1262	return SecurityAgent::noReason;	// success
1263}
1264
1265Reason QueryNewPassphrase::accept(CssmManagedData &passphrase, CssmData *oldPassphrase)
1266{
1267	//@@@ acceptance criteria are currently hardwired here
1268	//@@@ This validation presumes ASCII - UTF8 might be more lenient
1269
1270	// if we have an old passphrase, check it
1271	if (oldPassphrase && !safer_cast<KeychainDatabase&>(database).validatePassphrase(*oldPassphrase))
1272		return SecurityAgent::oldPassphraseWrong;
1273
1274	// sanity check the new passphrase (but allow user override)
1275	if (!(mPassphraseValid && passphrase.get() == mPassphrase)) {
1276		mPassphrase = passphrase;
1277        if (oldPassphrase) mOldPassphrase = *oldPassphrase;
1278		mPassphraseValid = true;
1279		if (mPassphrase.length() == 0)
1280			return SecurityAgent::passphraseIsNull;
1281		if (mPassphrase.length() < 6)
1282			return SecurityAgent::passphraseTooSimple;
1283	}
1284
1285	// accept this
1286	return SecurityAgent::noReason;
1287}
1288
1289//
1290// Get a passphrase for unspecified use
1291//
1292Reason QueryGenericPassphrase::operator () (const CssmData *prompt, bool verify,
1293                                            string &passphrase)
1294{
1295    return query(prompt, verify, passphrase);
1296}
1297
1298Reason QueryGenericPassphrase::query(const CssmData *prompt, bool verify,
1299                                     string &passphrase)
1300{
1301    Reason reason = SecurityAgent::noReason;
1302    OSStatus status;    // not really used; remove?
1303    AuthValueVector arguments;
1304    AuthItemSet hints, context;
1305
1306#if defined(NOSA)
1307    if (getenv("NOSA")) {
1308		// FIXME  3690984
1309		return SecurityAgent::noReason;
1310    }
1311#endif
1312
1313    hints.insert(mClientHints.begin(), mClientHints.end());
1314    hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? (UInt32)prompt->length() : 0, prompt ? prompt->data() : NULL)));
1315    // XXX/gh  defined by dmitch but no analogous hint in
1316    // AuthorizationTagsPriv.h:
1317    // CSSM_ATTRIBUTE_ALERT_TITLE (optional alert panel title)
1318
1319    if (false == verify) {  // import
1320		create("builtin", "generic-unlock", noSecuritySession);
1321    } else {		// verify passphrase (export)
1322					// new-passphrase-generic works with the pre-4 June 2004 agent;
1323					// generic-new-passphrase is required for the new agent
1324		create("builtin", "generic-new-passphrase", noSecuritySession);
1325    }
1326
1327    AuthItem *passwordItem;
1328
1329    do {
1330        setInput(hints, context);
1331		status = invoke();
1332		checkResult();
1333		passwordItem = mOutContext.find(AGENT_PASSWORD);
1334
1335    } while (!passwordItem);
1336
1337    passwordItem->getString(passphrase);
1338
1339    return reason;
1340}
1341
1342
1343//
1344// Get a DB blob's passphrase--keychain synchronization
1345//
1346Reason QueryDBBlobSecret::operator () (DbHandle *dbHandleArray, uint8 dbHandleArrayCount, DbHandle *dbHandleAuthenticated)
1347{
1348    return query(dbHandleArray, dbHandleArrayCount, dbHandleAuthenticated);
1349}
1350
1351Reason QueryDBBlobSecret::query(DbHandle *dbHandleArray, uint8 dbHandleArrayCount, DbHandle *dbHandleAuthenticated)
1352{
1353    Reason reason = SecurityAgent::noReason;
1354	CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
1355    OSStatus status;    // not really used; remove?
1356    AuthValueVector arguments;
1357    AuthItemSet hints/*NUKEME*/, context;
1358
1359#if defined(NOSA)
1360    if (getenv("NOSA")) {
1361		// FIXME  akin to 3690984
1362		return SecurityAgent::noReason;
1363    }
1364#endif
1365
1366	hints.insert(mClientHints.begin(), mClientHints.end());
1367	create("builtin", "generic-unlock-kcblob", noSecuritySession);
1368
1369    AuthItem *secretItem;
1370
1371	int retryCount = 0;
1372
1373    do {
1374        AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
1375        hints.erase(triesHint); hints.insert(triesHint); // replace
1376
1377		if (++retryCount > maxTries)
1378		{
1379			reason = SecurityAgent::tooManyTries;
1380		}
1381
1382        AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
1383        hints.erase(retryHint); hints.insert(retryHint); // replace
1384
1385        setInput(hints, context);
1386		status = invoke();
1387		checkResult();
1388		secretItem = mOutContext.find(AGENT_PASSWORD);
1389		if (!secretItem)
1390			continue;
1391		secretItem->getCssmData(passphrase);
1392
1393    } while ((reason = accept(passphrase, dbHandleArray, dbHandleArrayCount, dbHandleAuthenticated)));
1394
1395    return reason;
1396}
1397
1398Reason QueryDBBlobSecret::accept(CssmManagedData &passphrase,
1399								 DbHandle *dbHandlesToAuthenticate, uint8 dbHandleCount, DbHandle *dbHandleAuthenticated)
1400{
1401	DbHandle *currHdl = dbHandlesToAuthenticate;
1402	short index;
1403	Boolean authenticated = false;
1404	for (index=0; index < dbHandleCount && !authenticated; index++)
1405	{
1406		try
1407		{
1408			RefPointer<KeychainDatabase> dbToUnlock = Server::keychain(*currHdl);
1409			dbToUnlock->unlockDb(passphrase);
1410			authenticated = true;
1411			*dbHandleAuthenticated = *currHdl; // return the DbHandle that 'passphrase' authenticated with.
1412		}
1413		catch (const CommonError &err)
1414		{
1415			currHdl++; // we failed to authenticate with this one, onto the next one.
1416		}
1417	}
1418	if ( !authenticated )
1419		return SecurityAgent::invalidPassphrase;
1420
1421	return SecurityAgent::noReason;
1422}
1423
1424QueryInvokeMechanism::QueryInvokeMechanism(const AuthHostType type, Session &session) :
1425    SecurityAgentQuery(type, session) { }
1426
1427void QueryInvokeMechanism::initialize(const string &inPluginId, const string &inMechanismId, const AuthValueVector &inArguments, const SessionId inSessionId)
1428{
1429    if (SecurityAgent::Client::init == SecurityAgent::Client::state())
1430    {
1431        create(inPluginId.c_str(), inMechanismId.c_str(), inSessionId);
1432        mArguments = inArguments;
1433    }
1434}
1435
1436// XXX/cs should return AuthorizationResult
1437void QueryInvokeMechanism::run(const AuthValueVector &inArguments, AuthItemSet &inHints, AuthItemSet &inContext, AuthorizationResult *outResult)
1438{
1439    // prepopulate with client hints
1440	inHints.insert(mClientHints.begin(), mClientHints.end());
1441
1442	if (Server::active().inDarkWake())
1443		CssmError::throwMe(CSSM_ERRCODE_IN_DARK_WAKE);
1444
1445    setArguments(inArguments);
1446    setInput(inHints, inContext);
1447    MacOSError::check(invoke());
1448
1449	if (outResult) *outResult = result();
1450
1451    inHints = outHints();
1452    inContext = outContext();
1453}
1454
1455void QueryInvokeMechanism::terminateAgent()
1456{
1457    terminate();
1458}
1459
1460// @@@  no pluggable authentication possible!
1461Reason
1462QueryKeychainAuth::operator () (const char *database, const char *description, AclAuthorization action, const char *prompt)
1463{
1464    Reason reason = SecurityAgent::noReason;
1465    AuthItemSet hints, context;
1466	AuthValueVector arguments;
1467	int retryCount = 0;
1468	string username;
1469	string password;
1470
1471    using CommonCriteria::Securityd::KeychainAuthLogger;
1472    KeychainAuthLogger logger(mAuditToken, AUE_ssauthint, database, description);
1473
1474#if defined(NOSA)
1475    /* XXX/gh  probably not complete; stolen verbatim from rogue-app query */
1476    if (getenv("NOSA")) {
1477		char answer[maxPassphraseLength+10];
1478
1479        string applicationPath;
1480        AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
1481		if (applicationPathItem)
1482		  applicationPathItem->getString(applicationPath);
1483
1484		getNoSA(answer, sizeof(answer), "Allow %s to do %d on %s in %s? [yn][g]%s ",
1485			applicationPath.c_str(), int(action), (description ? description : "[NULL item]"),
1486			(database ? database : "[NULL database]"),
1487			mPassphraseCheck ? ":passphrase" : "");
1488		// turn passphrase (no ':') into y:passphrase
1489		if (mPassphraseCheck && !strchr(answer, ':')) {
1490			memmove(answer+2, answer, strlen(answer)+1);
1491			memcpy(answer, "y:", 2);
1492		}
1493
1494		allow = answer[0] == 'y';
1495		remember = answer[1] == 'g';
1496		return SecurityAgent::noReason;
1497    }
1498#endif
1499
1500    hints.insert(mClientHints.begin(), mClientHints.end());
1501
1502	// put action/operation (sint32) into hints
1503	hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast<sint32*>(&action))));
1504
1505    hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? (uint32_t)strlen(prompt) : 0, const_cast<char*>(prompt))));
1506
1507	// item name into hints
1508	hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? (uint32_t)strlen(description) : 0, const_cast<char*>(description))));
1509
1510	// keychain name into hints
1511	hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? (uint32_t)strlen(database) : 0, const_cast<char*>(database))));
1512
1513    create("builtin", "confirm-access-user-password", noSecuritySession);
1514
1515    AuthItem *usernameItem;
1516    AuthItem *passwordItem;
1517
1518    do {
1519
1520        AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
1521        hints.erase(triesHint); hints.insert(triesHint); // replace
1522
1523		if (++retryCount > maxTries)
1524			reason = SecurityAgent::tooManyTries;
1525
1526        if (SecurityAgent::noReason != reason)
1527        {
1528            if (SecurityAgent::tooManyTries == reason)
1529                logger.logFailure(NULL,  CommonCriteria::errTooManyTries);
1530            else
1531                logger.logFailure();
1532        }
1533
1534        AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
1535        hints.erase(retryHint); hints.insert(retryHint); // replace
1536
1537        setInput(hints, context);
1538        try
1539        {
1540            invoke();
1541            checkResult();
1542        }
1543        catch (...)     // user probably clicked "deny"
1544        {
1545            logger.logFailure();
1546            throw;
1547        }
1548        usernameItem = mOutContext.find(AGENT_USERNAME);
1549		passwordItem = mOutContext.find(AGENT_PASSWORD);
1550		if (!usernameItem || !passwordItem)
1551			continue;
1552        usernameItem->getString(username);
1553        passwordItem->getString(password);
1554    } while ((reason = accept(username, password)));
1555
1556    if (SecurityAgent::noReason == reason)
1557        logger.logSuccess();
1558    // else we logged the denial in the loop
1559
1560    return reason;
1561}
1562
1563Reason
1564QueryKeychainAuth::accept(string &username, string &passphrase)
1565{
1566    const char *user = username.c_str();
1567    const char *passwd = passphrase.c_str();
1568    int checkpw_status = checkpw(user, passwd);
1569
1570    if (checkpw_status != CHECKPW_SUCCESS)
1571		return SecurityAgent::invalidPassphrase;
1572
1573	return SecurityAgent::noReason;
1574}
1575
1576