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.
45 #include <sys/param.h>
48 #include <sys/socket.h>
49 #include <arpa/inet.h>
50 #include <sys/fsuid.h>
63 #include <gssapi/gssapi.h>
70 #include "krb5_util.h"
75 * array of struct pollfd suitable to pass to poll. initialized to
76 * zero - a zero struct is ignored by poll() because the events mask is 0.
79 * linked list of struct clnt_info which associates a clntXXX directory
80 * with an index into pollarray[], and other basic data about that client.
82 * Directory structure: created by the kernel nfs client
83 * {pipefs_nfsdir}/clntXX : one per rpc_clnt struct in the kernel
84 * {pipefs_nfsdir}/clntXX/krb5 : read uid for which kernel wants
85 * a context, write the resulting context
86 * {pipefs_nfsdir}/clntXX/info : stores info such as server name
89 * Poll all {pipefs_nfsdir}/clntXX/krb5 files. When ready, data read
90 * is a uid; performs rpcsec_gss context initialization protocol to
91 * get a cred for that user. Writes result to corresponding krb5 file
92 * in a form the kernel code will understand.
93 * In addition, we make sure we are notified whenever anything is
94 * created or destroyed in {pipefs_nfsdir} or in an of the clntXX directories,
95 * and rescan the whole {pipefs_nfsdir} when this happens.
98 struct pollfd * pollarray;
100 int pollsize; /* the size of pollaray (in pollfd's) */
102 /* XXX buffer problems: */
104 read_service_info(char *info_file_name, char **servicename, char **servername,
105 int *prog, int *vers, char **protocol, int *port) {
106 #define INFOBUFLEN 256
107 char buf[INFOBUFLEN];
108 static char dummy[128];
110 static char service[128];
111 static char address[128];
119 struct hostent *ent = NULL;
122 *servicename = *servername = *protocol = NULL;
124 if ((fd = open(info_file_name, O_RDONLY)) == -1) {
125 printerr(0, "ERROR: can't open %s: %s\n", info_file_name,
129 if ((nbytes = read(fd, buf, INFOBUFLEN)) == -1)
133 numfields = sscanf(buf,"RPC server: %127s\n"
134 "service: %127s %15s version %15s\n"
138 service, program, version,
142 if (numfields == 5) {
143 strcpy(protoname, "tcp");
144 } else if (numfields != 6) {
149 if ((p = strstr(buf, "port")) != NULL)
150 sscanf(p, "port: %127s\n", cb_port);
152 /* check service, program, and version */
153 if(memcmp(service, "nfs", 3)) return -1;
154 *prog = atoi(program + 1); /* skip open paren */
155 *vers = atoi(version);
156 if((*prog != 100003) || ((*vers != 2) && (*vers != 3) && (*vers != 4)))
159 /* create service name */
160 inaddr = inet_addr(address);
161 if (!(ent = gethostbyaddr(&inaddr, sizeof(inaddr), AF_INET))) {
162 printerr(0, "ERROR: can't resolve server %s name\n", address);
165 if (!(*servername = calloc(strlen(ent->h_name) + 1, 1)))
167 memcpy(*servername, ent->h_name, strlen(ent->h_name));
168 snprintf(buf, INFOBUFLEN, "%s@%s", service, ent->h_name);
169 if (!(*servicename = calloc(strlen(buf) + 1, 1)))
171 memcpy(*servicename, buf, strlen(buf));
172 if (cb_port[0] != '\0')
173 *port = atoi(cb_port);
175 if (!(*protocol = strdup(protoname)))
179 printerr(0, "ERROR: failed to read service info\n");
180 if (fd != -1) close(fd);
181 if (*servername) free(*servername);
182 if (*servicename) free(*servicename);
183 if (*protocol) free(*protocol);
188 destroy_client(struct clnt_info *clp)
190 if (clp->krb5_poll_index != -1)
191 memset(&pollarray[clp->krb5_poll_index], 0,
192 sizeof(struct pollfd));
193 if (clp->spkm3_poll_index != -1)
194 memset(&pollarray[clp->spkm3_poll_index], 0,
195 sizeof(struct pollfd));
196 if (clp->dir_fd != -1) close(clp->dir_fd);
197 if (clp->krb5_fd != -1) close(clp->krb5_fd);
198 if (clp->spkm3_fd != -1) close(clp->spkm3_fd);
199 if (clp->dirname) free(clp->dirname);
200 if (clp->servicename) free(clp->servicename);
201 if (clp->servername) free(clp->servername);
202 if (clp->protocol) free(clp->protocol);
206 static struct clnt_info *
207 insert_new_clnt(void)
209 struct clnt_info *clp = NULL;
211 if (!(clp = (struct clnt_info *)calloc(1,sizeof(struct clnt_info)))) {
212 printerr(0, "ERROR: can't malloc clnt_info: %s\n",
216 clp->krb5_poll_index = -1;
217 clp->spkm3_poll_index = -1;
222 TAILQ_INSERT_HEAD(&clnt_list, clp, list);
228 process_clnt_dir_files(struct clnt_info * clp)
232 char info_file_name[32];
234 if (clp->krb5_fd == -1) {
235 snprintf(kname, sizeof(kname), "%s/krb5", clp->dirname);
236 clp->krb5_fd = open(kname, O_RDWR);
238 if (clp->spkm3_fd == -1) {
239 snprintf(sname, sizeof(sname), "%s/spkm3", clp->dirname);
240 clp->spkm3_fd = open(sname, O_RDWR);
242 if((clp->krb5_fd == -1) && (clp->spkm3_fd == -1))
244 snprintf(info_file_name, sizeof(info_file_name), "%s/info",
246 if ((clp->servicename == NULL) &&
247 read_service_info(info_file_name, &clp->servicename,
248 &clp->servername, &clp->prog, &clp->vers,
249 &clp->protocol, &clp->port))
255 get_poll_index(int *ind)
260 for (i=0; i<FD_ALLOC_BLOCK; i++) {
261 if (pollarray[i].events == 0) {
267 printerr(0, "ERROR: No pollarray slots open\n");
275 insert_clnt_poll(struct clnt_info *clp)
277 if ((clp->krb5_fd != -1) && (clp->krb5_poll_index == -1)) {
278 if (get_poll_index(&clp->krb5_poll_index)) {
279 printerr(0, "ERROR: Too many krb5 clients\n");
282 pollarray[clp->krb5_poll_index].fd = clp->krb5_fd;
283 pollarray[clp->krb5_poll_index].events |= POLLIN;
286 if ((clp->spkm3_fd != -1) && (clp->spkm3_poll_index == -1)) {
287 if (get_poll_index(&clp->spkm3_poll_index)) {
288 printerr(0, "ERROR: Too many spkm3 clients\n");
291 pollarray[clp->spkm3_poll_index].fd = clp->spkm3_fd;
292 pollarray[clp->spkm3_poll_index].events |= POLLIN;
299 process_clnt_dir(char *dir)
301 struct clnt_info * clp;
303 if (!(clp = insert_new_clnt()))
304 goto fail_destroy_client;
306 if (!(clp->dirname = calloc(strlen(dir) + 1, 1))) {
307 goto fail_destroy_client;
309 memcpy(clp->dirname, dir, strlen(dir));
310 if ((clp->dir_fd = open(clp->dirname, O_RDONLY)) == -1) {
311 printerr(0, "ERROR: can't open %s: %s\n",
312 clp->dirname, strerror(errno));
313 goto fail_destroy_client;
315 fcntl(clp->dir_fd, F_SETSIG, DNOTIFY_SIGNAL);
316 fcntl(clp->dir_fd, F_NOTIFY, DN_CREATE | DN_DELETE | DN_MULTISHOT);
318 if (process_clnt_dir_files(clp))
319 goto fail_keep_client;
321 if (insert_clnt_poll(clp))
322 goto fail_destroy_client;
328 TAILQ_REMOVE(&clnt_list, clp, list);
332 /* We couldn't find some subdirectories, but we keep the client
333 * around in case we get a notification on the directory when the
334 * subdirectories are created. */
339 init_client_list(void)
341 TAILQ_INIT(&clnt_list);
342 /* Eventually plan to grow/shrink poll array: */
343 pollsize = FD_ALLOC_BLOCK;
344 pollarray = calloc(pollsize, sizeof(struct pollfd));
348 * This is run after a DNOTIFY signal, and should clear up any
349 * directories that are no longer around, and re-scan any existing
350 * directories, since the DNOTIFY could have been in there.
353 update_old_clients(struct dirent **namelist, int size)
355 struct clnt_info *clp;
359 for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) {
361 for (i=0; i < size; i++) {
362 if (!strcmp(clp->dirname, namelist[i]->d_name)) {
368 printerr(2, "destroying client %s\n", clp->dirname);
369 saveprev = clp->list.tqe_prev;
370 TAILQ_REMOVE(&clnt_list, clp, list);
375 for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) {
376 if (!process_clnt_dir_files(clp))
377 insert_clnt_poll(clp);
381 /* Search for a client by directory name, return 1 if found, 0 otherwise */
383 find_client(char *dirname)
385 struct clnt_info *clp;
387 for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next)
388 if (!strcmp(clp->dirname, dirname))
393 /* Used to read (and re-read) list of clients, set up poll array. */
395 update_client_list(void)
397 struct dirent **namelist;
400 if (chdir(pipefs_nfsdir) < 0) {
401 printerr(0, "ERROR: can't chdir to %s: %s\n",
402 pipefs_nfsdir, strerror(errno));
406 j = scandir(pipefs_nfsdir, &namelist, NULL, alphasort);
408 printerr(0, "ERROR: can't scandir %s: %s\n",
409 pipefs_nfsdir, strerror(errno));
412 update_old_clients(namelist, j);
413 for (i=0; i < j; i++) {
414 if (i < FD_ALLOC_BLOCK
415 && !strncmp(namelist[i]->d_name, "clnt", 4)
416 && !find_client(namelist[i]->d_name))
417 process_clnt_dir(namelist[i]->d_name);
426 do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd,
427 gss_buffer_desc *context_token)
429 char *buf = NULL, *p = NULL, *end = NULL;
430 unsigned int timeout = 0; /* XXX decide on a reasonable value */
431 unsigned int buf_size = 0;
433 printerr(1, "doing downcall\n");
434 buf_size = sizeof(uid) + sizeof(timeout) + sizeof(pd->pd_seq_win) +
435 sizeof(pd->pd_ctx_hndl.length) + pd->pd_ctx_hndl.length +
436 sizeof(context_token->length) + context_token->length;
437 p = buf = malloc(buf_size);
438 end = buf + buf_size;
440 if (WRITE_BYTES(&p, end, uid)) goto out_err;
441 /* Not setting any timeout for now: */
442 if (WRITE_BYTES(&p, end, timeout)) goto out_err;
443 if (WRITE_BYTES(&p, end, pd->pd_seq_win)) goto out_err;
444 if (write_buffer(&p, end, &pd->pd_ctx_hndl)) goto out_err;
445 if (write_buffer(&p, end, context_token)) goto out_err;
447 if (write(k5_fd, buf, p - buf) < p - buf) goto out_err;
452 printerr(0, "Failed to write downcall!\n");
457 do_error_downcall(int k5_fd, uid_t uid, int err)
460 char *p = buf, *end = buf + 1024;
461 unsigned int timeout = 0;
464 printerr(1, "doing error downcall\n");
466 if (WRITE_BYTES(&p, end, uid)) goto out_err;
467 if (WRITE_BYTES(&p, end, timeout)) goto out_err;
468 /* use seq_win = 0 to indicate an error: */
469 if (WRITE_BYTES(&p, end, zero)) goto out_err;
470 if (WRITE_BYTES(&p, end, err)) goto out_err;
472 if (write(k5_fd, buf, p - buf) < p - buf) goto out_err;
475 printerr(1, "Failed to write error downcall!\n");
480 * Create an RPC connection and establish an authenticated
481 * gss context with a server.
483 int create_auth_rpc_client(struct clnt_info *clp,
484 CLIENT **clnt_return,
489 CLIENT *rpc_clnt = NULL;
490 struct rpc_gss_sec sec;
496 char rpc_errmsg[1024];
497 int sockp = RPC_ANYSOCK;
498 int sendsz = 32768, recvsz = 32768;
499 struct addrinfo ai_hints, *a = NULL;
503 /* Create the context as the user (not as root) */
504 save_uid = geteuid();
505 if (setfsuid(uid) != 0) {
506 printerr(0, "WARNING: Failed to setfsuid for "
507 "user with uid %d\n", uid);
510 printerr(2, "creating context using fsuid %d (save_uid %d)\n",
513 sec.qop = GSS_C_QOP_DEFAULT;
514 sec.svc = RPCSEC_GSS_SVC_NONE;
515 sec.cred = GSS_C_NO_CREDENTIAL;
517 if (authtype == AUTHTYPE_KRB5) {
518 sec.mech = (gss_OID)&krb5oid;
519 sec.req_flags = GSS_C_MUTUAL_FLAG;
521 else if (authtype == AUTHTYPE_SPKM3) {
522 sec.mech = (gss_OID)&spkm3oid;
523 /* XXX sec.req_flags = GSS_C_ANON_FLAG;
524 * Need a way to switch....
526 sec.req_flags = GSS_C_MUTUAL_FLAG;
529 printerr(0, "ERROR: Invalid authentication type (%d) "
530 "in create_auth_rpc_client\n", authtype);
535 if (authtype == AUTHTYPE_KRB5) {
536 #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
538 * Do this before creating rpc connection since we won't need
539 * rpc connection if it fails!
541 if (limit_krb5_enctypes(&sec, uid)) {
542 printerr(1, "WARNING: Failed while limiting krb5 "
543 "encryption types for user with uid %d\n",
550 /* create an rpc connection to the nfs server */
552 printerr(2, "creating %s client for server %s\n", clp->protocol,
555 memset(&ai_hints, '\0', sizeof(ai_hints));
556 ai_hints.ai_family = PF_INET;
557 ai_hints.ai_flags |= AI_CANONNAME;
558 if ((strcmp(clp->protocol, "tcp")) == 0) {
559 ai_hints.ai_socktype = SOCK_STREAM;
560 ai_hints.ai_protocol = IPPROTO_TCP;
561 } else if ((strcmp(clp->protocol, "udp")) == 0) {
562 ai_hints.ai_socktype = SOCK_DGRAM;
563 ai_hints.ai_protocol = IPPROTO_UDP;
565 printerr(0, "WARNING: unrecognized protocol, '%s', requested "
566 "for connection to server %s for user with uid %d\n",
567 clp->protocol, clp->servername, uid);
571 /* extract the service name from clp->servicename */
572 if ((at_sign = strchr(clp->servicename, '@')) == NULL) {
573 printerr(0, "WARNING: servicename (%s) not formatted as "
574 "expected with service@host\n", clp->servicename);
577 if ((at_sign - clp->servicename) >= sizeof(service)) {
578 printerr(0, "WARNING: service portion of servicename (%s) "
579 "is too long!\n", clp->servicename);
582 strncpy(service, clp->servicename, at_sign - clp->servicename);
583 service[at_sign - clp->servicename] = '\0';
585 errcode = getaddrinfo(clp->servername, service, &ai_hints, &a);
587 printerr(0, "WARNING: Error from getaddrinfo for server "
588 "'%s': %s\n", clp->servername, gai_strerror(errcode));
593 printerr(0, "WARNING: No address information found for "
594 "connection to server %s for user with uid %d\n",
595 clp->servername, uid);
599 ((struct sockaddr_in *)a->ai_addr)->sin_port = htons(clp->port);
600 if (a->ai_protocol == IPPROTO_TCP) {
601 if ((rpc_clnt = clnttcp_create(
602 (struct sockaddr_in *) a->ai_addr,
603 clp->prog, clp->vers, &sockp,
604 sendsz, recvsz)) == NULL) {
605 snprintf(rpc_errmsg, sizeof(rpc_errmsg),
606 "WARNING: can't create tcp rpc_clnt "
607 "for server %s for user with uid %d",
608 clp->servername, uid);
610 clnt_spcreateerror(rpc_errmsg));
613 } else if (a->ai_protocol == IPPROTO_UDP) {
614 const struct timeval timeout = {5, 0};
615 if ((rpc_clnt = clntudp_bufcreate(
616 (struct sockaddr_in *) a->ai_addr,
617 clp->prog, clp->vers, timeout,
618 &sockp, sendsz, recvsz)) == NULL) {
619 snprintf(rpc_errmsg, sizeof(rpc_errmsg),
620 "WARNING: can't create udp rpc_clnt "
621 "for server %s for user with uid %d",
622 clp->servername, uid);
624 clnt_spcreateerror(rpc_errmsg));
628 /* Shouldn't happen! */
629 printerr(0, "ERROR: requested protocol '%s', but "
630 "got addrinfo with protocol %d\n",
631 clp->protocol, a->ai_protocol);
634 /* We're done with this */
638 printerr(2, "creating context with server %s\n", clp->servicename);
639 auth = authgss_create_default(rpc_clnt, clp->servicename, &sec);
641 /* Our caller should print appropriate message */
642 printerr(2, "WARNING: Failed to create %s context for "
643 "user with uid %d for server %s\n",
644 (authtype == AUTHTYPE_KRB5 ? "krb5":"spkm3"),
645 uid, clp->servername);
650 rpc_clnt->cl_auth = auth;
651 *clnt_return = rpc_clnt;
656 if (sec.cred != GSS_C_NO_CREDENTIAL)
657 gss_release_cred(&min_stat, &sec.cred);
658 if (a != NULL) freeaddrinfo(a);
659 /* Restore euid to original value */
660 if ((save_uid != -1) && (setfsuid(save_uid) != uid)) {
661 printerr(0, "WARNING: Failed to restore fsuid"
662 " to uid %d from %d\n", save_uid, uid);
667 /* Only destroy here if failure. Otherwise, caller is responsible */
668 if (rpc_clnt) clnt_destroy(rpc_clnt);
675 * this code uses the userland rpcsec gss library to create a krb5
676 * context on behalf of the kernel
679 handle_krb5_upcall(struct clnt_info *clp)
682 CLIENT *rpc_clnt = NULL;
684 struct authgss_private_data pd;
685 gss_buffer_desc token;
686 char **credlist = NULL;
689 int create_resp = -1;
691 printerr(1, "handling krb5 upcall\n");
695 memset(&pd, 0, sizeof(struct authgss_private_data));
697 if (read(clp->krb5_fd, &uid, sizeof(uid)) < sizeof(uid)) {
698 printerr(0, "WARNING: failed reading uid from krb5 "
699 "upcall pipe: %s\n", strerror(errno));
703 if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0)) {
704 /* Tell krb5 gss which credentials cache to use */
705 for (dirname = ccachesearch; *dirname != NULL; dirname++) {
706 gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname);
708 create_resp = create_auth_rpc_client(clp, &rpc_clnt, &auth, uid,
710 if (create_resp == 0)
714 if (create_resp != 0) {
715 if (uid == 0 && root_uses_machine_creds == 1) {
718 gssd_refresh_krb5_machine_credential(clp->servername,
721 * Get a list of credential cache names and try each
722 * of them until one works or we've tried them all
724 if (gssd_get_krb5_machine_cred_list(&credlist)) {
725 printerr(0, "ERROR: No credentials found "
726 "for connection to server %s\n",
728 goto out_return_error;
730 for (ccname = credlist; ccname && *ccname; ccname++) {
731 gssd_setup_krb5_machine_gss_ccache(*ccname);
732 if ((create_auth_rpc_client(clp, &rpc_clnt,
734 AUTHTYPE_KRB5)) == 0) {
739 printerr(2, "WARNING: Failed to create krb5 context "
740 "for user with uid %d with credentials "
741 "cache %s for server %s\n",
742 uid, *ccname, clp->servername);
744 gssd_free_krb5_machine_cred_list(credlist);
746 printerr(0, "WARNING: Failed to create krb5 context "
747 "for user with uid %d with any "
748 "credentials cache for server %s\n",
749 uid, clp->servername);
750 goto out_return_error;
753 printerr(0, "WARNING: Failed to create krb5 context "
754 "for user with uid %d for server %s\n",
755 uid, clp->servername);
756 goto out_return_error;
760 if (!authgss_get_private_data(auth, &pd)) {
761 printerr(0, "WARNING: Failed to obtain authentication "
762 "data for user with uid %d for server %s\n",
763 uid, clp->servername);
764 goto out_return_error;
767 if (serialize_context_for_kernel(pd.pd_ctx, &token, &krb5oid)) {
768 printerr(0, "WARNING: Failed to serialize krb5 context for "
769 "user with uid %d for server %s\n",
770 uid, clp->servername);
771 goto out_return_error;
774 do_downcall(clp->krb5_fd, uid, &pd, &token);
779 if (pd.pd_ctx_hndl.length != 0)
780 authgss_free_private_data(&pd);
784 clnt_destroy(rpc_clnt);
788 do_error_downcall(clp->krb5_fd, uid, -1);
793 * this code uses the userland rpcsec gss library to create an spkm3
794 * context on behalf of the kernel
797 handle_spkm3_upcall(struct clnt_info *clp)
800 CLIENT *rpc_clnt = NULL;
802 struct authgss_private_data pd;
803 gss_buffer_desc token;
805 printerr(2, "handling spkm3 upcall\n");
810 if (read(clp->spkm3_fd, &uid, sizeof(uid)) < sizeof(uid)) {
811 printerr(0, "WARNING: failed reading uid from spkm3 "
812 "upcall pipe: %s\n", strerror(errno));
816 if (create_auth_rpc_client(clp, &rpc_clnt, &auth, uid, AUTHTYPE_SPKM3)) {
817 printerr(0, "WARNING: Failed to create spkm3 context for "
818 "user with uid %d\n", uid);
819 goto out_return_error;
822 if (!authgss_get_private_data(auth, &pd)) {
823 printerr(0, "WARNING: Failed to obtain authentication "
824 "data for user with uid %d for server %s\n",
825 uid, clp->servername);
826 goto out_return_error;
829 if (serialize_context_for_kernel(pd.pd_ctx, &token, &spkm3oid)) {
830 printerr(0, "WARNING: Failed to serialize spkm3 context for "
831 "user with uid %d for server\n",
832 uid, clp->servername);
833 goto out_return_error;
836 do_downcall(clp->spkm3_fd, uid, &pd, &token);
844 clnt_destroy(rpc_clnt);
848 do_error_downcall(clp->spkm3_fd, uid, -1);