4 Copyright (c) 2000-2004 The Regents of the University of Michigan.
7 Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
8 Copyright (c) 2001 Andy Adamson <andros@UMICH.EDU>.
9 Copyright (c) 2002 Marius Aamodt Eriksen <marius@UMICH.EDU>.
10 Copyright (c) 2002 Bruce Fields <bfields@UMICH.EDU>
11 Copyright (c) 2004 Kevin Coffman <kwc@umich.edu>
12 All rights reserved, all wrongs reversed.
14 Redistribution and use in source and binary forms, with or without
15 modification, are permitted provided that the following conditions
18 1. Redistributions of source code must retain the above copyright
19 notice, this list of conditions and the following disclaimer.
20 2. Redistributions in binary form must reproduce the above copyright
21 notice, this list of conditions and the following disclaimer in the
22 documentation and/or other materials provided with the distribution.
23 3. Neither the name of the University nor the names of its
24 contributors may be used to endorse or promote products derived
25 from this software without specific prior written permission.
27 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
28 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
29 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
34 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
35 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
36 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
37 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43 #endif /* HAVE_CONFIG_H */
49 #include <sys/param.h>
52 #include <sys/socket.h>
53 #include <arpa/inet.h>
54 #include <sys/fsuid.h>
67 #include <gssapi/gssapi.h>
73 #include "krb5_util.h"
78 * array of struct pollfd suitable to pass to poll. initialized to
79 * zero - a zero struct is ignored by poll() because the events mask is 0.
82 * linked list of struct clnt_info which associates a clntXXX directory
83 * with an index into pollarray[], and other basic data about that client.
85 * Directory structure: created by the kernel nfs client
86 * {pipefs_nfsdir}/clntXX : one per rpc_clnt struct in the kernel
87 * {pipefs_nfsdir}/clntXX/krb5 : read uid for which kernel wants
88 * a context, write the resulting context
89 * {pipefs_nfsdir}/clntXX/info : stores info such as server name
92 * Poll all {pipefs_nfsdir}/clntXX/krb5 files. When ready, data read
93 * is a uid; performs rpcsec_gss context initialization protocol to
94 * get a cred for that user. Writes result to corresponding krb5 file
95 * in a form the kernel code will understand.
96 * In addition, we make sure we are notified whenever anything is
97 * created or destroyed in {pipefs_nfsdir} or in an of the clntXX directories,
98 * and rescan the whole {pipefs_nfsdir} when this happens.
101 struct pollfd * pollarray;
103 int pollsize; /* the size of pollaray (in pollfd's) */
105 /* XXX buffer problems: */
107 read_service_info(char *info_file_name, char **servicename, char **servername,
108 int *prog, int *vers, char **protocol, int *port) {
109 #define INFOBUFLEN 256
110 char buf[INFOBUFLEN + 1];
111 static char dummy[128];
113 static char service[128];
114 static char address[128];
122 struct hostent *ent = NULL;
125 *servicename = *servername = *protocol = NULL;
127 if ((fd = open(info_file_name, O_RDONLY)) == -1) {
128 printerr(0, "ERROR: can't open %s: %s\n", info_file_name,
132 if ((nbytes = read(fd, buf, INFOBUFLEN)) == -1)
137 numfields = sscanf(buf,"RPC server: %127s\n"
138 "service: %127s %15s version %15s\n"
142 service, program, version,
146 if (numfields == 5) {
147 strcpy(protoname, "tcp");
148 } else if (numfields != 6) {
153 if ((p = strstr(buf, "port")) != NULL)
154 sscanf(p, "port: %127s\n", cb_port);
156 /* check service, program, and version */
157 if(memcmp(service, "nfs", 3)) return -1;
158 *prog = atoi(program + 1); /* skip open paren */
159 *vers = atoi(version);
160 if((*prog != 100003) || ((*vers != 2) && (*vers != 3) && (*vers != 4)))
163 /* create service name */
164 inaddr = inet_addr(address);
165 if (!(ent = gethostbyaddr(&inaddr, sizeof(inaddr), AF_INET))) {
166 printerr(0, "ERROR: can't resolve server %s name\n", address);
169 if (!(*servername = calloc(strlen(ent->h_name) + 1, 1)))
171 memcpy(*servername, ent->h_name, strlen(ent->h_name));
172 snprintf(buf, INFOBUFLEN, "%s@%s", service, ent->h_name);
173 if (!(*servicename = calloc(strlen(buf) + 1, 1)))
175 memcpy(*servicename, buf, strlen(buf));
176 if (cb_port[0] != '\0')
177 *port = atoi(cb_port);
179 if (!(*protocol = strdup(protoname)))
183 printerr(0, "ERROR: failed to read service info\n");
184 if (fd != -1) close(fd);
188 *servicename = *servername = *protocol = NULL;
193 destroy_client(struct clnt_info *clp)
195 if (clp->krb5_poll_index != -1)
196 memset(&pollarray[clp->krb5_poll_index], 0,
197 sizeof(struct pollfd));
198 if (clp->spkm3_poll_index != -1)
199 memset(&pollarray[clp->spkm3_poll_index], 0,
200 sizeof(struct pollfd));
201 if (clp->dir_fd != -1) close(clp->dir_fd);
202 if (clp->krb5_fd != -1) close(clp->krb5_fd);
203 if (clp->spkm3_fd != -1) close(clp->spkm3_fd);
205 free(clp->servicename);
206 free(clp->servername);
211 static struct clnt_info *
212 insert_new_clnt(void)
214 struct clnt_info *clp = NULL;
216 if (!(clp = (struct clnt_info *)calloc(1,sizeof(struct clnt_info)))) {
217 printerr(0, "ERROR: can't malloc clnt_info: %s\n",
221 clp->krb5_poll_index = -1;
222 clp->spkm3_poll_index = -1;
227 TAILQ_INSERT_HEAD(&clnt_list, clp, list);
233 process_clnt_dir_files(struct clnt_info * clp)
237 char info_file_name[32];
239 if (clp->krb5_fd == -1) {
240 snprintf(kname, sizeof(kname), "%s/krb5", clp->dirname);
241 clp->krb5_fd = open(kname, O_RDWR);
243 if (clp->spkm3_fd == -1) {
244 snprintf(sname, sizeof(sname), "%s/spkm3", clp->dirname);
245 clp->spkm3_fd = open(sname, O_RDWR);
247 if((clp->krb5_fd == -1) && (clp->spkm3_fd == -1))
249 snprintf(info_file_name, sizeof(info_file_name), "%s/info",
251 if ((clp->servicename == NULL) &&
252 read_service_info(info_file_name, &clp->servicename,
253 &clp->servername, &clp->prog, &clp->vers,
254 &clp->protocol, &clp->port))
260 get_poll_index(int *ind)
265 for (i=0; i<FD_ALLOC_BLOCK; i++) {
266 if (pollarray[i].events == 0) {
272 printerr(0, "ERROR: No pollarray slots open\n");
280 insert_clnt_poll(struct clnt_info *clp)
282 if ((clp->krb5_fd != -1) && (clp->krb5_poll_index == -1)) {
283 if (get_poll_index(&clp->krb5_poll_index)) {
284 printerr(0, "ERROR: Too many krb5 clients\n");
287 pollarray[clp->krb5_poll_index].fd = clp->krb5_fd;
288 pollarray[clp->krb5_poll_index].events |= POLLIN;
291 if ((clp->spkm3_fd != -1) && (clp->spkm3_poll_index == -1)) {
292 if (get_poll_index(&clp->spkm3_poll_index)) {
293 printerr(0, "ERROR: Too many spkm3 clients\n");
296 pollarray[clp->spkm3_poll_index].fd = clp->spkm3_fd;
297 pollarray[clp->spkm3_poll_index].events |= POLLIN;
304 process_clnt_dir(char *dir)
306 struct clnt_info * clp;
308 if (!(clp = insert_new_clnt()))
309 goto fail_destroy_client;
311 if (!(clp->dirname = calloc(strlen(dir) + 1, 1))) {
312 goto fail_destroy_client;
314 memcpy(clp->dirname, dir, strlen(dir));
315 if ((clp->dir_fd = open(clp->dirname, O_RDONLY)) == -1) {
316 printerr(0, "ERROR: can't open %s: %s\n",
317 clp->dirname, strerror(errno));
318 goto fail_destroy_client;
320 fcntl(clp->dir_fd, F_SETSIG, DNOTIFY_SIGNAL);
321 fcntl(clp->dir_fd, F_NOTIFY, DN_CREATE | DN_DELETE | DN_MULTISHOT);
323 if (process_clnt_dir_files(clp))
324 goto fail_keep_client;
326 if (insert_clnt_poll(clp))
327 goto fail_destroy_client;
333 TAILQ_REMOVE(&clnt_list, clp, list);
337 /* We couldn't find some subdirectories, but we keep the client
338 * around in case we get a notification on the directory when the
339 * subdirectories are created. */
344 init_client_list(void)
346 TAILQ_INIT(&clnt_list);
347 /* Eventually plan to grow/shrink poll array: */
348 pollsize = FD_ALLOC_BLOCK;
349 pollarray = calloc(pollsize, sizeof(struct pollfd));
353 * This is run after a DNOTIFY signal, and should clear up any
354 * directories that are no longer around, and re-scan any existing
355 * directories, since the DNOTIFY could have been in there.
358 update_old_clients(struct dirent **namelist, int size)
360 struct clnt_info *clp;
364 for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) {
366 for (i=0; i < size; i++) {
367 if (!strcmp(clp->dirname, namelist[i]->d_name)) {
373 printerr(2, "destroying client %s\n", clp->dirname);
374 saveprev = clp->list.tqe_prev;
375 TAILQ_REMOVE(&clnt_list, clp, list);
380 for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) {
381 if (!process_clnt_dir_files(clp))
382 insert_clnt_poll(clp);
386 /* Search for a client by directory name, return 1 if found, 0 otherwise */
388 find_client(char *dirname)
390 struct clnt_info *clp;
392 for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next)
393 if (!strcmp(clp->dirname, dirname))
398 /* Used to read (and re-read) list of clients, set up poll array. */
400 update_client_list(void)
402 struct dirent **namelist;
405 if (chdir(pipefs_nfsdir) < 0) {
406 printerr(0, "ERROR: can't chdir to %s: %s\n",
407 pipefs_nfsdir, strerror(errno));
411 j = scandir(pipefs_nfsdir, &namelist, NULL, alphasort);
413 printerr(0, "ERROR: can't scandir %s: %s\n",
414 pipefs_nfsdir, strerror(errno));
417 update_old_clients(namelist, j);
418 for (i=0; i < j; i++) {
419 if (i < FD_ALLOC_BLOCK
420 && !strncmp(namelist[i]->d_name, "clnt", 4)
421 && !find_client(namelist[i]->d_name))
422 process_clnt_dir(namelist[i]->d_name);
431 do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd,
432 gss_buffer_desc *context_token)
434 char *buf = NULL, *p = NULL, *end = NULL;
435 unsigned int timeout = context_timeout;
436 unsigned int buf_size = 0;
438 printerr(1, "doing downcall\n");
439 buf_size = sizeof(uid) + sizeof(timeout) + sizeof(pd->pd_seq_win) +
440 sizeof(pd->pd_ctx_hndl.length) + pd->pd_ctx_hndl.length +
441 sizeof(context_token->length) + context_token->length;
442 p = buf = malloc(buf_size);
443 end = buf + buf_size;
445 if (WRITE_BYTES(&p, end, uid)) goto out_err;
446 if (WRITE_BYTES(&p, end, timeout)) goto out_err;
447 if (WRITE_BYTES(&p, end, pd->pd_seq_win)) goto out_err;
448 if (write_buffer(&p, end, &pd->pd_ctx_hndl)) goto out_err;
449 if (write_buffer(&p, end, context_token)) goto out_err;
451 if (write(k5_fd, buf, p - buf) < p - buf) goto out_err;
456 printerr(1, "Failed to write downcall!\n");
461 do_error_downcall(int k5_fd, uid_t uid, int err)
464 char *p = buf, *end = buf + 1024;
465 unsigned int timeout = 0;
468 printerr(1, "doing error downcall\n");
470 if (WRITE_BYTES(&p, end, uid)) goto out_err;
471 if (WRITE_BYTES(&p, end, timeout)) goto out_err;
472 /* use seq_win = 0 to indicate an error: */
473 if (WRITE_BYTES(&p, end, zero)) goto out_err;
474 if (WRITE_BYTES(&p, end, err)) goto out_err;
476 if (write(k5_fd, buf, p - buf) < p - buf) goto out_err;
479 printerr(1, "Failed to write error downcall!\n");
484 * Create an RPC connection and establish an authenticated
485 * gss context with a server.
487 int create_auth_rpc_client(struct clnt_info *clp,
488 CLIENT **clnt_return,
493 CLIENT *rpc_clnt = NULL;
494 struct rpc_gss_sec sec;
500 char rpc_errmsg[1024];
501 int sockp = RPC_ANYSOCK;
502 int sendsz = 32768, recvsz = 32768;
503 struct addrinfo ai_hints, *a = NULL;
507 /* Create the context as the user (not as root) */
508 save_uid = geteuid();
509 if (setfsuid(uid) != 0) {
510 printerr(0, "WARNING: Failed to setfsuid for "
511 "user with uid %d\n", uid);
514 printerr(2, "creating context using fsuid %d (save_uid %d)\n",
517 sec.qop = GSS_C_QOP_DEFAULT;
518 sec.svc = RPCSEC_GSS_SVC_NONE;
519 sec.cred = GSS_C_NO_CREDENTIAL;
521 if (authtype == AUTHTYPE_KRB5) {
522 sec.mech = (gss_OID)&krb5oid;
523 sec.req_flags = GSS_C_MUTUAL_FLAG;
525 else if (authtype == AUTHTYPE_SPKM3) {
526 sec.mech = (gss_OID)&spkm3oid;
527 /* XXX sec.req_flags = GSS_C_ANON_FLAG;
528 * Need a way to switch....
530 sec.req_flags = GSS_C_MUTUAL_FLAG;
533 printerr(0, "ERROR: Invalid authentication type (%d) "
534 "in create_auth_rpc_client\n", authtype);
539 if (authtype == AUTHTYPE_KRB5) {
540 #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
542 * Do this before creating rpc connection since we won't need
543 * rpc connection if it fails!
545 if (limit_krb5_enctypes(&sec, uid)) {
546 printerr(1, "WARNING: Failed while limiting krb5 "
547 "encryption types for user with uid %d\n",
554 /* create an rpc connection to the nfs server */
556 printerr(2, "creating %s client for server %s\n", clp->protocol,
559 memset(&ai_hints, '\0', sizeof(ai_hints));
560 ai_hints.ai_family = PF_INET;
561 ai_hints.ai_flags |= AI_CANONNAME;
562 if ((strcmp(clp->protocol, "tcp")) == 0) {
563 ai_hints.ai_socktype = SOCK_STREAM;
564 ai_hints.ai_protocol = IPPROTO_TCP;
565 } else if ((strcmp(clp->protocol, "udp")) == 0) {
566 ai_hints.ai_socktype = SOCK_DGRAM;
567 ai_hints.ai_protocol = IPPROTO_UDP;
569 printerr(0, "WARNING: unrecognized protocol, '%s', requested "
570 "for connection to server %s for user with uid %d\n",
571 clp->protocol, clp->servername, uid);
575 /* extract the service name from clp->servicename */
576 if ((at_sign = strchr(clp->servicename, '@')) == NULL) {
577 printerr(0, "WARNING: servicename (%s) not formatted as "
578 "expected with service@host\n", clp->servicename);
581 if ((at_sign - clp->servicename) >= sizeof(service)) {
582 printerr(0, "WARNING: service portion of servicename (%s) "
583 "is too long!\n", clp->servicename);
586 strncpy(service, clp->servicename, at_sign - clp->servicename);
587 service[at_sign - clp->servicename] = '\0';
589 errcode = getaddrinfo(clp->servername, service, &ai_hints, &a);
591 printerr(0, "WARNING: Error from getaddrinfo for server "
592 "'%s': %s\n", clp->servername, gai_strerror(errcode));
597 printerr(0, "WARNING: No address information found for "
598 "connection to server %s for user with uid %d\n",
599 clp->servername, uid);
603 ((struct sockaddr_in *)a->ai_addr)->sin_port = htons(clp->port);
604 if (a->ai_protocol == IPPROTO_TCP) {
605 if ((rpc_clnt = clnttcp_create(
606 (struct sockaddr_in *) a->ai_addr,
607 clp->prog, clp->vers, &sockp,
608 sendsz, recvsz)) == NULL) {
609 snprintf(rpc_errmsg, sizeof(rpc_errmsg),
610 "WARNING: can't create tcp rpc_clnt "
611 "for server %s for user with uid %d",
612 clp->servername, uid);
614 clnt_spcreateerror(rpc_errmsg));
617 } else if (a->ai_protocol == IPPROTO_UDP) {
618 const struct timeval timeout = {5, 0};
619 if ((rpc_clnt = clntudp_bufcreate(
620 (struct sockaddr_in *) a->ai_addr,
621 clp->prog, clp->vers, timeout,
622 &sockp, sendsz, recvsz)) == NULL) {
623 snprintf(rpc_errmsg, sizeof(rpc_errmsg),
624 "WARNING: can't create udp rpc_clnt "
625 "for server %s for user with uid %d",
626 clp->servername, uid);
628 clnt_spcreateerror(rpc_errmsg));
632 /* Shouldn't happen! */
633 printerr(0, "ERROR: requested protocol '%s', but "
634 "got addrinfo with protocol %d\n",
635 clp->protocol, a->ai_protocol);
638 /* We're done with this */
642 printerr(2, "creating context with server %s\n", clp->servicename);
643 auth = authgss_create_default(rpc_clnt, clp->servicename, &sec);
645 /* Our caller should print appropriate message */
646 printerr(2, "WARNING: Failed to create %s context for "
647 "user with uid %d for server %s\n",
648 (authtype == AUTHTYPE_KRB5 ? "krb5":"spkm3"),
649 uid, clp->servername);
654 rpc_clnt->cl_auth = auth;
655 *clnt_return = rpc_clnt;
660 if (sec.cred != GSS_C_NO_CREDENTIAL)
661 gss_release_cred(&min_stat, &sec.cred);
662 if (a != NULL) freeaddrinfo(a);
663 /* Restore euid to original value */
664 if ((save_uid != -1) && (setfsuid(save_uid) != uid)) {
665 printerr(0, "WARNING: Failed to restore fsuid"
666 " to uid %d from %d\n", save_uid, uid);
671 /* Only destroy here if failure. Otherwise, caller is responsible */
672 if (rpc_clnt) clnt_destroy(rpc_clnt);
679 * this code uses the userland rpcsec gss library to create a krb5
680 * context on behalf of the kernel
683 handle_krb5_upcall(struct clnt_info *clp)
686 CLIENT *rpc_clnt = NULL;
688 struct authgss_private_data pd;
689 gss_buffer_desc token;
690 char **credlist = NULL;
693 int create_resp = -1;
695 printerr(1, "handling krb5 upcall\n");
699 memset(&pd, 0, sizeof(struct authgss_private_data));
701 if (read(clp->krb5_fd, &uid, sizeof(uid)) < sizeof(uid)) {
702 printerr(0, "WARNING: failed reading uid from krb5 "
703 "upcall pipe: %s\n", strerror(errno));
707 if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0)) {
708 /* Tell krb5 gss which credentials cache to use */
709 for (dirname = ccachesearch; *dirname != NULL; dirname++) {
710 if (gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname) == 0)
711 create_resp = create_auth_rpc_client(clp, &rpc_clnt, &auth, uid,
713 if (create_resp == 0)
717 if (create_resp != 0) {
718 if (uid == 0 && root_uses_machine_creds == 1) {
721 gssd_refresh_krb5_machine_credential(clp->servername,
724 * Get a list of credential cache names and try each
725 * of them until one works or we've tried them all
727 if (gssd_get_krb5_machine_cred_list(&credlist)) {
728 printerr(0, "ERROR: No credentials found "
729 "for connection to server %s\n",
731 goto out_return_error;
733 for (ccname = credlist; ccname && *ccname; ccname++) {
734 gssd_setup_krb5_machine_gss_ccache(*ccname);
735 if ((create_auth_rpc_client(clp, &rpc_clnt,
737 AUTHTYPE_KRB5)) == 0) {
742 printerr(2, "WARNING: Failed to create krb5 context "
743 "for user with uid %d with credentials "
744 "cache %s for server %s\n",
745 uid, *ccname, clp->servername);
747 gssd_free_krb5_machine_cred_list(credlist);
749 printerr(1, "WARNING: Failed to create krb5 context "
750 "for user with uid %d with any "
751 "credentials cache for server %s\n",
752 uid, clp->servername);
753 goto out_return_error;
756 printerr(1, "WARNING: Failed to create krb5 context "
757 "for user with uid %d for server %s\n",
758 uid, clp->servername);
759 goto out_return_error;
763 if (!authgss_get_private_data(auth, &pd)) {
764 printerr(1, "WARNING: Failed to obtain authentication "
765 "data for user with uid %d for server %s\n",
766 uid, clp->servername);
767 goto out_return_error;
770 if (serialize_context_for_kernel(pd.pd_ctx, &token, &krb5oid, NULL)) {
771 printerr(0, "WARNING: Failed to serialize krb5 context for "
772 "user with uid %d for server %s\n",
773 uid, clp->servername);
774 goto out_return_error;
777 do_downcall(clp->krb5_fd, uid, &pd, &token);
782 #ifndef HAVE_LIBTIRPC
783 if (pd.pd_ctx_hndl.length != 0)
784 authgss_free_private_data(&pd);
789 clnt_destroy(rpc_clnt);
793 do_error_downcall(clp->krb5_fd, uid, -1);
798 * this code uses the userland rpcsec gss library to create an spkm3
799 * context on behalf of the kernel
802 handle_spkm3_upcall(struct clnt_info *clp)
805 CLIENT *rpc_clnt = NULL;
807 struct authgss_private_data pd;
808 gss_buffer_desc token;
810 printerr(2, "handling spkm3 upcall\n");
815 if (read(clp->spkm3_fd, &uid, sizeof(uid)) < sizeof(uid)) {
816 printerr(0, "WARNING: failed reading uid from spkm3 "
817 "upcall pipe: %s\n", strerror(errno));
821 if (create_auth_rpc_client(clp, &rpc_clnt, &auth, uid, AUTHTYPE_SPKM3)) {
822 printerr(0, "WARNING: Failed to create spkm3 context for "
823 "user with uid %d\n", uid);
824 goto out_return_error;
827 if (!authgss_get_private_data(auth, &pd)) {
828 printerr(0, "WARNING: Failed to obtain authentication "
829 "data for user with uid %d for server %s\n",
830 uid, clp->servername);
831 goto out_return_error;
834 if (serialize_context_for_kernel(pd.pd_ctx, &token, &spkm3oid, NULL)) {
835 printerr(0, "WARNING: Failed to serialize spkm3 context for "
836 "user with uid %d for server\n",
837 uid, clp->servername);
838 goto out_return_error;
841 do_downcall(clp->spkm3_fd, uid, &pd, &token);
849 clnt_destroy(rpc_clnt);
853 do_error_downcall(clp->spkm3_fd, uid, -1);