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), ¤tTry)); 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), ¤tTry)); 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