]> git.saurik.com Git - apple/security.git/blob - securityd/src/agentquery.cpp
Security-59754.41.1.tar.gz
[apple/security.git] / securityd / src / agentquery.cpp
1 /*
2 * Copyright (c) 2000-2015 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 #define __STDC_WANT_LIB_EXT1__ 1
28 #include <string.h>
29
30 #include "agentquery.h"
31 #include "ccaudit_extensions.h"
32
33 #include <Security/AuthorizationTags.h>
34 #include <Security/AuthorizationTagsPriv.h>
35 #include <Security/checkpw.h>
36 #include <Security/Security.h>
37 #include <System/sys/fileport.h>
38 #include <bsm/audit.h>
39 #include <bsm/audit_uevents.h> // AUE_ssauthint
40 #include <membership.h>
41 #include <membershipPriv.h>
42 #include <security_utilities/logging.h>
43 #include <security_utilities/mach++.h>
44 #include <stdlib.h>
45 #include <xpc/xpc.h>
46 #include <xpc/private.h>
47 #include "securityd_service/securityd_service/securityd_service_client.h"
48
49 // Includes for <rdar://problem/34677969> Always require the user's password on keychain approval dialogs
50 #include "server.h"
51
52 #define SECURITYAGENT_BOOTSTRAP_NAME_BASE "com.apple.security.agent"
53 #define SECURITYAGENT_LOGINWINDOW_BOOTSTRAP_NAME_BASE "com.apple.security.agent.login"
54
55 #define AUTH_XPC_ITEM_NAME "_item_name"
56 #define AUTH_XPC_ITEM_FLAGS "_item_flags"
57 #define AUTH_XPC_ITEM_VALUE "_item_value"
58 #define AUTH_XPC_ITEM_TYPE "_item_type"
59 #define AUTH_XPC_ITEM_SENSITIVE_VALUE_LENGTH "_item_sensitive_value_length"
60
61 #define AUTH_XPC_REQUEST_METHOD_KEY "_agent_request_key"
62 #define AUTH_XPC_REQUEST_METHOD_CREATE "_agent_request_create"
63 #define AUTH_XPC_REQUEST_METHOD_INVOKE "_agent_request_invoke"
64 #define AUTH_XPC_REQUEST_METHOD_DEACTIVATE "_agent_request_deactivate"
65 #define AUTH_XPC_REQUEST_METHOD_DESTROY "_agent_request_destroy"
66 #define AUTH_XPC_REPLY_METHOD_KEY "_agent_reply_key"
67 #define AUTH_XPC_REPLY_METHOD_RESULT "_agent_reply_result"
68 #define AUTH_XPC_REPLY_METHOD_INTERRUPT "_agent_reply_interrupt"
69 #define AUTH_XPC_REPLY_METHOD_CREATE "_agent_reply_create"
70 #define AUTH_XPC_REPLY_METHOD_DEACTIVATE "_agent_reply_deactivate"
71 #define AUTH_XPC_PLUGIN_NAME "_agent_plugin"
72 #define AUTH_XPC_MECHANISM_NAME "_agent_mechanism"
73 #define AUTH_XPC_HINTS_NAME "_agent_hints"
74 #define AUTH_XPC_CONTEXT_NAME "_agent_context"
75 #define AUTH_XPC_IMMUTABLE_HINTS_NAME "_agent_immutable_hints"
76 #define AUTH_XPC_REQUEST_INSTANCE "_agent_instance"
77 #define AUTH_XPC_REPLY_RESULT_VALUE "_agent_reply_result_value"
78 #define AUTH_XPC_AUDIT_SESSION_PORT "_agent_audit_session_port"
79 #define AUTH_XPC_BOOTSTRAP_PORT "_agent_bootstrap_port"
80
81 #define UUID_INITIALIZER_FROM_SESSIONID(sessionid) \
82 { 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))) }
83
84
85 // SecurityAgentXPCConnection
86
87 SecurityAgentXPCConnection::SecurityAgentXPCConnection(Session &session)
88 : mHostInstance(session.authhost()),
89 mSession(session),
90 mConnection(&Server::connection()),
91 mAuditToken(Server::connection().auditToken())
92 {
93 // this may take a while
94 Server::active().longTermActivity();
95 secnotice("SecurityAgentConnection", "new SecurityAgentConnection(%p)", this);
96 mXPCConnection = NULL;
97 mNobodyUID = -2;
98 struct passwd *pw = getpwnam("nobody");
99 if (NULL != pw) {
100 mNobodyUID = pw->pw_uid;
101 }
102 }
103
104 SecurityAgentXPCConnection::~SecurityAgentXPCConnection()
105 {
106 secnotice("SecurityAgentConnection", "SecurityAgentConnection(%p) dying", this);
107 mConnection->useAgent(NULL);
108
109 // If a connection has been established, we need to tear it down.
110 if (NULL != mXPCConnection) {
111 // Tearing this down is a multi-step process. First, request a cancellation.
112 // This is safe even if the connection is already in the canceled state.
113 xpc_connection_cancel(mXPCConnection);
114
115 // Then release the XPC connection
116 xpc_release(mXPCConnection);
117 mXPCConnection = NULL;
118 }
119 }
120
121 bool SecurityAgentXPCConnection::inDarkWake()
122 {
123 return mSession.server().inDarkWake();
124 }
125
126 void
127 SecurityAgentXPCConnection::activate(bool ignoreUid)
128 {
129 secnotice("SecurityAgentConnection", "activate(%p)", this);
130
131 mConnection->useAgent(this);
132 if (mXPCConnection != NULL) {
133 // If we already have an XPC connection, there's nothing to do.
134 return;
135 }
136
137 try {
138 uuid_t sessionUUID = UUID_INITIALIZER_FROM_SESSIONID(mSession.sessionId());
139
140 // Yes, these need to be throws, as we're still in securityd, and thus still have to do flow control with exceptions.
141 if (!(mSession.attributes() & sessionHasGraphicAccess))
142 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
143 if (inDarkWake())
144 CssmError::throwMe(CSSM_ERRCODE_IN_DARK_WAKE);
145 uid_t targetUid = mHostInstance->session().originatorUid();
146
147 secnotice("SecurityAgentXPCConnection","Retrieved UID %d for this session", targetUid);
148 if (!ignoreUid && targetUid != 0 && targetUid != mNobodyUID) {
149 mXPCConnection = xpc_connection_create_mach_service(SECURITYAGENT_BOOTSTRAP_NAME_BASE, NULL, 0);
150 xpc_connection_set_target_uid(mXPCConnection, targetUid);
151 secnotice("SecurityAgentXPCConnection", "Creating a standard security agent");
152 } else {
153 mXPCConnection = xpc_connection_create_mach_service(SECURITYAGENT_LOGINWINDOW_BOOTSTRAP_NAME_BASE, NULL,
154 XPC_CONNECTION_MACH_SERVICE_PRIVILEGED);
155 xpc_connection_set_instance(mXPCConnection, sessionUUID);
156 secnotice("SecurityAgentXPCConnection", "Creating a loginwindow security agent");
157 }
158
159 xpc_connection_set_event_handler(mXPCConnection, ^(xpc_object_t object) {
160 if (xpc_get_type(object) == XPC_TYPE_ERROR) {
161 secnotice("SecurityAgentXPCConnection", "error during xpc: %s", xpc_dictionary_get_string(object, XPC_ERROR_KEY_DESCRIPTION));
162 }
163 });
164 xpc_connection_resume(mXPCConnection);
165 secnotice("SecurityAgentXPCConnection", "%p activated", this);
166 }
167 catch (MacOSError &err) {
168 mConnection->useAgent(NULL); // guess not
169 Syslog::error("SecurityAgentConnection: error activating SecurityAgent instance %p", this);
170 throw;
171 }
172
173 secnotice("SecurityAgentXPCConnection", "contact didn't throw (%p)", this);
174 }
175
176 void
177 SecurityAgentXPCConnection::terminate()
178 {
179 activate(false);
180
181 // @@@ This happens already in the destructor; presumably we do this to tear things down orderly
182 mConnection->useAgent(NULL);
183 }
184
185
186 using SecurityAgent::Reason;
187 using namespace Authorization;
188
189 ModuleNexus<RecursiveMutex> gAllXPCClientsMutex;
190 ModuleNexus<set<SecurityAgentXPCQuery*> > allXPCClients;
191
192 void
193 SecurityAgentXPCQuery::killAllXPCClients()
194 {
195 // grab the lock for the client list -- we need to make sure no one modifies the structure while we are iterating it.
196 StLock<Mutex> _(gAllXPCClientsMutex());
197
198 set<SecurityAgentXPCQuery*>::iterator clientIterator = allXPCClients().begin();
199 while (clientIterator != allXPCClients().end())
200 {
201 set<SecurityAgentXPCQuery*>::iterator thisClient = clientIterator++;
202 if ((*thisClient)->getTerminateOnSleep())
203 {
204 (*thisClient)->terminate();
205 }
206 }
207 }
208
209
210 SecurityAgentXPCQuery::SecurityAgentXPCQuery(Session &session)
211 : SecurityAgentXPCConnection(session), mAgentConnected(false), mTerminateOnSleep(false)
212 {
213 secnotice("SecurityAgentXPCQuery", "new SecurityAgentXPCQuery(%p)", this);
214 }
215
216 SecurityAgentXPCQuery::~SecurityAgentXPCQuery()
217 {
218 secnotice("SecurityAgentXPCQuery", "SecurityAgentXPCQuery(%p) dying", this);
219 if (mAgentConnected) {
220 this->disconnect();
221 }
222 }
223
224 void
225 SecurityAgentXPCQuery::inferHints(Process &thisProcess)
226 {
227 AuthItemSet clientHints;
228 SecurityAgent::RequestorType type = SecurityAgent::bundle;
229 pid_t clientPid = thisProcess.pid();
230 uid_t clientUid = thisProcess.uid();
231 string guestPath = thisProcess.getPath();
232 Boolean ignoreSession = TRUE;
233
234 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_TYPE, AuthValueOverlay(sizeof(type), &type)));
235 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PATH, AuthValueOverlay(guestPath)));
236 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PID, AuthValueOverlay(sizeof(clientPid), &clientPid)));
237 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_UID, AuthValueOverlay(sizeof(clientUid), &clientUid)));
238
239 /*
240 * If its loginwindow that's asking, override the loginwindow shield detection
241 * up front so that it can trigger SecurityAgent dialogs (like password change)
242 * for when the OD password and keychain password is out of sync.
243 */
244
245 if (guestPath == "/System/Library/CoreServices/loginwindow.app") {
246 clientHints.insert(AuthItemRef(AGENT_HINT_IGNORE_SESSION, AuthValueOverlay(sizeof(ignoreSession), &ignoreSession)));
247 }
248
249 mClientHints.insert(clientHints.begin(), clientHints.end());
250
251 bool validSignature = thisProcess.checkAppleSigned();
252 AuthItemSet clientImmutableHints;
253
254 clientImmutableHints.insert(AuthItemRef(AGENT_HINT_CLIENT_SIGNED, AuthValueOverlay(sizeof(validSignature), &validSignature)));
255
256 mImmutableHints.insert(clientImmutableHints.begin(), clientImmutableHints.end());
257 }
258
259 void SecurityAgentXPCQuery::addHint(const char *name, const void *value, UInt32 valueLen, UInt32 flags)
260 {
261 AuthorizationItem item = { name, valueLen, const_cast<void *>(value), flags };
262 mClientHints.insert(AuthItemRef(item));
263 }
264
265
266 void
267 SecurityAgentXPCQuery::readChoice()
268 {
269 allow = false;
270 remember = false;
271
272 AuthItem *allowAction = mOutContext.find(AGENT_CONTEXT_ALLOW);
273 if (allowAction)
274 {
275 string allowString;
276 if (allowAction->getString(allowString)
277 && (allowString == "YES"))
278 allow = true;
279 }
280
281 AuthItem *rememberAction = mOutContext.find(AGENT_CONTEXT_REMEMBER_ACTION);
282 if (rememberAction)
283 {
284 string rememberString;
285 if (rememberAction->getString(rememberString)
286 && (rememberString == "YES"))
287 remember = true;
288 }
289 }
290
291 void
292 SecurityAgentXPCQuery::disconnect()
293 {
294 if (NULL != mXPCConnection) {
295 xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
296 xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_DESTROY);
297 xpc_connection_send_message(mXPCConnection, requestObject);
298 xpc_release(requestObject);
299 }
300
301 StLock<Mutex> _(gAllXPCClientsMutex());
302 allXPCClients().erase(this);
303 }
304
305 void
306 SecurityAgentXPCQuery::terminate()
307 {
308 this->disconnect();
309 }
310
311 static void xpcArrayToAuthItemSet(AuthItemSet *setToBuild, xpc_object_t input) {
312 setToBuild->clear();
313
314 xpc_array_apply(input, ^bool(size_t index, xpc_object_t item) {
315 const char *name = xpc_dictionary_get_string(item, AUTH_XPC_ITEM_NAME);
316
317 size_t length;
318 const void *data = xpc_dictionary_get_data(item, AUTH_XPC_ITEM_VALUE, &length);
319 void *dataCopy = 0;
320
321 // <rdar://problem/13033889> authd is holding on to multiple copies of my password in the clear
322 bool sensitive = xpc_dictionary_get_value(item, AUTH_XPC_ITEM_SENSITIVE_VALUE_LENGTH);
323 if (sensitive) {
324 size_t sensitiveLength = (size_t)xpc_dictionary_get_uint64(item, AUTH_XPC_ITEM_SENSITIVE_VALUE_LENGTH);
325 if (sensitiveLength > length) {
326 secnotice("SecurityAgentXPCQuery", "Sensitive data len %zu is not valid", sensitiveLength);
327 return true;
328 }
329 dataCopy = malloc(sensitiveLength);
330 memcpy(dataCopy, data, sensitiveLength);
331 memset_s((void *)data, length, 0, sensitiveLength); // clear the sensitive data, memset_s is never optimized away
332 length = sensitiveLength;
333 } else {
334 dataCopy = malloc(length);
335 memcpy(dataCopy, data, length);
336 }
337
338 uint64_t flags = xpc_dictionary_get_uint64(item, AUTH_XPC_ITEM_FLAGS);
339 AuthItemRef nextItem(name, AuthValueOverlay((uint32_t)length, dataCopy), (uint32_t)flags);
340 setToBuild->insert(nextItem);
341 memset(dataCopy, 0, length); // The authorization items contain things like passwords, so wiping clean is important.
342 free(dataCopy);
343 return true;
344 });
345 }
346
347 void
348 SecurityAgentXPCQuery::create(const char *pluginId, const char *mechanismId)
349 {
350 bool ignoreUid = false;
351
352 do {
353 activate(ignoreUid);
354
355 mAgentConnected = false;
356
357 xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
358 xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_CREATE);
359 xpc_dictionary_set_string(requestObject, AUTH_XPC_PLUGIN_NAME, pluginId);
360 xpc_dictionary_set_string(requestObject, AUTH_XPC_MECHANISM_NAME, mechanismId);
361
362 uid_t targetUid = Server::process().uid();
363 bool doSwitchAudit = (ignoreUid || targetUid == 0 || targetUid == mNobodyUID);
364 bool doSwitchBootstrap = (ignoreUid || targetUid == 0 || targetUid == mNobodyUID);
365
366 if (doSwitchAudit) {
367 mach_port_name_t jobPort;
368 if (0 == audit_session_port(mSession.sessionId(), &jobPort)) {
369 secnotice("SecurityAgentXPCQuery", "attaching an audit session port because the uid was %d", targetUid);
370 xpc_dictionary_set_mach_send(requestObject, AUTH_XPC_AUDIT_SESSION_PORT, jobPort);
371 if (mach_port_mod_refs(mach_task_self(), jobPort, MACH_PORT_RIGHT_SEND, -1) != KERN_SUCCESS) {
372 secnotice("SecurityAgentXPCQuery", "unable to release send right for audit session, leaking");
373 }
374 }
375 }
376
377 if (doSwitchBootstrap) {
378 secnotice("SecurityAgentXPCQuery", "attaching a bootstrap port because the uid was %d", targetUid);
379 MachPlusPlus::Bootstrap processBootstrap = Server::process().taskPort().bootstrap();
380 xpc_dictionary_set_mach_send(requestObject, AUTH_XPC_BOOTSTRAP_PORT, processBootstrap);
381 }
382
383 xpc_object_t object = xpc_connection_send_message_with_reply_sync(mXPCConnection, requestObject);
384 if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) {
385 const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY);
386 if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_CREATE)) {
387 uint64_t status = xpc_dictionary_get_uint64(object, AUTH_XPC_REPLY_RESULT_VALUE);
388 if (status == kAuthorizationResultAllow) {
389 mAgentConnected = true;
390 } else {
391 secnotice("SecurityAgentXPCQuery", "plugin create failed in SecurityAgent");
392 MacOSError::throwMe(errAuthorizationInternal);
393 }
394 }
395 } else if (xpc_get_type(object) == XPC_TYPE_ERROR) {
396 if (XPC_ERROR_CONNECTION_INVALID == object) {
397 // If we get an error before getting the create response, try again without the UID
398 if (ignoreUid) {
399 secnotice("SecurityAgentXPCQuery", "failed to establish connection, no retries left");
400 xpc_release(object);
401 MacOSError::throwMe(errAuthorizationInternal);
402 } else {
403 secnotice("SecurityAgentXPCQuery", "failed to establish connection, retrying with no UID");
404 ignoreUid = true;
405 xpc_release(mXPCConnection);
406 mXPCConnection = NULL;
407 }
408 } else if (XPC_ERROR_CONNECTION_INTERRUPTED == object) {
409 // If we get an error before getting the create response, try again
410 }
411 }
412 xpc_release(object);
413 xpc_release(requestObject);
414 } while (!mAgentConnected);
415
416 StLock<Mutex> _(gAllXPCClientsMutex());
417 allXPCClients().insert(this);
418 }
419
420 static xpc_object_t authItemSetToXPCArray(AuthItemSet input) {
421 xpc_object_t outputArray = xpc_array_create(NULL, 0);
422 for (AuthItemSet::iterator i = input.begin(); i != input.end(); i++) {
423 AuthItemRef item = *i;
424
425 xpc_object_t xpc_data = xpc_dictionary_create(NULL, NULL, 0);
426 xpc_dictionary_set_string(xpc_data, AUTH_XPC_ITEM_NAME, item->name());
427 AuthorizationValue value = item->value();
428 if (value.data != NULL) {
429 xpc_dictionary_set_data(xpc_data, AUTH_XPC_ITEM_VALUE, value.data, value.length);
430 }
431 xpc_dictionary_set_uint64(xpc_data, AUTH_XPC_ITEM_FLAGS, item->flags());
432 xpc_array_append_value(outputArray, xpc_data);
433 xpc_release(xpc_data);
434 }
435 return outputArray;
436 }
437
438 void
439 SecurityAgentXPCQuery::invoke() {
440 xpc_object_t hintsArray = authItemSetToXPCArray(mInHints);
441 xpc_object_t contextArray = authItemSetToXPCArray(mInContext);
442 xpc_object_t immutableHintsArray = authItemSetToXPCArray(mImmutableHints);
443
444 xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
445 xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_INVOKE);
446 xpc_dictionary_set_value(requestObject, AUTH_XPC_HINTS_NAME, hintsArray);
447 xpc_dictionary_set_value(requestObject, AUTH_XPC_CONTEXT_NAME, contextArray);
448 xpc_dictionary_set_value(requestObject, AUTH_XPC_IMMUTABLE_HINTS_NAME, immutableHintsArray);
449
450 xpc_object_t object = xpc_connection_send_message_with_reply_sync(mXPCConnection, requestObject);
451 if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) {
452 const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY);
453 if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_RESULT)) {
454 xpc_object_t xpcHints = xpc_dictionary_get_value(object, AUTH_XPC_HINTS_NAME);
455 xpc_object_t xpcContext = xpc_dictionary_get_value(object, AUTH_XPC_CONTEXT_NAME);
456 AuthItemSet tempHints, tempContext;
457 xpcArrayToAuthItemSet(&tempHints, xpcHints);
458 xpcArrayToAuthItemSet(&tempContext, xpcContext);
459 mOutHints = tempHints;
460 mOutContext = tempContext;
461 mLastResult = xpc_dictionary_get_uint64(object, AUTH_XPC_REPLY_RESULT_VALUE);
462 }
463 } else if (xpc_get_type(object) == XPC_TYPE_ERROR) {
464 if (XPC_ERROR_CONNECTION_INVALID == object) {
465 // If the connection drops, return an "auth undefined" result, because we cannot continue
466 } else if (XPC_ERROR_CONNECTION_INTERRUPTED == object) {
467 // If the agent dies, return an "auth undefined" result, because we cannot continue
468 }
469 }
470 xpc_release(object);
471
472 xpc_release(hintsArray);
473 xpc_release(contextArray);
474 xpc_release(immutableHintsArray);
475 xpc_release(requestObject);
476 }
477
478 void SecurityAgentXPCQuery::checkResult()
479 {
480 // now check the OSStatus return from the server side
481 switch (mLastResult) {
482 case kAuthorizationResultAllow: return;
483 case kAuthorizationResultDeny:
484 case kAuthorizationResultUserCanceled: CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
485 default: MacOSError::throwMe(errAuthorizationInternal);
486 }
487 }
488
489 //
490 // Perform the "rogue app" access query dialog
491 //
492 QueryKeychainUse::QueryKeychainUse(bool needPass, const Database *db)
493 : mPassphraseCheck(NULL)
494 {
495 // if passphrase checking requested, save KeychainDatabase reference
496 // (will quietly disable check if db isn't a keychain)
497
498 // Always require password due to <rdar://problem/34677969>
499 mPassphraseCheck = dynamic_cast<const KeychainDatabase *>(db);
500
501 setTerminateOnSleep(true);
502 }
503
504 // Callers to this function must hold the common lock
505 Reason QueryKeychainUse::queryUser (const char *database, const char *description, AclAuthorization action)
506 {
507 Reason reason = SecurityAgent::noReason;
508 uint32_t retryCount = 0;
509 AuthItemSet hints, context;
510
511 // prepopulate with client hints
512 hints.insert(mClientHints.begin(), mClientHints.end());
513
514 // put action/operation (sint32) into hints
515 hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast<sint32*>(&action))));
516
517 // item name into hints
518
519 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? (uint32_t)strlen(description) : 0, const_cast<char*>(description))));
520
521 // keychain name into hints
522 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? (uint32_t)strlen(database) : 0, const_cast<char*>(database))));
523
524 if (mPassphraseCheck)
525 {
526 create("builtin", "confirm-access-password");
527
528 CssmAutoData data(Allocator::standard(Allocator::sensitive));
529
530 do
531 {
532
533 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
534 hints.erase(triesHint); hints.insert(triesHint); // replace
535
536 if (retryCount++ > kMaximumAuthorizationTries)
537 {
538 reason = SecurityAgent::tooManyTries;
539 }
540
541 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
542 hints.erase(retryHint); hints.insert(retryHint); // replace
543
544 setInput(hints, context);
545
546 {
547 // Must drop the common lock while showing UI.
548 StSyncLock<Mutex, Mutex> syncLock(const_cast<KeychainDatabase*>(mPassphraseCheck)->common().uiLock(), const_cast<KeychainDatabase*>(mPassphraseCheck)->common());
549 invoke();
550 }
551
552 if (retryCount > kMaximumAuthorizationTries)
553 {
554 return reason;
555 }
556
557 checkResult();
558
559 AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
560 if (!passwordItem)
561 continue;
562
563 passwordItem->getCssmData(data);
564
565 // decode() replaces the master key, so do this only if we know the passphrase is correct.
566 // I suspect decode() is redundant but something might rely on its side effects so let's keep it.
567 if (const_cast<KeychainDatabase*>(mPassphraseCheck)->validatePassphrase(data) && const_cast<KeychainDatabase*>(mPassphraseCheck)->decode(data)) {
568 reason = SecurityAgent::noReason;
569 } else {
570 reason = SecurityAgent::invalidPassphrase;
571 }
572 }
573 while (reason != SecurityAgent::noReason);
574
575 readChoice();
576 }
577 else
578 {
579 // create("builtin", "confirm-access");
580 // setInput(hints, context);
581 // invoke();
582
583 // This is a hack to support <rdar://problem/34677969>, we can never simply prompt for confirmation
584 secerror("ACL validation fallback case! Must ask user for account password because we have no database");
585 Session &session = Server::session();
586 try{
587 session.verifyKeyStorePassphrase(1, true, description);
588 } catch (...) {
589 return SecurityAgent::invalidPassphrase;
590 }
591 SecurityAgentXPCQuery::allow = true;
592 }
593
594 return reason;
595 }
596
597 //
598 // Obtain passphrases and submit them to the accept() method until it is accepted
599 // or we can't get another passphrase. Accept() should consume the passphrase
600 // if it is accepted. If no passphrase is acceptable, throw out of here.
601 //
602 Reason QueryOld::query()
603 {
604 Reason reason = SecurityAgent::noReason;
605 AuthItemSet hints, context;
606 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
607 int retryCount = 0;
608
609 // prepopulate with client hints
610
611 const char *keychainPath = database.dbName();
612 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
613
614 hints.insert(mClientHints.begin(), mClientHints.end());
615
616 create("builtin", "unlock-keychain");
617
618 do
619 {
620 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
621 hints.erase(triesHint); hints.insert(triesHint); // replace
622
623 ++retryCount;
624
625 if (retryCount > maxTries)
626 {
627 reason = SecurityAgent::tooManyTries;
628 }
629
630 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
631 hints.erase(retryHint); hints.insert(retryHint); // replace
632
633 setInput(hints, context);
634 invoke();
635
636 if (retryCount > maxTries)
637 {
638 return reason;
639 }
640
641 checkResult();
642
643 AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
644 if (!passwordItem)
645 continue;
646
647 passwordItem->getCssmData(passphrase);
648
649 }
650 while ((reason = accept(passphrase)));
651
652 return SecurityAgent::noReason;
653 }
654
655
656 //
657 // Get existing passphrase (unlock) Query
658 //
659 Reason QueryOld::operator () ()
660 {
661 return query();
662 }
663
664
665 //
666 // End-classes for old secrets
667 //
668 Reason QueryUnlock::accept(CssmManagedData &passphrase)
669 {
670 // Must hold the 'common' lock to call decode; otherwise there's a data corruption issue
671 StLock<Mutex> _(safer_cast<KeychainDatabase &>(database).common());
672
673 // Calling validatePassphrase here throws when trying to constitute a key.
674 // Unsure why but since this is for the KC unlock path and not a validation path the wrong password won't make things worse.
675 if (safer_cast<KeychainDatabase &>(database).decode(passphrase))
676 return SecurityAgent::noReason;
677 else
678 return SecurityAgent::invalidPassphrase;
679 }
680
681 Reason QueryUnlock::retrievePassword(CssmOwnedData &passphrase) {
682 CssmAutoData pass(Allocator::standard(Allocator::sensitive));
683
684 AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
685 if (!passwordItem)
686 return SecurityAgent::invalidPassphrase;
687
688 passwordItem->getCssmData(pass);
689
690 passphrase = pass;
691
692 return SecurityAgent::noReason;
693 }
694
695 QueryKeybagPassphrase::QueryKeybagPassphrase(Session & session, int32_t tries) : mSession(session), mContext(), mRetries(tries)
696 {
697 setTerminateOnSleep(true);
698 mContext = mSession.get_current_service_context();
699 }
700
701 Reason QueryKeybagPassphrase::query()
702 {
703 Reason reason = SecurityAgent::noReason;
704 AuthItemSet hints, context;
705 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
706 int retryCount = 0;
707
708 // prepopulate with client hints
709
710 const char *keychainPath = "iCloud";
711 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
712
713 hints.insert(mClientHints.begin(), mClientHints.end());
714
715 create("builtin", "unlock-keychain");
716
717 int currentTry = 0;
718 do
719 {
720 currentTry = retryCount;
721 if (retryCount > mRetries)
722 {
723 return SecurityAgent::tooManyTries;
724 }
725 retryCount++;
726
727 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(currentTry), &currentTry));
728 hints.erase(triesHint); hints.insert(triesHint); // replace
729
730 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
731 hints.erase(retryHint); hints.insert(retryHint); // replace
732
733 setInput(hints, context);
734 invoke();
735
736 checkResult();
737
738 AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
739 if (!passwordItem)
740 continue;
741
742 passwordItem->getCssmData(passphrase);
743 }
744 while ((reason = accept(passphrase)));
745
746 return SecurityAgent::noReason;
747 }
748
749 Reason QueryKeybagPassphrase::accept(Security::CssmManagedData & password)
750 {
751 if (service_client_kb_unlock(&mContext, password.data(), (int)password.length()) == 0) {
752 mSession.keybagSetState(session_keybag_unlocked);
753 return SecurityAgent::noReason;
754 } else
755 return SecurityAgent::invalidPassphrase;
756 }
757
758 QueryKeybagNewPassphrase::QueryKeybagNewPassphrase(Session & session) : QueryKeybagPassphrase(session) {}
759
760 Reason QueryKeybagNewPassphrase::query(CssmOwnedData &oldPassphrase, CssmOwnedData &passphrase)
761 {
762 CssmAutoData pass(Allocator::standard(Allocator::sensitive));
763 CssmAutoData oldPass(Allocator::standard(Allocator::sensitive));
764 Reason reason = SecurityAgent::noReason;
765 AuthItemSet hints, context;
766 int retryCount = 0;
767
768 // prepopulate with client hints
769
770 const char *keychainPath = "iCloud";
771 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
772
773 const char *showResetString = "YES";
774 hints.insert(AuthItemRef(AGENT_HINT_SHOW_RESET, AuthValueOverlay((uint32_t)strlen(showResetString), const_cast<char*>(showResetString))));
775
776 hints.insert(mClientHints.begin(), mClientHints.end());
777
778 create("builtin", "change-passphrase");
779
780 int currentTry = 0;
781 AuthItem *resetPassword = NULL;
782 do
783 {
784 currentTry = retryCount;
785 if (retryCount > mRetries)
786 {
787 return SecurityAgent::tooManyTries;
788 }
789 retryCount++;
790
791 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(currentTry), &currentTry));
792 hints.erase(triesHint); hints.insert(triesHint); // replace
793
794 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
795 hints.erase(retryHint); hints.insert(retryHint); // replace
796
797 setInput(hints, context);
798 invoke();
799
800 checkResult();
801
802 resetPassword = mOutContext.find(AGENT_CONTEXT_RESET_PASSWORD);
803 if (resetPassword != NULL) {
804 return SecurityAgent::resettingPassword;
805 }
806
807 AuthItem *oldPasswordItem = mOutContext.find(AGENT_PASSWORD);
808 if (!oldPasswordItem)
809 continue;
810
811 oldPasswordItem->getCssmData(oldPass);
812 }
813 while ((reason = accept(oldPass)));
814
815 if (reason == SecurityAgent::noReason) {
816 AuthItem *passwordItem = mOutContext.find(AGENT_CONTEXT_NEW_PASSWORD);
817 if (!passwordItem)
818 return SecurityAgent::invalidPassphrase;
819
820 passwordItem->getCssmData(pass);
821
822 oldPassphrase = oldPass;
823 passphrase = pass;
824 }
825
826 return SecurityAgent::noReason;
827 }
828
829 QueryPIN::QueryPIN(Database &db)
830 : QueryOld(db), mPin(Allocator::standard())
831 {
832 this->inferHints(Server::process());
833 }
834
835
836 Reason QueryPIN::accept(CssmManagedData &pin)
837 {
838 // no retries for now
839 mPin = pin;
840 return SecurityAgent::noReason;
841 }
842
843
844 //
845 // Obtain passphrases and submit them to the accept() method until it is accepted
846 // or we can't get another passphrase. Accept() should consume the passphrase
847 // if it is accepted. If no passphrase is acceptable, throw out of here.
848 //
849 Reason QueryNewPassphrase::query()
850 {
851 Reason reason = initialReason;
852 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
853 CssmAutoData oldPassphrase(Allocator::standard(Allocator::sensitive));
854
855 AuthItemSet hints, context;
856
857 int retryCount = 0;
858
859 // prepopulate with client hints
860 hints.insert(mClientHints.begin(), mClientHints.end());
861
862 // keychain name into hints
863 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database.dbName())));
864
865 switch (initialReason)
866 {
867 case SecurityAgent::newDatabase:
868 create("builtin", "new-passphrase");
869 break;
870 case SecurityAgent::changePassphrase:
871 create("builtin", "change-passphrase");
872 break;
873 default:
874 assert(false);
875 }
876
877 do
878 {
879 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
880 hints.erase(triesHint); hints.insert(triesHint); // replace
881
882 if (++retryCount > maxTries)
883 {
884 reason = SecurityAgent::tooManyTries;
885 }
886
887 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
888 hints.erase(retryHint); hints.insert(retryHint); // replace
889
890 setInput(hints, context);
891 invoke();
892
893 if (retryCount > maxTries)
894 {
895 return reason;
896 }
897
898 checkResult();
899
900 if (SecurityAgent::changePassphrase == initialReason)
901 {
902 AuthItem *oldPasswordItem = mOutContext.find(AGENT_PASSWORD);
903 if (!oldPasswordItem)
904 continue;
905
906 oldPasswordItem->getCssmData(oldPassphrase);
907 }
908
909 AuthItem *passwordItem = mOutContext.find(AGENT_CONTEXT_NEW_PASSWORD);
910 if (!passwordItem)
911 continue;
912
913 passwordItem->getCssmData(passphrase);
914
915 }
916 while ((reason = accept(passphrase, (initialReason == SecurityAgent::changePassphrase) ? &oldPassphrase.get() : NULL)));
917
918 return SecurityAgent::noReason;
919 }
920
921
922 //
923 // Get new passphrase Query
924 //
925 Reason QueryNewPassphrase::operator () (CssmOwnedData &oldPassphrase, CssmOwnedData &passphrase)
926 {
927 if (Reason result = query())
928 return result; // failed
929 passphrase = mPassphrase;
930 oldPassphrase = mOldPassphrase;
931 return SecurityAgent::noReason; // success
932 }
933
934 Reason QueryNewPassphrase::accept(CssmManagedData &passphrase, CssmData *oldPassphrase)
935 {
936 //@@@ acceptance criteria are currently hardwired here
937 //@@@ This validation presumes ASCII - UTF8 might be more lenient
938
939 // if we have an old passphrase, check it
940 if (oldPassphrase && !safer_cast<KeychainDatabase&>(database).validatePassphrase(*oldPassphrase))
941 return SecurityAgent::oldPassphraseWrong;
942
943 // sanity check the new passphrase (but allow user override)
944 if (!(mPassphraseValid && passphrase.get() == mPassphrase)) {
945 mPassphrase = passphrase;
946 if (oldPassphrase) mOldPassphrase = *oldPassphrase;
947 mPassphraseValid = true;
948 if (mPassphrase.length() == 0)
949 return SecurityAgent::passphraseIsNull;
950 if (mPassphrase.length() < 6)
951 return SecurityAgent::passphraseTooSimple;
952 }
953
954 // accept this
955 return SecurityAgent::noReason;
956 }
957
958 //
959 // Get a passphrase for unspecified use
960 //
961 Reason QueryGenericPassphrase::operator () (const CssmData *prompt, bool verify,
962 string &passphrase)
963 {
964 return query(prompt, verify, passphrase);
965 }
966
967 Reason QueryGenericPassphrase::query(const CssmData *prompt, bool verify,
968 string &passphrase)
969 {
970 Reason reason = SecurityAgent::noReason;
971 AuthItemSet hints, context;
972
973 hints.insert(mClientHints.begin(), mClientHints.end());
974 hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? (UInt32)prompt->length() : 0, prompt ? prompt->data() : NULL)));
975 // XXX/gh defined by dmitch but no analogous hint in
976 // AuthorizationTagsPriv.h:
977 // CSSM_ATTRIBUTE_ALERT_TITLE (optional alert panel title)
978
979 if (false == verify) { // import
980 create("builtin", "generic-unlock");
981 } else { // verify passphrase (export)
982 create("builtin", "generic-new-passphrase");
983 }
984
985 AuthItem *passwordItem;
986
987 do {
988 setInput(hints, context);
989 invoke();
990 checkResult();
991 passwordItem = mOutContext.find(AGENT_PASSWORD);
992
993 } while (!passwordItem);
994
995 passwordItem->getString(passphrase);
996
997 return reason;
998 }
999
1000
1001 //
1002 // Get a DB blob's passphrase--keychain synchronization
1003 //
1004 Reason QueryDBBlobSecret::operator () (DbHandle *dbHandleArray, uint8 dbHandleArrayCount, DbHandle *dbHandleAuthenticated)
1005 {
1006 return query(dbHandleArray, dbHandleArrayCount, dbHandleAuthenticated);
1007 }
1008
1009 Reason QueryDBBlobSecret::query(DbHandle *dbHandleArray, uint8 dbHandleArrayCount, DbHandle *dbHandleAuthenticated)
1010 {
1011 Reason reason = SecurityAgent::noReason;
1012 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
1013 AuthItemSet hints/*NUKEME*/, context;
1014
1015 hints.insert(mClientHints.begin(), mClientHints.end());
1016 create("builtin", "generic-unlock-kcblob");
1017
1018 AuthItem *secretItem;
1019
1020 int retryCount = 0;
1021
1022 do {
1023 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
1024 hints.erase(triesHint); hints.insert(triesHint); // replace
1025
1026 if (++retryCount > maxTries)
1027 {
1028 reason = SecurityAgent::tooManyTries;
1029 }
1030
1031 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
1032 hints.erase(retryHint); hints.insert(retryHint); // replace
1033
1034 setInput(hints, context);
1035 invoke();
1036 checkResult();
1037 secretItem = mOutContext.find(AGENT_PASSWORD);
1038 if (!secretItem)
1039 continue;
1040 secretItem->getCssmData(passphrase);
1041
1042 } while ((reason = accept(passphrase, dbHandleArray, dbHandleArrayCount, dbHandleAuthenticated)));
1043
1044 return reason;
1045 }
1046
1047 Reason QueryDBBlobSecret::accept(CssmManagedData &passphrase,
1048 DbHandle *dbHandlesToAuthenticate, uint8 dbHandleCount, DbHandle *dbHandleAuthenticated)
1049 {
1050 DbHandle *currHdl = dbHandlesToAuthenticate;
1051 short index;
1052 Boolean authenticated = false;
1053 for (index=0; index < dbHandleCount && !authenticated; index++)
1054 {
1055 try
1056 {
1057 RefPointer<KeychainDatabase> dbToUnlock = Server::keychain(*currHdl);
1058 dbToUnlock->unlockDb(passphrase, false);
1059 authenticated = true;
1060 *dbHandleAuthenticated = *currHdl; // return the DbHandle that 'passphrase' authenticated with.
1061 }
1062 catch (const CommonError &err)
1063 {
1064 currHdl++; // we failed to authenticate with this one, onto the next one.
1065 }
1066 }
1067 if ( !authenticated )
1068 return SecurityAgent::invalidPassphrase;
1069
1070 return SecurityAgent::noReason;
1071 }
1072
1073 // @@@ no pluggable authentication possible!
1074 Reason
1075 QueryKeychainAuth::performQuery(const KeychainDatabase& db, const char *description, AclAuthorization action, const char *prompt)
1076 {
1077 Reason reason = SecurityAgent::noReason;
1078 AuthItemSet hints, context;
1079 int retryCount = 0;
1080 string username;
1081 string password;
1082
1083 using CommonCriteria::Securityd::KeychainAuthLogger;
1084 KeychainAuthLogger logger(mAuditToken, (short)AUE_ssauthint, db.dbName(), description);
1085
1086 hints.insert(mClientHints.begin(), mClientHints.end());
1087
1088 // put action/operation (sint32) into hints
1089 hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast<sint32*>(&action))));
1090
1091 hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? (uint32_t)strlen(prompt) : 0, const_cast<char*>(prompt))));
1092
1093 // item name into hints
1094 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? (uint32_t)strlen(description) : 0, const_cast<char*>(description))));
1095
1096 // keychain name into hints
1097 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(db.dbName() ? (uint32_t)strlen(db.dbName()) : 0, const_cast<char*>(db.dbName()))));
1098
1099 create("builtin", "confirm-access-user-password");
1100
1101 AuthItem *usernameItem;
1102 AuthItem *passwordItem;
1103
1104 // This entire do..while requires the UI lock because we do accept() in the condition, which in some cases is reentrant
1105 StSyncLock<Mutex, Mutex> syncLock(db.common().uiLock(), db.common());
1106 do {
1107 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
1108 hints.erase(triesHint); hints.insert(triesHint); // replace
1109
1110 if (++retryCount > maxTries)
1111 reason = SecurityAgent::tooManyTries;
1112
1113 if (SecurityAgent::noReason != reason)
1114 {
1115 if (SecurityAgent::tooManyTries == reason)
1116 logger.logFailure(NULL, CommonCriteria::errTooManyTries);
1117 else
1118 logger.logFailure();
1119 }
1120
1121 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
1122 hints.erase(retryHint); hints.insert(retryHint); // replace
1123
1124 setInput(hints, context);
1125 try
1126 {
1127 invoke();
1128 checkResult();
1129 }
1130 catch (...) // user probably clicked "deny"
1131 {
1132 logger.logFailure();
1133 throw;
1134 }
1135 usernameItem = mOutContext.find(AGENT_USERNAME);
1136 passwordItem = mOutContext.find(AGENT_PASSWORD);
1137 if (!usernameItem || !passwordItem)
1138 continue;
1139 usernameItem->getString(username);
1140 passwordItem->getString(password);
1141 } while ((reason = accept(username, password)));
1142 syncLock.unlock();
1143
1144 if (SecurityAgent::noReason == reason)
1145 logger.logSuccess();
1146 // else we logged the denial in the loop
1147
1148 return reason;
1149 }
1150
1151 Reason
1152 QueryKeychainAuth::accept(string &username, string &passphrase)
1153 {
1154 // Note: QueryKeychainAuth currently requires that the
1155 // specified user be in the admin group. If this requirement
1156 // ever needs to change, the group name should be passed as
1157 // a separate argument to this method.
1158
1159 const char *user = username.c_str();
1160 const char *passwd = passphrase.c_str();
1161 int checkpw_status = checkpw(user, passwd);
1162
1163 if (checkpw_status != CHECKPW_SUCCESS) {
1164 return SecurityAgent::invalidPassphrase;
1165 }
1166
1167 const char *group = "admin";
1168 if (group) {
1169 int rc, ismember;
1170 uuid_t group_uuid, user_uuid;
1171 rc = mbr_group_name_to_uuid(group, group_uuid);
1172 if (rc) { return SecurityAgent::userNotInGroup; }
1173
1174 rc = mbr_user_name_to_uuid(user, user_uuid);
1175 if (rc) { return SecurityAgent::userNotInGroup; }
1176
1177 rc = mbr_check_membership(user_uuid, group_uuid, &ismember);
1178 if (rc || !ismember) { return SecurityAgent::userNotInGroup; }
1179 }
1180
1181 return SecurityAgent::noReason;
1182 }
1183