]> git.saurik.com Git - apple/securityd.git/blob - src/agentquery.cpp
securityd-30557.tar.gz
[apple/securityd.git] / src / agentquery.cpp
1 /*
2 * Copyright (c) 2000-2004 Apple Computer, 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
30 #include <Security/AuthorizationTags.h>
31 #include <Security/AuthorizationTagsPriv.h>
32
33 //
34 // NOSA support functions. This is a test mode where the SecurityAgent
35 // is simulated via stdio in the client. Good for running automated tests
36 // of client programs. Only available if -DNOSA when compiling.
37 //
38 #if defined(NOSA)
39
40 #include <cstdarg>
41
42 static void getNoSA(char *buffer, size_t bufferSize, const char *fmt, ...)
43 {
44 // write prompt
45 va_list args;
46 va_start(args, fmt);
47 vfprintf(stdout, fmt, args);
48 va_end(args);
49
50 // read reply
51 memset(buffer, 0, bufferSize);
52 const char *nosa = getenv("NOSA");
53 if (!strcmp(nosa, "-")) {
54 if (fgets(buffer, bufferSize-1, stdin) == NULL)
55 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
56 buffer[strlen(buffer)-1] = '\0'; // remove trailing newline
57 if (!isatty(fileno(stdin)))
58 printf("%s\n", buffer); // echo to output if input not terminal
59 } else {
60 strncpy(buffer, nosa, bufferSize-1);
61 printf("%s\n", buffer);
62 }
63 if (buffer[0] == '\0') // empty input -> cancellation
64 CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
65 }
66
67 #endif //NOSA
68
69
70 using SecurityAgent::Reason;
71 using namespace Authorization;
72
73 SecurityAgentQuery::SecurityAgentQuery(const AuthHostType type, Session &session) : mAuthHostType(type), mHostInstance(session.authhost(mAuthHostType)), mConnection(&Server::connection())
74 {
75 // this may take a while
76 Server::active().longTermActivity();
77 secdebug("SecurityAgentQuery", "new SecurityAgentQuery(%p)", this);
78 }
79
80 SecurityAgentQuery::~SecurityAgentQuery()
81 {
82 secdebug("SecurityAgentQuery", "SecurityAgentQuery(%p) dying", this);
83 mConnection->useAgent(NULL);
84
85 #if defined(NOSA)
86 if (getenv("NOSA")) {
87 printf(" [query done]\n");
88 return;
89 }
90 #endif
91
92 if (SecurityAgent::Client::state() != SecurityAgent::Client::dead)
93 destroy();
94 }
95
96 void
97 SecurityAgentQuery::activate()
98 {
99 mConnection->useAgent(this);
100
101 try {
102 SecurityAgent::Client::activate(mHostInstance->activate());
103 } catch (...) {
104 mConnection->useAgent(NULL); // guess not
105 throw;
106 }
107 }
108
109 void
110 SecurityAgentQuery::inferHints(Process &thisProcess)
111 {
112 RefPointer<OSXCode> clientCode = thisProcess.clientCode();
113 SecurityAgent::RequestorType requestorType = SecurityAgent::unknown;
114 string bundlePath;
115
116 if (clientCode)
117 {
118 string encodedBundle = clientCode->encode();
119 char bundleType = (encodedBundle.c_str())[0]; // yay, no accessor
120 switch(bundleType)
121 {
122 case 'b': requestorType = SecurityAgent::bundle; break;
123 case 't': requestorType = SecurityAgent::tool; break;
124 }
125 bundlePath = clientCode->canonicalPath();
126 }
127
128 AuthItemSet processHints = clientHints(requestorType, bundlePath, thisProcess.pid(), thisProcess.uid());
129 mClientHints.insert(processHints.begin(), processHints.end());
130 }
131
132 void
133 SecurityAgentQuery::readChoice()
134 {
135 allow = false;
136 remember = false;
137
138 AuthItem *allowAction = outContext().find(AGENT_CONTEXT_ALLOW);
139 if (allowAction)
140 {
141 string allowString;
142 if (allowAction->getString(allowString)
143 && (allowString == "YES"))
144 allow = true;
145 }
146
147 AuthItem *rememberAction = outContext().find(AGENT_CONTEXT_REMEMBER_ACTION);
148 if (rememberAction)
149 {
150 string rememberString;
151 if (rememberAction->getString(rememberString)
152 && (rememberString == "YES"))
153 remember = true;
154 }
155 }
156
157 void
158 SecurityAgentQuery::terminate()
159 {
160 activate();
161
162 // @@@ This happens already in the destructor; presumably we do this to tear things down orderly
163 mConnection->useAgent(NULL);
164
165 SecurityAgent::Client::terminate();
166 }
167
168 void
169 SecurityAgentQuery::create(const char *pluginId, const char *mechanismId, const SessionId inSessionId)
170 {
171 activate();
172 OSStatus status = SecurityAgent::Client::create(pluginId, mechanismId, inSessionId);
173 if (status)
174 {
175 secdebug("SecurityAgentQuery", "agent went walkabout, restarting");
176 Session &session = mHostInstance->session();
177 mHostInstance = session.authhost(mAuthHostType, true);
178 activate();
179 status = SecurityAgent::Client::create(pluginId, mechanismId, inSessionId);
180 }
181 if (status) MacOSError::throwMe(status);
182 }
183
184 //
185 // Perform the "rogue app" access query dialog
186 //
187 QueryKeychainUse::QueryKeychainUse(bool needPass, const Database *db)
188 : mPassphraseCheck(NULL)
189 {
190 // if passphrase checking requested, save KeychainDatabase reference
191 // (will quietly disable check if db isn't a keychain)
192 if (needPass)
193 mPassphraseCheck = dynamic_cast<const KeychainDatabase *>(db);
194 }
195
196 Reason QueryKeychainUse::queryUser (const char *database, const char *description, AclAuthorization action)
197 {
198 Reason reason = SecurityAgent::noReason;
199 int retryCount = 0;
200 OSStatus status;
201 AuthValueVector arguments;
202 AuthItemSet hints, context;
203
204 #if defined(NOSA)
205 if (getenv("NOSA")) {
206 char answer[maxPassphraseLength+10];
207
208 string applicationPath;
209 AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
210 if (applicationPathItem)
211 applicationPathItem->getString(applicationPath);
212
213 getNoSA(answer, sizeof(answer), "Allow %s to do %d on %s in %s? [yn][g]%s ",
214 applicationPath.c_str(), int(action), (description ? description : "[NULL item]"),
215 (database ? database : "[NULL database]"),
216 mPassphraseCheck ? ":passphrase" : "");
217 // turn passphrase (no ':') into y:passphrase
218 if (mPassphraseCheck && !strchr(answer, ':')) {
219 memmove(answer+2, answer, strlen(answer)+1);
220 memcpy(answer, "y:", 2);
221 }
222
223 allow = answer[0] == 'y';
224 remember = answer[1] == 'g';
225 return SecurityAgent::noReason;
226 }
227 #endif
228
229 // prepopulate with client hints
230 hints.insert(mClientHints.begin(), mClientHints.end());
231
232 // put action/operation (sint32) into hints
233 hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast<SInt32*>(&action))));
234
235 // item name into hints
236
237 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? strlen(description) : 0, const_cast<char*>(description))));
238
239 // keychain name into hints
240 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? strlen(database) : 0, const_cast<char*>(database))));
241
242
243 if (mPassphraseCheck)
244 {
245 create("builtin", "confirm-access-password", noSecuritySession);
246
247 CssmAutoData data(Allocator::standard(Allocator::sensitive));
248
249 do
250 {
251
252 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
253 hints.erase(triesHint); hints.insert(triesHint); // replace
254
255 if (retryCount++ > kMaximumAuthorizationTries)
256 {
257 reason = SecurityAgent::tooManyTries;
258 }
259
260 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
261 hints.erase(retryHint); hints.insert(retryHint); // replace
262
263 setInput(hints, context);
264 status = invoke();
265
266 if (retryCount > kMaximumAuthorizationTries)
267 {
268 return reason;
269 }
270
271 checkResult();
272
273 AuthItem *passwordItem = outContext().find(kAuthorizationEnvironmentPassword);
274 if (!passwordItem)
275 continue;
276
277 passwordItem->getCssmData(data);
278 }
279 while (reason = (const_cast<KeychainDatabase*>(mPassphraseCheck)->decode(data) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase));
280 }
281 else
282 {
283 create("builtin", "confirm-access", noSecuritySession);
284 setInput(hints, context);
285 invoke();
286 }
287
288 readChoice();
289
290 return reason;
291 }
292
293 //
294 // Perform code signature ACL access adjustment dialogs
295 //
296 bool QueryCodeCheck::operator () (const char *aclPath)
297 {
298 OSStatus status;
299 AuthValueVector arguments;
300 AuthItemSet hints, context;
301
302 #if defined(NOSA)
303 if (getenv("NOSA")) {
304 char answer[10];
305
306 string applicationPath;
307 AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
308 if (applicationPathItem)
309 applicationPathItem->getString(applicationPath);
310
311 getNoSA(answer, sizeof(answer),
312 "Allow %s to match an ACL for %s [yn][g]? ",
313 applicationPath.c_str(), aclPath ? aclPath : "(unknown)");
314 allow = answer[0] == 'y';
315 remember = answer[1] == 'g';
316 return;
317 }
318 #endif
319
320 // prepopulate with client hints
321 hints.insert(mClientHints.begin(), mClientHints.end());
322
323 hints.insert(AuthItemRef(AGENT_HINT_APPLICATION_PATH, AuthValueOverlay(strlen(aclPath), const_cast<char*>(aclPath))));
324
325 create("builtin", "code-identity", noSecuritySession);
326
327 setInput(hints, context);
328 status = invoke();
329
330 checkResult();
331
332 // MacOSError::check(status);
333
334 return kAuthorizationResultAllow == result();
335 }
336
337
338 //
339 // Obtain passphrases and submit them to the accept() method until it is accepted
340 // or we can't get another passphrase. Accept() should consume the passphrase
341 // if it is accepted. If no passphrase is acceptable, throw out of here.
342 //
343 Reason QueryOld::query()
344 {
345 Reason reason = SecurityAgent::noReason;
346 OSStatus status;
347 AuthValueVector arguments;
348 AuthItemSet hints, context;
349 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
350 int retryCount = 0;
351
352 #if defined(NOSA)
353 // return the passphrase
354 if (getenv("NOSA")) {
355 char passphrase_[maxPassphraseLength];
356 getNoSA(passphrase, maxPassphraseLength, "Unlock %s [<CR> to cancel]: ", database.dbName());
357 passphrase.copy(passphrase_, strlen(passphrase_));
358 return database.decode(passphrase) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase;
359 }
360 #endif
361
362 // prepopulate with client hints
363
364 const char *keychainPath = database.dbName();
365 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(strlen(keychainPath), const_cast<char*>(keychainPath))));
366
367 hints.insert(mClientHints.begin(), mClientHints.end());
368
369 create("builtin", "unlock-keychain", noSecuritySession);
370
371 do
372 {
373 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
374 hints.erase(triesHint); hints.insert(triesHint); // replace
375
376 ++retryCount;
377
378 if (retryCount > maxTries)
379 {
380 reason = SecurityAgent::tooManyTries;
381 }
382
383 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
384 hints.erase(retryHint); hints.insert(retryHint); // replace
385
386 setInput(hints, context);
387 status = invoke();
388
389 if (retryCount > maxTries)
390 {
391 return reason;
392 }
393
394 checkResult();
395
396 AuthItem *passwordItem = outContext().find(kAuthorizationEnvironmentPassword);
397 if (!passwordItem)
398 continue;
399
400 passwordItem->getCssmData(passphrase);
401
402 }
403 while (reason = accept(passphrase));
404
405 return SecurityAgent::noReason;
406 }
407
408
409 //
410 // Get existing passphrase (unlock) Query
411 //
412 Reason QueryOld::operator () ()
413 {
414 return query();
415 }
416
417
418 //
419 // End-classes for old secrets
420 //
421 Reason QueryUnlock::accept(CssmManagedData &passphrase)
422 {
423 if (safer_cast<KeychainDatabase &>(database).decode(passphrase))
424 return SecurityAgent::noReason;
425 else
426 return SecurityAgent::invalidPassphrase;
427 }
428
429
430 QueryPIN::QueryPIN(Database &db)
431 : QueryOld(db), mPin(Allocator::standard())
432 {
433 this->inferHints(Server::process());
434 }
435
436
437 Reason QueryPIN::accept(CssmManagedData &pin)
438 {
439 // no retries for now
440 mPin = pin;
441 return SecurityAgent::noReason;
442 }
443
444
445 //
446 // Obtain passphrases and submit them to the accept() method until it is accepted
447 // or we can't get another passphrase. Accept() should consume the passphrase
448 // if it is accepted. If no passphrase is acceptable, throw out of here.
449 //
450 Reason QueryNewPassphrase::query()
451 {
452 Reason reason = initialReason;
453 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
454 CssmAutoData oldPassphrase(Allocator::standard(Allocator::sensitive));
455
456 OSStatus status;
457 AuthValueVector arguments;
458 AuthItemSet hints, context;
459
460 int retryCount = 0;
461
462 #if defined(NOSA)
463 if (getenv("NOSA")) {
464 char passphrase_[maxPassphraseLength];
465 getNoSA(passphrase_, maxPassphraseLength,
466 "New passphrase for %s (reason %d) [<CR> to cancel]: ",
467 database.dbName(), reason);
468 return SecurityAgent::noReason;
469 }
470 #endif
471
472 // prepopulate with client hints
473 hints.insert(mClientHints.begin(), mClientHints.end());
474
475 // keychain name into hints
476 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database.dbName())));
477
478 switch (initialReason)
479 {
480 case SecurityAgent::newDatabase:
481 create("builtin", "new-passphrase", noSecuritySession);
482 break;
483 case SecurityAgent::changePassphrase:
484 create("builtin", "change-passphrase", noSecuritySession);
485 break;
486 default:
487 assert(false);
488 }
489
490 do
491 {
492 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
493 hints.erase(triesHint); hints.insert(triesHint); // replace
494
495 if (++retryCount > maxTries)
496 {
497 reason = SecurityAgent::tooManyTries;
498 }
499
500 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
501 hints.erase(retryHint); hints.insert(retryHint); // replace
502
503 setInput(hints, context);
504 status = invoke();
505
506 if (retryCount > maxTries)
507 {
508 return reason;
509 }
510
511 checkResult();
512
513 if (SecurityAgent::changePassphrase == initialReason)
514 {
515 AuthItem *oldPasswordItem = outContext().find(AGENT_PASSWORD);
516 if (!oldPasswordItem)
517 continue;
518
519 oldPasswordItem->getCssmData(oldPassphrase);
520 }
521
522 AuthItem *passwordItem = outContext().find(AGENT_CONTEXT_NEW_PASSWORD);
523 if (!passwordItem)
524 continue;
525
526 passwordItem->getCssmData(passphrase);
527
528 }
529 while (reason = accept(passphrase, (initialReason == SecurityAgent::changePassphrase) ? &oldPassphrase.get() : NULL));
530
531 return SecurityAgent::noReason;
532 }
533
534
535 //
536 // Get new passphrase Query
537 //
538 Reason QueryNewPassphrase::operator () (CssmOwnedData &passphrase)
539 {
540 if (Reason result = query())
541 return result; // failed
542 passphrase = mPassphrase;
543 return SecurityAgent::noReason; // success
544 }
545
546 Reason QueryNewPassphrase::accept(CssmManagedData &passphrase, CssmData *oldPassphrase)
547 {
548 //@@@ acceptance criteria are currently hardwired here
549 //@@@ This validation presumes ASCII - UTF8 might be more lenient
550
551 // if we have an old passphrase, check it
552 if (oldPassphrase && !safer_cast<KeychainDatabase&>(database).validatePassphrase(*oldPassphrase))
553 return SecurityAgent::oldPassphraseWrong;
554
555 // sanity check the new passphrase (but allow user override)
556 if (!(mPassphraseValid && passphrase.get() == mPassphrase)) {
557 mPassphrase = passphrase;
558 mPassphraseValid = true;
559 if (mPassphrase.length() == 0)
560 return SecurityAgent::passphraseIsNull;
561 if (mPassphrase.length() < 6)
562 return SecurityAgent::passphraseTooSimple;
563 }
564
565 // accept this
566 return SecurityAgent::noReason;
567 }
568
569 //
570 // Get a passphrase for unspecified use
571 //
572 Reason QueryGenericPassphrase::operator () (const char *prompt, bool verify,
573 string &passphrase)
574 {
575 return query(prompt, verify, passphrase);
576 }
577
578 Reason QueryGenericPassphrase::query(const char *prompt, bool verify,
579 string &passphrase)
580 {
581 Reason reason = SecurityAgent::noReason;
582 OSStatus status; // not really used; remove?
583 AuthValueVector arguments;
584 AuthItemSet hints, context;
585
586 #if defined(NOSA)
587 if (getenv("NOSA")) {
588 // FIXME 3690984
589 return SecurityAgent::noReason;
590 }
591 #endif
592
593 hints.insert(mClientHints.begin(), mClientHints.end());
594 hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? strlen(prompt) : 0, const_cast<char*>(prompt))));
595 // XXX/gh defined by dmitch but no analogous hint in
596 // AuthorizationTagsPriv.h:
597 // CSSM_ATTRIBUTE_ALERT_TITLE (optional alert panel title)
598
599 if (false == verify) { // import
600 create("builtin", "generic-unlock", noSecuritySession);
601 } else { // verify passphrase (export)
602 // new-passphrase-generic works with the pre-4 June 2004 agent;
603 // generic-new-passphrase is required for the new agent
604 create("builtin", "generic-new-passphrase", noSecuritySession);
605 }
606
607 AuthItem *passwordItem;
608
609 do {
610 setInput(hints, context);
611 status = invoke();
612 checkResult();
613 passwordItem = outContext().find(AGENT_PASSWORD);
614
615 } while (!passwordItem);
616
617 passwordItem->getString(passphrase);
618
619 return reason;
620 }
621
622
623 //
624 // Get a DB blob's passphrase--keychain synchronization
625 //
626
627 void QueryDBBlobSecret::addHint(const char *name, const void *value, UInt32 valueLen, UInt32 flags)
628 {
629 AuthorizationItem item = { name, valueLen, const_cast<void *>(value), flags };
630 mClientHints.insert(AuthItemRef(item));
631 }
632
633 Reason QueryDBBlobSecret::operator () (DatabaseCryptoCore &dbCore, const DbBlob *secretsBlob)
634 {
635 return query(dbCore, secretsBlob);
636 }
637
638 Reason QueryDBBlobSecret::query(DatabaseCryptoCore &dbCore, const DbBlob *secretsBlob)
639 {
640 Reason reason = SecurityAgent::noReason;
641 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
642 OSStatus status; // not really used; remove?
643 AuthValueVector arguments;
644 AuthItemSet hints/*NUKEME*/, context;
645
646 #if defined(NOSA)
647 if (getenv("NOSA")) {
648 // FIXME akin to 3690984
649 return SecurityAgent::noReason;
650 }
651 #endif
652
653 hints.insert(mClientHints.begin(), mClientHints.end());
654
655 create("builtin", "generic-unlock-kcblob", noSecuritySession);
656
657 AuthItem *secretItem;
658
659 int retryCount = 0;
660
661 do {
662 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
663 hints.erase(triesHint); hints.insert(triesHint); // replace
664
665 if (++retryCount > maxTries)
666 {
667 reason = SecurityAgent::tooManyTries;
668 }
669
670 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
671 hints.erase(retryHint); hints.insert(retryHint); // replace
672
673 setInput(hints, context);
674 status = invoke();
675 checkResult();
676 secretItem = outContext().find(AGENT_PASSWORD);
677 if (!secretItem)
678 continue;
679 secretItem->getCssmData(passphrase);
680
681 } while (reason = accept(passphrase, dbCore, secretsBlob));
682
683 return reason;
684 }
685
686 Reason QueryDBBlobSecret::accept(CssmManagedData &passphrase,
687 DatabaseCryptoCore &dbCore,
688 const DbBlob *secretsBlob)
689 {
690 try {
691 dbCore.setup(secretsBlob, passphrase);
692 dbCore.decodeCore(secretsBlob, NULL);
693 } catch (const CommonError &err) {
694 // XXX/gh Are there errors other than this?
695 return SecurityAgent::invalidPassphrase;
696 }
697 return SecurityAgent::noReason;
698 }
699
700 QueryInvokeMechanism::QueryInvokeMechanism(const AuthHostType type, Session &session) :
701 SecurityAgentQuery(type, session) { }
702
703 void QueryInvokeMechanism::initialize(const string &inPluginId, const string &inMechanismId, const AuthValueVector &inArguments, const SessionId inSessionId)
704 {
705 if (SecurityAgent::Client::init == SecurityAgent::Client::state())
706 {
707 create(inPluginId.c_str(), inMechanismId.c_str(), inSessionId);
708 mArguments = inArguments;
709 }
710 }
711
712 // XXX/cs should return AuthorizationResult
713 void QueryInvokeMechanism::run(const AuthValueVector &inArguments, AuthItemSet &inHints, AuthItemSet &inContext, AuthorizationResult *outResult)
714 {
715 // prepopulate with client hints
716 inHints.insert(mClientHints.begin(), mClientHints.end());
717
718 setArguments(inArguments);
719 setInput(inHints, inContext);
720 MacOSError::check(invoke());
721
722 if (outResult) *outResult = result();
723
724 inHints = outHints();
725 inContext = outContext();
726 }
727
728 void QueryInvokeMechanism::terminateAgent()
729 {
730 terminate();
731 }