]> git.saurik.com Git - apple/security.git/blob - SecurityServer/SecurityAgentClient.cpp
Security-54.tar.gz
[apple/security.git] / SecurityServer / SecurityAgentClient.cpp
1 /*
2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
8 * using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
16 */
17
18
19 //
20 // SecurityAgentClient - client interface to SecurityAgent
21 //
22 // This file changes behavior depending on two environment variables.
23 // AGENTNAME/AGENTPATH: if defined, is the name and path to
24 // the SecurityAgent binary to autolaunch. If undefined, SecurityAgent must be running.
25 // NOSA: If set, check for NOSA environment variable and if set, simulate the Agent
26 // using stdio in the client.
27 //
28 // A note on message flow: the combined send/receive operation at the heart of each
29 // secagent_client_* call can receive three types of message:
30 // (o) SecurityAgent reply -- ok, process
31 // (o) Dead port notification -- agent died, translated to NO_USER_INTERACTION error thrown
32 // (o) Cancel message -- will come out as INVALID_ID error and throw
33 //
34 // @@@ SA keepalive option.
35 //
36 #include "SecurityAgentClient.h"
37 #include "secagent.h"
38 #include <Security/utilities.h>
39 #include <Security/Authorization.h>
40 #include <cstdio>
41 #include <unistd.h>
42 #include <mach/mach_error.h>
43 #include <mach/ndr.h>
44 #include <Security/debugging.h>
45 #include <sys/wait.h>
46 #include <sys/syslimits.h>
47 #include <time.h>
48 #include <signal.h>
49 #include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
50
51 // @@@ Should be in <time.h> but it isn't as of Puma5F22
52 extern "C" int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
53
54 namespace Security {
55 namespace SecurityAgent {
56
57
58 using namespace Security;
59 using namespace MachPlusPlus;
60
61
62 // pass structured arguments in/out of IPC calls. See "data walkers" for details
63 #define COPY(copy) copy, copy.length(), copy
64 #define COPY_OUT(copy) &copy, &copy##Length, &copy##Base
65 #define COPY_OUT_DECL(type,name) type *name, *name##Base; mach_msg_type_number_t name##Length
66
67
68 //
69 // Encode a requestor
70 //
71 class Requestor {
72 public:
73 Requestor(const OSXCode *code) { if (code) extForm = code->encode(); }
74 operator const char * () const { return extForm.c_str(); }
75
76 private:
77 string extForm;
78 };
79
80
81 //
82 // Check a return from a MIG client call
83 //
84 void Client::check(kern_return_t error)
85 {
86 // first check the Mach IPC return code
87 switch (error) {
88 case KERN_SUCCESS: // peachy
89 break;
90 case MIG_SERVER_DIED: // explicit can't-send-it's-dead
91 stage = mainStage;
92 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
93 default: // some random Mach error
94 stage = mainStage;
95 MachPlusPlus::Error::throwMe(error);
96 }
97
98 // now check the OSStatus return from the server side
99 switch (status) {
100 case noErr:
101 case errAuthorizationDenied:
102 break;
103 case userCanceledErr:
104 unstage();
105 CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
106 default:
107 unstage();
108 MacOSError::throwMe(status);
109 }
110 }
111
112 void Client::unstage()
113 {
114 if (stage != mainStage) {
115 mStagePort.deallocate();
116 stage = mainStage;
117 }
118 }
119
120
121 //
122 // NOSA support functions. This is a test mode where the SecurityAgent
123 // is simulated via stdio in the client. Good for running automated tests
124 // of client programs. Only available if -DNOSA when compiling.
125 //
126 #if defined(NOSA)
127
128 #include <cstdarg>
129
130 static void getNoSA(char *buffer, size_t bufferSize, const char *fmt, ...)
131 {
132 // write prompt
133 va_list args;
134 va_start(args, fmt);
135 vfprintf(stdout, fmt, args);
136 va_end(args);
137
138 // read reply
139 memset(buffer, 0, bufferSize);
140 const char *nosa = getenv("NOSA");
141 if (!strcmp(nosa, "-")) {
142 if (fgets(buffer, bufferSize-1, stdin) == NULL)
143 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
144 buffer[strlen(buffer)-1] = '\0'; // remove trailing newline
145 if (!isatty(fileno(stdin)))
146 printf("%s\n", buffer); // echo to output if input not terminal
147 } else {
148 strncpy(buffer, nosa, bufferSize-1);
149 printf("%s\n", buffer);
150 }
151 if (buffer[0] == '\0') // empty input -> cancellation
152 CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
153 }
154
155 #endif //NOSA
156
157
158 //
159 // Initialize our CSSM interface
160 //
161 Client::Client() : mActive(false), mUsePBS(true), mKeepAlive(false), stage(mainStage)
162 {
163 }
164
165 /*
166 * The new, preferred way to activate the Security Agent. The Security
167 * Server will take advantage of this interface; the old constructor is
168 * kept around for compatibility with the only other client, DiskCopy.
169 * DiskCopy needs to be fixed to use the Security Server itself rather
170 * than this library.
171 */
172 Client::Client(uid_t clientUID, Bootstrap clientBootstrap) :
173 mActive(false), desktopUid(clientUID), mUsePBS(false),
174 mClientBootstrap(clientBootstrap), mKeepAlive(false), stage(mainStage)
175 {
176 setClientGroupID();
177 debug("SAclnt", "Desktop: uid %d, gid %d", desktopUid, desktopGid);
178 }
179
180 Client::~Client()
181 {
182 terminate();
183 }
184
185
186 //
187 // Activate a session
188 //
189 void Client::activate(const char *name)
190 {
191 if (!mActive) {
192 establishServer(name ? name : "SecurityAgent");
193
194 // create reply port
195 mClientPort.allocate(MACH_PORT_RIGHT_RECEIVE);
196 mClientPort.insertRight(MACH_MSG_TYPE_MAKE_SEND);
197
198 // get notified if the server dies (shouldn't happen, but...)
199 mServerPort.requestNotify(mClientPort, MACH_NOTIFY_DEAD_NAME, true);
200
201 // ready
202 mActive = true;
203 }
204 }
205
206 void Client::terminate()
207 {
208 if (mActive) {
209 mServerPort.deallocate();
210 mClientPort.destroy();
211 mActive = false;
212 }
213 }
214
215
216 //
217 // Cancel a client call.
218 // This actually sends a reply message to the thread waiting for a reply,
219 // thereby unblocking it.
220 // @@@ Theoretically we should thread-lock this so only one cancel message
221 // ever gets sent. But right now, this is only used to completely tear down
222 // a client session, so duplicate replies don't bother us.
223 //
224 void Client::cancel()
225 {
226 // this is the common prefix of SecurityAgent client call replies
227 struct {
228 mach_msg_header_t Head;
229 NDR_record_t NDR;
230 kern_return_t result;
231 OSStatus status;
232 } request;
233
234 request.Head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
235 request.Head.msgh_remote_port = mClientPort;
236 request.Head.msgh_local_port = MACH_PORT_NULL;
237 request.Head.msgh_id = cancelMessagePseudoID;
238 request.NDR = NDR_record;
239
240 // set call succeeded, no error status
241 request.result = KERN_SUCCESS;
242 request.status = noErr;
243
244 // send it (do not receive a reply). Use zero timeout to avoid hangs
245 MachPlusPlus::check(mach_msg_overwrite(&request.Head, MACH_SEND_MSG|MACH_SEND_TIMEOUT,
246 sizeof(request), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE,
247 MACH_PORT_NULL, (mach_msg_header_t *) NULL, 0));
248 }
249
250
251 //
252 // Get the port for the SecurityAgent.
253 // Start it if necessary (and possible). Throw an exception if we can't get to it.
254 // Sets mServerPort on success.
255 //
256 void Client::establishServer(const char *name)
257 {
258 /*
259 * Once we wean ourselves off PBS we can eliminate "bootstrap" and use
260 * mClientBootstrap directly.
261 */
262 if (mUsePBS)
263 locateDesktop();
264 else
265 pbsBootstrap = mClientBootstrap;
266
267 // If the userids don't match, that means you can't do user interaction
268 // @@@ Check session so we don't pop up UI in a non-UI context
269 // @@@ Expose this to caller so it can implement its own idea of getuid()!
270 if (desktopUid != getuid() && getuid() != 0)
271 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
272
273 // if the server is already running, we're done
274 Bootstrap bootstrap(pbsBootstrap);
275 if (mServerPort = bootstrap.lookupOptional(name))
276 return;
277
278 #if defined(AGENTNAME) && defined(AGENTPATH)
279 // switch the bootstrap port to that of the logged-in user
280 StBootstrap bootSaver(pbsBootstrap);
281
282 // try to start the agent
283 switch (pid_t pid = fork()) {
284 case 0: // child
285 {
286 // Setup the environment for the SecurityAgent
287 unsetenv("USER");
288 unsetenv("LOGNAME");
289 unsetenv("HOME");
290
291 debug("SAclnt", "setgid(%d)", desktopGid);
292 setgid(desktopGid); // switch to login-user gid
293 debug("SAclnt", "setuid(%d)", desktopUid);
294 // Must be setuid and not seteuid since we do not want the agent to be able
295 // to call seteuid(0) successfully.
296 setuid(desktopUid); // switch to login-user uid
297
298 // close down any files that might have been open at this point
299 int maxDescriptors = getdtablesize ();
300 int i;
301
302 for (i = 3; i < maxDescriptors; ++i)
303 {
304 close (i);
305 }
306
307 // construct path to SecurityAgent
308 char agentExecutable[PATH_MAX + 1];
309 const char *path = getenv("SECURITYAGENT");
310 if (!path)
311 path = AGENTPATH;
312 snprintf(agentExecutable, sizeof(agentExecutable), "%s/Contents/MacOS/" AGENTNAME, path);
313 debug("SAclnt", "execl(%s)", agentExecutable);
314 execl(agentExecutable, agentExecutable, NULL);
315 debug("SAclnt", "execl of SecurityAgent failed, errno=%d", errno);
316
317 // Unconditional suicide follows.
318 // See comments below on why we can't use abort()
319 #if 1
320 _exit(1);
321 #else
322 // NOTE: OS X abort() is implemented as kill(getuid()), which fails
323 // for a setuid-root process that has setuid'd. Go back to root to die...
324 setuid(0);
325 abort();
326 #endif
327 }
328 case -1: // error (in parent)
329 UnixError::throwMe();
330 default: // parent
331 {
332 static const int timeout = 300;
333
334 debug("SAclnt", "Starting security agent (%d seconds timeout)", timeout);
335 struct timespec rqtp;
336 memset(&rqtp, 0, sizeof(rqtp));
337 rqtp.tv_nsec = 100000000; /* 10^8 nanaseconds = 1/10th of a second */
338 for (int n = timeout; n > 0; nanosleep(&rqtp, NULL), n--) {
339 if (mServerPort = bootstrap.lookupOptional(name))
340 break;
341 int status;
342 switch (pid_t rc = waitpid(pid, &status, WNOHANG)) {
343 case 0: // child still running
344 continue;
345 case -1: // error
346 switch (errno) {
347 case EINTR:
348 case EAGAIN: // transient
349 continue;
350 case ECHILD: // no such child (dead; already reaped elsewhere)
351 debug("SAclnt", "child is dead (reaped elsewhere)");
352 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
353 default:
354 debug("SAclnt", "waitpid failed: errno=%d", errno);
355 UnixError::throwMe();
356 }
357 default:
358 assert(rc == pid);
359 debug("SAclnt", "child died without claiming the SecurityAgent port");
360 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
361 }
362 }
363
364 if (mServerPort == 0) { // couldn't contact Security Agent
365 debug("SAclnt", "Autolaunch failed");
366 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
367 }
368 debug("SAclnt", "SecurityAgent located");
369 return;
370 }
371 }
372 #endif
373
374 // well, this didn't work. Too bad
375 debug("SAclnt", "Cannot contact SecurityAgent");
376 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION); //@@@ or INTERNAL_ERROR?
377 }
378
379
380 //
381 // Staged query maintainance
382 //
383 void Client::finishStagedQuery()
384 {
385 if (stage == mainStage)
386 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); //@@@ invent a "state mismatch error"?
387 #if defined(NOSA)
388 if (getenv("NOSA")) {
389 printf(" [query done]\n");
390 stage = mainStage;
391 return;
392 }
393 #endif
394 check(secagent_client_finishStagedQuery(mStagePort, mClientPort, &status));
395 unstage();
396 terminate();
397 }
398
399 void Client::cancelStagedQuery(Reason reason)
400 {
401 if (stage == mainStage)
402 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); //@@@ invent a "state mismatch error"?
403 #if defined(NOSA)
404 if (getenv("NOSA")) {
405 printf(" [query canceled; reason=%d]\n", reason);
406 stage = mainStage;
407 return;
408 }
409 #endif
410 check(secagent_client_cancelStagedQuery(mStagePort, mClientPort, &status, reason));
411 unstage();
412 terminate();
413 }
414
415
416 //
417 // Query the user to unlock a keychain. This is a staged protocol with a private side-port.
418 //
419 void Client::queryUnlockDatabase(const OSXCode *requestor, pid_t requestPid,
420 const char *database, char passphrase[maxPassphraseLength])
421 {
422 Requestor req(requestor);
423
424 #if defined(NOSA)
425 if (getenv("NOSA")) {
426 getNoSA(passphrase, maxPassphraseLength, "Unlock %s [<CR> to cancel]: ", database);
427 stage = unlockStage;
428 return;
429 }
430 #endif
431 activate();
432 check(secagent_client_unlockDatabase(mServerPort, mClientPort,
433 &status, req, requestPid, database, &mStagePort.port(), passphrase));
434 stage = unlockStage;
435 }
436
437 void Client::retryUnlockDatabase(Reason reason, char passphrase[maxPassphraseLength])
438 {
439 if (stage != unlockStage)
440 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); //@@@ invent a "state mismatch error"?
441 #if defined(NOSA)
442 if (getenv("NOSA")) {
443 getNoSA(passphrase, maxPassphraseLength, "Retry unlock [<CR> to cancel]: ");
444 return;
445 }
446 #endif
447 check(secagent_client_retryUnlockDatabase(mStagePort, mClientPort,
448 &status, reason, passphrase));
449 }
450
451
452 //
453 // Ask for a (new) password for something.
454 //
455 void Client::queryNewPassphrase(const OSXCode *requestor, pid_t requestPid,
456 const char *database, Reason reason, char passphrase[maxPassphraseLength])
457 {
458 Requestor req(requestor);
459
460 #if defined(NOSA)
461 if (getenv("NOSA")) {
462 getNoSA(passphrase, maxPassphraseLength,
463 "New passphrase for %s (reason %d) [<CR> to cancel]: ",
464 (database ? database : "[NULL database]"), reason);
465 stage = newPassphraseStage;
466 return;
467 }
468 #endif
469 activate();
470 check(secagent_client_queryNewPassphrase(mServerPort, mClientPort,
471 &status, req, requestPid, database, reason,
472 &mStagePort.port(), passphrase));
473 stage = newPassphraseStage;
474 }
475
476 void Client::retryNewPassphrase(Reason reason, char passphrase[maxPassphraseLength])
477 {
478 if (stage != newPassphraseStage)
479 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); //@@@ invent a "state mismatch error"?
480 #if defined(NOSA)
481 if (getenv("NOSA")) {
482 getNoSA(passphrase, maxPassphraseLength,
483 "retry new passphrase (reason %d) [<CR> to cancel]: ", reason);
484 return;
485 }
486 #endif
487 check(secagent_client_retryNewPassphrase(mStagePort, mClientPort,
488 &status, reason, passphrase));
489 }
490
491
492 //
493 // Ask the user permission to use an item.
494 // This is used by the keychain-style ACL subject type (only).
495 //
496 void Client::queryKeychainAccess(const OSXCode *requestor, pid_t requestPid,
497 const char *database, const char *itemName, AclAuthorization action,
498 bool needPassphrase, KeychainChoice &choice)
499 {
500 Requestor req(requestor);
501
502 #if defined(NOSA)
503 if (getenv("NOSA")) {
504 char answer[maxPassphraseLength+10];
505 getNoSA(answer, sizeof(answer), "Allow [someone] to do %d on %s in %s? [yn][g]%s ",
506 int(action), (itemName ? itemName : "[NULL item]"),
507 (database ? database : "[NULL database]"),
508 needPassphrase ? ":passphrase" : "");
509 // turn passphrase (no ':') into y:passphrase
510 if (needPassphrase && !strchr(answer, ':')) {
511 memmove(answer+2, answer, strlen(answer)+1);
512 memcpy(answer, "y:", 2);
513 }
514 choice.allowAccess = answer[0] == 'y';
515 choice.continueGrantingToCaller = answer[1] == 'g';
516 if (const char *colon = strchr(answer, ':'))
517 strncpy(choice.passphrase, colon+1, maxPassphraseLength);
518 else
519 choice.passphrase[0] = '\0';
520 return;
521 }
522 #endif
523 activate();
524 check(secagent_client_queryKeychainAccess(mServerPort, mClientPort,
525 &status, req, requestPid, (database ? database : ""), itemName, action,
526 needPassphrase, &choice));
527 terminate();
528 }
529
530
531 //
532 // Query the user for a generic existing passphrase, with selectable prompt.
533 //
534 void Client::queryOldGenericPassphrase(const OSXCode *requestor, pid_t requestPid,
535 const char *prompt,
536 KeychainBox &addToKeychain, char passphrase[maxPassphraseLength])
537 {
538 Requestor req(requestor);
539
540 #if defined(NOSA)
541 if (getenv("NOSA")) {
542 getNoSA(passphrase, maxPassphraseLength,
543 "Old passphrase (\"%s\") [<CR> to cancel]: ", prompt);
544 // @@@ addToKeychain not hooked up; stays unchanged
545 stage = oldGenericPassphraseStage;
546 return;
547 }
548 #endif
549 activate();
550 MigBoolean addBox = addToKeychain.setting;
551 check(secagent_client_queryOldGenericPassphrase(mServerPort, mClientPort,
552 &status, req, requestPid, prompt, &mStagePort.port(),
553 addToKeychain.show, &addBox, passphrase));
554 addToKeychain.setting = addBox;
555 stage = oldGenericPassphraseStage;
556 }
557
558 void Client::retryOldGenericPassphrase(Reason reason,
559 bool &addToKeychain, char passphrase[maxPassphraseLength])
560 {
561 if (stage != oldGenericPassphraseStage)
562 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); //@@@ invent a "state mismatch error"?
563 #if defined(NOSA)
564 if (getenv("NOSA")) {
565 getNoSA(passphrase, maxPassphraseLength,
566 "Retry old passphrase [<CR> to cancel]: ");
567 return;
568 }
569 #endif
570 MigBoolean addBox;
571 check(secagent_client_retryOldGenericPassphrase(mStagePort, mClientPort,
572 &status, reason, &addBox, passphrase));
573 addToKeychain = addBox;
574 }
575
576
577 //
578 // Ask for a new passphrase for something (with selectable prompt).
579 //
580 void Client::queryNewGenericPassphrase(const OSXCode *requestor, pid_t requestPid,
581 const char *prompt, Reason reason,
582 KeychainBox &addToKeychain, char passphrase[maxPassphraseLength])
583 {
584 Requestor req(requestor);
585
586 #if defined(NOSA)
587 if (getenv("NOSA")) {
588 getNoSA(passphrase, maxPassphraseLength,
589 "New passphrase (\"%s\") (reason %d) [<CR> to cancel]: ",
590 prompt, reason);
591 // @@@ addToKeychain not hooked up; stays unchanged
592 stage = newGenericPassphraseStage;
593 return;
594 }
595 #endif
596 activate();
597 MigBoolean addBox = addToKeychain.setting;
598 check(secagent_client_queryNewGenericPassphrase(mServerPort, mClientPort,
599 &status, req, requestPid, prompt, reason,
600 &mStagePort.port(), addToKeychain.show, &addBox, passphrase));
601 addToKeychain.setting = addBox;
602 stage = newGenericPassphraseStage;
603 }
604
605 void Client::retryNewGenericPassphrase(Reason reason,
606 bool &addToKeychain, char passphrase[maxPassphraseLength])
607 {
608 if (stage != newGenericPassphraseStage)
609 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); //@@@ invent a "state mismatch error"?
610 #if defined(NOSA)
611 if (getenv("NOSA")) {
612 getNoSA(passphrase, maxPassphraseLength,
613 "retry new passphrase (reason %d) [<CR> to cancel]: ", reason);
614 return;
615 }
616 #endif
617 MigBoolean addBox;
618 check(secagent_client_retryNewGenericPassphrase(mStagePort, mClientPort,
619 &status, reason, &addBox, passphrase));
620 addToKeychain = addBox;
621 }
622
623
624 //
625 // Authorization by authentication
626 //
627 bool Client::authorizationAuthenticate(const OSXCode *requestor, pid_t requestPid,
628 const char *neededGroup, const char *candidateUser,
629 char user[maxUsernameLength], char passphrase[maxPassphraseLength])
630 {
631 Requestor req(requestor);
632
633 #if defined(NOSA)
634 if (getenv("NOSA")) {
635 getNoSA(user, maxUsernameLength,
636 "User to authenticate for group %s (try \"%s\" [\"-\" to deny]): ",
637 neededGroup, (candidateUser ? candidateUser : "[NULL]"));
638 if (strcmp(user, "-"))
639 getNoSA(passphrase, maxPassphraseLength,
640 "Passphrase for user %s: ", user);
641 stage = authorizeStage;
642 return strcmp(user, "-");
643 }
644 #endif
645 activate();
646 check(secagent_client_authorizationAuthenticate(mServerPort, mClientPort,
647 &status, req, requestPid, neededGroup, candidateUser, &mStagePort.port(), user, passphrase));
648 stage = authorizeStage;
649 return status == noErr;
650 }
651
652 bool Client::retryAuthorizationAuthenticate(Reason reason, char user[maxUsernameLength],
653 char passphrase[maxPassphraseLength])
654 {
655 if (stage != authorizeStage)
656 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); //@@@ invent a "state mismatch error"?
657 #if defined(NOSA)
658 if (getenv("NOSA")) {
659 getNoSA(user, maxUsernameLength,
660 "Retry authenticate (reason=%d) ([\"-\" to deny again]): ", reason);
661 if (strcmp(user, "-"))
662 getNoSA(passphrase, maxPassphraseLength,
663 "Passphrase for user %s: ", user);
664 return strcmp(user, "-");
665 }
666 #endif
667 check(secagent_client_retryAuthorizationAuthenticate(mStagePort, mClientPort,
668 &status, reason, user, passphrase));
669 return status == noErr;
670 }
671
672 //
673 // invokeMechanism old style
674 //
675 bool Client::invokeMechanism(const string &inPluginId, const string &inMechanismId, const AuthorizationValueVector *inArguments, const AuthorizationItemSet *inHints, const AuthorizationItemSet *inContext, AuthorizationResult *outResult, AuthorizationItemSet *&outHintsPtr, AuthorizationItemSet *&outContextPtr)
676 {
677 Copier<AuthorizationValueVector> inArgumentVector(inArguments);
678 Copier<AuthorizationItemSet> inHintsSet(inHints);
679 Copier<AuthorizationItemSet> inContextSet(inContext);
680
681 COPY_OUT_DECL(AuthorizationItemSet, outHintsSet);
682 COPY_OUT_DECL(AuthorizationItemSet, outContextSet);
683
684 activate();
685
686 // either noErr (user cancel, allow) or throws authInternal
687 check(secagent_client_invokeMechanism(mServerPort, mClientPort,
688 &status, &mStagePort.port(),
689 inPluginId.c_str(),
690 inMechanismId.c_str(),
691 COPY(inArgumentVector),
692 COPY(inHintsSet),
693 COPY(inContextSet),
694 outResult,
695 COPY_OUT(outHintsSet),
696 COPY_OUT(outContextSet)));
697
698 if (status != errAuthorizationDenied)
699 {
700 relocate(outHintsSet, outHintsSetBase);
701 Copier<AuthorizationItemSet> copyHints(outHintsSet);
702 // the auth engine releases this when done
703 outHintsPtr = copyHints.keep();
704 relocate(outContextSet, outContextSetBase);
705 Copier<AuthorizationItemSet> copyContext(outContextSet);
706 // the auth engine releases this when done
707 outContextPtr = copyContext.keep();
708 }
709
710 return (status == noErr);
711 }
712
713
714 void Client::terminateAgent()
715 {
716 if (mUsePBS)
717 // find the right place to look
718 locateDesktop();
719
720 // make sure we're doing this for the right user
721 // @@@ Check session as well!
722 if (desktopUid != getuid() && getuid() != 0)
723 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
724
725 // if the server is already running, it's time to kill it
726 bool agentRunning = false;
727 if (mUsePBS)
728 {
729 Bootstrap bootstrap(pbsBootstrap);
730 if (mServerPort = bootstrap.lookupOptional("SecurityAgent"))
731 agentRunning = true;
732 }
733 else
734 {
735 if (mServerPort = mClientBootstrap.lookupOptional("SecurityAgent"))
736 agentRunning = true;
737 }
738 if (agentRunning)
739 {
740 activate();
741 check(secagent_client_terminate(mServerPort, mClientPort));
742 }
743 }
744
745 #include <sys/types.h>
746 #include <grp.h>
747
748 void Client::setClientGroupID(const char *grpName)
749 {
750 /*
751 * desktopGid is unsigned so the compiler warns about the conversion
752 * of -2.
753 */
754 struct group *grent = getgrnam(grpName ? grpName : "nobody");
755 desktopGid = grent ? grent->gr_gid : -2;
756 }
757
758 //
759 // Locate and identify the current desktop.
760 // This is moderately atrocious code. There really ought to be a way to identify
761 // the logged-in (graphics console) user (and whether there is one). As it stands,
762 // we locate the "pbs" (pasteboard server) process and obtain its uid. No pbs, no
763 // user interaction. (By all accounts, a dead pbs is a death sentence anyway.)
764 //
765 #include <sys/sysctl.h>
766 #include <mach/mach_error.h>
767
768 void Client::locateDesktop()
769 {
770 int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL};
771 size_t bufSize;
772 struct kinfo_proc *procBuf;
773
774 if (sysctl(mib, 3, NULL, &bufSize, NULL, 0) < 0) {
775 perror("sysctl");
776 abort();
777 }
778
779 procBuf = (struct kinfo_proc *)malloc(bufSize); //@@@ which allocator?
780 if (sysctl(mib, 3, procBuf, &bufSize, NULL, 0))
781 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
782 int count = bufSize / sizeof(struct kinfo_proc);
783 struct kinfo_proc *pbsProc = NULL;
784 for (struct kinfo_proc *proc = procBuf; proc < procBuf + count; proc++) {
785 if (!strncmp(proc->kp_proc.p_comm, "pbs", MAXCOMLEN)) {
786 pbsProc = proc;
787 break;
788 }
789 }
790
791 if (!pbsProc) { // no pasteboard server -- user not logged in
792 debug("SAclnt", "No pasteboard server - no user logged in");
793 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
794 }
795
796 desktopUid = pbsProc->kp_eproc.e_ucred.cr_uid;
797 desktopGid = pbsProc->kp_eproc.e_ucred.cr_gid;
798 pid_t pbsPid = pbsProc->kp_proc.p_pid;
799
800 debug("SAclnt", "Desktop has uid %d", desktopUid);
801 free(procBuf);
802
803 kern_return_t result;
804 mach_port_t pbsTaskPort;
805 result = task_for_pid(mach_task_self(), pbsPid, &pbsTaskPort);
806 if (result)
807 {
808 mach_error("task_for_pid(pbs)", result);
809 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
810 }
811
812 result = task_get_bootstrap_port(pbsTaskPort, &pbsBootstrap);
813 if (result)
814 {
815 mach_error("task_get_bootstrap_port(pbs)", result);
816 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
817 }
818 }
819
820 } // end namespace SecurityAgent
821 } // end namespace Security