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>
62 #include <gssapi/gssapi.h>
69 #include "krb5_util.h"
74 * array of struct pollfd suitable to pass to poll. initialized to
75 * zero - a zero struct is ignored by poll() because the events mask is 0.
78 * linked list of struct clnt_info which associates a clntXXX directory
79 * with an index into pollarray[], and other basic data about that client.
81 * Directory structure: created by the kernel nfs client
82 * /pipefsdir/clntXX : one per rpc_clnt struct in the kernel
83 * /pipefsdir/clntXX/krb5 : read uid for which kernel wants
84 * a context, write the resulting context
85 * /pipefsdir/clntXX/info : stores info such as server name
88 * Poll all /pipefsdir/clntXX/krb5 files. When ready, data read
89 * is a uid; performs rpcsec_gss context initialization protocol to
90 * get a cred for that user. Writes result to corresponding krb5 file
91 * in a form the kernel code will understand.
92 * In addition, we make sure we are notified whenever anything is
93 * created or destroyed in pipefsdir/ or in an of the clntXX directories,
94 * and rescan the whole pipefsdir when this happens.
97 struct pollfd * pollarray;
99 int pollsize; /* the size of pollaray (in pollfd's) */
101 /* XXX buffer problems: */
103 read_service_info(char *info_file_name, char **servicename, char **servername,
104 int *prog, int *vers, char **protocol) {
105 #define INFOBUFLEN 256
106 char buf[INFOBUFLEN];
107 static char dummy[128];
109 static char service[128];
110 static char address[128];
116 struct hostent *ent = NULL;
119 *servicename = *servername = *protocol = NULL;
121 if ((fd = open(info_file_name, O_RDONLY)) == -1) {
122 printerr(0, "ERROR: can't open %s: %s\n", info_file_name,
126 if ((nbytes = read(fd, buf, INFOBUFLEN)) == -1)
130 numfields = sscanf(buf,"RPC server: %127s\n"
131 "service: %127s %15s version %15s\n"
135 service, program, version,
139 if (numfields == 5) {
140 strcpy(protoname, "tcp");
141 } else if (numfields != 6) {
145 /* check service, program, and version */
146 if(memcmp(service, "nfs", 3)) return -1;
147 *prog = atoi(program + 1); /* skip open paren */
148 *vers = atoi(version);
149 if((*prog != 100003) || ((*vers != 2) && (*vers != 3) && (*vers != 4)))
152 /* create service name */
153 inaddr = inet_addr(address);
154 if (!(ent = gethostbyaddr(&inaddr, sizeof(inaddr), AF_INET))) {
155 printerr(0, "ERROR: can't resolve server %s name\n", address);
158 if (!(*servername = calloc(strlen(ent->h_name) + 1, 1)))
160 memcpy(*servername, ent->h_name, strlen(ent->h_name));
161 snprintf(buf, INFOBUFLEN, "%s@%s", service, ent->h_name);
162 if (!(*servicename = calloc(strlen(buf) + 1, 1)))
164 memcpy(*servicename, buf, strlen(buf));
166 if (!(*protocol = strdup(protoname)))
170 printerr(0, "ERROR: failed to read service info\n");
171 if (fd != -1) close(fd);
172 if (*servername) free(*servername);
173 if (*servicename) free(*servicename);
174 if (*protocol) free(*protocol);
179 destroy_client(struct clnt_info *clp)
181 if (clp->krb5_poll_index != -1)
182 memset(&pollarray[clp->krb5_poll_index], 0,
183 sizeof(struct pollfd));
184 if (clp->spkm3_poll_index != -1)
185 memset(&pollarray[clp->spkm3_poll_index], 0,
186 sizeof(struct pollfd));
187 if (clp->dir_fd != -1) close(clp->dir_fd);
188 if (clp->krb5_fd != -1) close(clp->krb5_fd);
189 if (clp->spkm3_fd != -1) close(clp->spkm3_fd);
190 if (clp->dirname) free(clp->dirname);
191 if (clp->servicename) free(clp->servicename);
192 if (clp->servername) free(clp->servername);
193 if (clp->protocol) free(clp->protocol);
197 static struct clnt_info *
198 insert_new_clnt(void)
200 struct clnt_info *clp = NULL;
202 if (!(clp = (struct clnt_info *)calloc(1,sizeof(struct clnt_info)))) {
203 printerr(0, "ERROR: can't malloc clnt_info: %s\n",
207 clp->krb5_poll_index = -1;
208 clp->spkm3_poll_index = -1;
213 TAILQ_INSERT_HEAD(&clnt_list, clp, list);
219 process_clnt_dir_files(struct clnt_info * clp)
223 char info_file_name[32];
225 if (clp->krb5_fd == -1) {
226 snprintf(kname, sizeof(kname), "%s/krb5", clp->dirname);
227 clp->krb5_fd = open(kname, O_RDWR);
229 if (clp->spkm3_fd == -1) {
230 snprintf(sname, sizeof(sname), "%s/spkm3", clp->dirname);
231 clp->spkm3_fd = open(sname, O_RDWR);
233 if((clp->krb5_fd == -1) && (clp->spkm3_fd == -1))
235 snprintf(info_file_name, sizeof(info_file_name), "%s/info",
237 if ((clp->servicename == NULL) &&
238 read_service_info(info_file_name, &clp->servicename,
239 &clp->servername, &clp->prog, &clp->vers,
246 get_poll_index(int *ind)
251 for (i=0; i<FD_ALLOC_BLOCK; i++) {
252 if (pollarray[i].events == 0) {
258 printerr(0, "ERROR: No pollarray slots open\n");
266 insert_clnt_poll(struct clnt_info *clp)
268 if ((clp->krb5_fd != -1) && (clp->krb5_poll_index == -1)) {
269 if (get_poll_index(&clp->krb5_poll_index)) {
270 printerr(0, "ERROR: Too many krb5 clients\n");
273 pollarray[clp->krb5_poll_index].fd = clp->krb5_fd;
274 pollarray[clp->krb5_poll_index].events |= POLLIN;
277 if ((clp->spkm3_fd != -1) && (clp->spkm3_poll_index == -1)) {
278 if (get_poll_index(&clp->spkm3_poll_index)) {
279 printerr(0, "ERROR: Too many spkm3 clients\n");
282 pollarray[clp->spkm3_poll_index].fd = clp->spkm3_fd;
283 pollarray[clp->spkm3_poll_index].events |= POLLIN;
290 process_clnt_dir(char *dir)
292 struct clnt_info * clp;
294 if (!(clp = insert_new_clnt()))
295 goto fail_destroy_client;
297 if (!(clp->dirname = calloc(strlen(dir) + 1, 1))) {
298 goto fail_destroy_client;
300 memcpy(clp->dirname, dir, strlen(dir));
301 if ((clp->dir_fd = open(clp->dirname, O_RDONLY)) == -1) {
302 printerr(0, "ERROR: can't open %s: %s\n",
303 clp->dirname, strerror(errno));
304 goto fail_destroy_client;
306 fcntl(clp->dir_fd, F_SETSIG, DNOTIFY_SIGNAL);
307 fcntl(clp->dir_fd, F_NOTIFY, DN_CREATE | DN_DELETE | DN_MULTISHOT);
309 if (process_clnt_dir_files(clp))
310 goto fail_keep_client;
312 if (insert_clnt_poll(clp))
313 goto fail_destroy_client;
319 TAILQ_REMOVE(&clnt_list, clp, list);
323 /* We couldn't find some subdirectories, but we keep the client
324 * around in case we get a notification on the directory when the
325 * subdirectories are created. */
330 init_client_list(void)
332 TAILQ_INIT(&clnt_list);
333 /* Eventually plan to grow/shrink poll array: */
334 pollsize = FD_ALLOC_BLOCK;
335 pollarray = calloc(pollsize, sizeof(struct pollfd));
339 * This is run after a DNOTIFY signal, and should clear up any
340 * directories that are no longer around, and re-scan any existing
341 * directories, since the DNOTIFY could have been in there.
344 update_old_clients(struct dirent **namelist, int size)
346 struct clnt_info *clp;
350 for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) {
352 for (i=0; i < size; i++) {
353 if (!strcmp(clp->dirname, namelist[i]->d_name)) {
359 printerr(2, "destroying client %s\n", clp->dirname);
360 saveprev = clp->list.tqe_prev;
361 TAILQ_REMOVE(&clnt_list, clp, list);
366 for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) {
367 if (!process_clnt_dir_files(clp))
368 insert_clnt_poll(clp);
372 /* Search for a client by directory name, return 1 if found, 0 otherwise */
374 find_client(char *dirname)
376 struct clnt_info *clp;
378 for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next)
379 if (!strcmp(clp->dirname, dirname))
384 /* Used to read (and re-read) list of clients, set up poll array. */
386 update_client_list(void)
388 struct dirent **namelist;
391 if (chdir(pipefsdir) < 0) {
392 printerr(0, "ERROR: can't chdir to %s: %s\n",
393 pipefsdir, strerror(errno));
397 j = scandir(pipefsdir, &namelist, NULL, alphasort);
399 printerr(0, "ERROR: can't scandir %s: %s\n",
400 pipefsdir, strerror(errno));
403 update_old_clients(namelist, j);
404 for (i=0; i < j; i++) {
405 if (i < FD_ALLOC_BLOCK
406 && !strncmp(namelist[i]->d_name, "clnt", 4)
407 && !find_client(namelist[i]->d_name))
408 process_clnt_dir(namelist[i]->d_name);
417 do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd,
418 gss_buffer_desc *context_token)
420 char *buf = NULL, *p = NULL, *end = NULL;
421 unsigned int timeout = 0; /* XXX decide on a reasonable value */
422 unsigned int buf_size = 0;
424 printerr(1, "doing downcall\n");
425 buf_size = sizeof(uid) + sizeof(timeout) + sizeof(pd->pd_seq_win) +
426 sizeof(pd->pd_ctx_hndl.length) + pd->pd_ctx_hndl.length +
427 sizeof(context_token->length) + context_token->length;
428 p = buf = malloc(buf_size);
429 end = buf + buf_size;
431 if (WRITE_BYTES(&p, end, uid)) goto out_err;
432 /* Not setting any timeout for now: */
433 if (WRITE_BYTES(&p, end, timeout)) goto out_err;
434 if (WRITE_BYTES(&p, end, pd->pd_seq_win)) goto out_err;
435 if (write_buffer(&p, end, &pd->pd_ctx_hndl)) goto out_err;
436 if (write_buffer(&p, end, context_token)) goto out_err;
438 if (write(k5_fd, buf, p - buf) < p - buf) goto out_err;
443 printerr(0, "Failed to write downcall!\n");
448 do_error_downcall(int k5_fd, uid_t uid, int err)
451 char *p = buf, *end = buf + 1024;
452 unsigned int timeout = 0;
455 printerr(1, "doing error downcall\n");
457 if (WRITE_BYTES(&p, end, uid)) goto out_err;
458 if (WRITE_BYTES(&p, end, timeout)) goto out_err;
459 /* use seq_win = 0 to indicate an error: */
460 if (WRITE_BYTES(&p, end, zero)) goto out_err;
461 if (WRITE_BYTES(&p, end, err)) goto out_err;
463 if (write(k5_fd, buf, p - buf) < p - buf) goto out_err;
466 printerr(0, "Failed to write error downcall!\n");
471 * Create an RPC connection and establish an authenticated
472 * gss context with a server.
474 int create_auth_rpc_client(struct clnt_info *clp,
475 CLIENT **clnt_return,
480 CLIENT *rpc_clnt = NULL;
481 struct rpc_gss_sec sec;
487 char rpc_errmsg[1024];
488 int sockp = RPC_ANYSOCK;
489 int sendsz = 32768, recvsz = 32768;
490 struct addrinfo ai_hints, *a = NULL;
494 /* Create the context as the user (not as root) */
495 save_uid = geteuid();
496 if (setfsuid(uid) != 0) {
497 printerr(0, "WARNING: Failed to setfsuid for "
498 "user with uid %d\n", uid);
501 printerr(2, "creating context using fsuid %d (save_uid %d)\n",
504 sec.qop = GSS_C_QOP_DEFAULT;
505 sec.svc = RPCSEC_GSS_SVC_NONE;
506 sec.cred = GSS_C_NO_CREDENTIAL;
508 if (authtype == AUTHTYPE_KRB5) {
509 sec.mech = (gss_OID)&krb5oid;
510 sec.req_flags = GSS_C_MUTUAL_FLAG;
512 else if (authtype == AUTHTYPE_SPKM3) {
513 sec.mech = (gss_OID)&spkm3oid;
514 /* XXX sec.req_flags = GSS_C_ANON_FLAG;
515 * Need a way to switch....
517 sec.req_flags = GSS_C_MUTUAL_FLAG;
520 printerr(0, "ERROR: Invalid authentication type (%d) "
521 "in create_auth_rpc_client\n", authtype);
526 if (authtype == AUTHTYPE_KRB5) {
527 #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
529 * Do this before creating rpc connection since we won't need
530 * rpc connection if it fails!
532 if (limit_krb5_enctypes(&sec, uid)) {
533 printerr(1, "WARNING: Failed while limiting krb5 "
534 "encryption types for user with uid %d\n",
541 /* create an rpc connection to the nfs server */
543 printerr(2, "creating %s client for server %s\n", clp->protocol,
546 memset(&ai_hints, '\0', sizeof(ai_hints));
547 ai_hints.ai_family = PF_INET;
548 ai_hints.ai_flags |= AI_CANONNAME;
549 if ((strcmp(clp->protocol, "tcp")) == 0) {
550 ai_hints.ai_socktype = SOCK_STREAM;
551 ai_hints.ai_protocol = IPPROTO_TCP;
552 } else if ((strcmp(clp->protocol, "udp")) == 0) {
553 ai_hints.ai_socktype = SOCK_DGRAM;
554 ai_hints.ai_protocol = IPPROTO_UDP;
556 printerr(0, "WARNING: unrecognized protocol, '%s', requested "
557 "for connection to server %s for user with uid %d",
558 clp->protocol, clp->servername, uid);
562 /* extract the service name from clp->servicename */
563 if ((at_sign = strchr(clp->servicename, '@')) == NULL) {
564 printerr(0, "WARNING: servicename (%s) not formatted as "
565 "expected with service@host", clp->servicename);
568 if ((at_sign - clp->servicename) >= sizeof(service)) {
569 printerr(0, "WARNING: service portion of servicename (%s) "
570 "is too long!", clp->servicename);
573 strncpy(service, clp->servicename, at_sign - clp->servicename);
574 service[at_sign - clp->servicename] = '\0';
576 errcode = getaddrinfo(clp->servername, service, &ai_hints, &a);
578 printerr(0, "WARNING: Error from getaddrinfo for server "
579 "'%s': %s", clp->servername, gai_strerror(errcode));
584 printerr(0, "WARNING: No address information found for "
585 "connection to server %s for user with uid %d",
586 clp->servername, uid);
589 if (a->ai_protocol == IPPROTO_TCP) {
590 if ((rpc_clnt = clnttcp_create(
591 (struct sockaddr_in *) a->ai_addr,
592 clp->prog, clp->vers, &sockp,
593 sendsz, recvsz)) == NULL) {
594 snprintf(rpc_errmsg, sizeof(rpc_errmsg),
595 "WARNING: can't create tcp rpc_clnt "
596 "for server %s for user with uid %d",
597 clp->servername, uid);
599 clnt_spcreateerror(rpc_errmsg));
602 } else if (a->ai_protocol == IPPROTO_UDP) {
603 const struct timeval timeout = {5, 0};
604 if ((rpc_clnt = clntudp_bufcreate(
605 (struct sockaddr_in *) a->ai_addr,
606 clp->prog, clp->vers, timeout,
607 &sockp, sendsz, recvsz)) == NULL) {
608 snprintf(rpc_errmsg, sizeof(rpc_errmsg),
609 "WARNING: can't create udp rpc_clnt "
610 "for server %s for user with uid %d",
611 clp->servername, uid);
613 clnt_spcreateerror(rpc_errmsg));
617 /* Shouldn't happen! */
618 printerr(0, "ERROR: requested protocol '%s', but "
619 "got addrinfo with protocol %d",
620 clp->protocol, a->ai_protocol);
623 /* We're done with this */
627 printerr(2, "creating context with server %s\n", clp->servicename);
628 auth = authgss_create_default(rpc_clnt, clp->servicename, &sec);
630 /* Our caller should print appropriate message */
631 printerr(2, "WARNING: Failed to create %s context for "
632 "user with uid %d for server %s\n",
633 (authtype == AUTHTYPE_KRB5 ? "krb5":"spkm3"),
634 uid, clp->servername);
639 rpc_clnt->cl_auth = auth;
640 *clnt_return = rpc_clnt;
645 if (sec.cred != GSS_C_NO_CREDENTIAL)
646 gss_release_cred(&min_stat, &sec.cred);
647 if (a != NULL) freeaddrinfo(a);
648 /* Restore euid to original value */
649 if ((save_uid != -1) && (setfsuid(save_uid) != uid)) {
650 printerr(0, "WARNING: Failed to restore fsuid"
651 " to uid %d from %d\n", save_uid, uid);
656 /* Only destroy here if failure. Otherwise, caller is responsible */
657 if (rpc_clnt) clnt_destroy(rpc_clnt);
664 * this code uses the userland rpcsec gss library to create a krb5
665 * context on behalf of the kernel
668 handle_krb5_upcall(struct clnt_info *clp)
671 CLIENT *rpc_clnt = NULL;
673 struct authgss_private_data pd;
674 gss_buffer_desc token;
675 char **credlist = NULL;
678 printerr(1, "handling krb5 upcall\n");
682 memset(&pd, 0, sizeof(struct authgss_private_data));
684 if (read(clp->krb5_fd, &uid, sizeof(uid)) < sizeof(uid)) {
685 printerr(0, "WARNING: failed reading uid from krb5 "
686 "upcall pipe: %s\n", strerror(errno));
694 * Get a list of credential cache names and try each
695 * of them until one works or we've tried them all
697 if (gssd_get_krb5_machine_cred_list(&credlist)) {
698 printerr(0, "WARNING: Failed to obtain machine "
699 "credentials for connection to "
700 "server %s\n", clp->servername);
701 goto out_return_error;
703 for (ccname = credlist; ccname && *ccname; ccname++) {
704 gssd_setup_krb5_machine_gss_ccache(*ccname);
705 if ((create_auth_rpc_client(clp, &rpc_clnt, &auth, uid,
706 AUTHTYPE_KRB5)) == 0) {
711 printerr(2, "WARNING: Failed to create krb5 context "
712 "for user with uid %d with credentials "
713 "cache %s for server %s\n",
714 uid, *ccname, clp->servername);
716 gssd_free_krb5_machine_cred_list(credlist);
718 printerr(0, "WARNING: Failed to create krb5 context "
719 "for user with uid %d with any "
720 "credentials cache for server %s\n",
721 uid, clp->servername);
722 goto out_return_error;
726 /* Tell krb5 gss which credentials cache to use */
727 gssd_setup_krb5_user_gss_ccache(uid, clp->servername);
729 if ((create_auth_rpc_client(clp, &rpc_clnt, &auth, uid,
730 AUTHTYPE_KRB5)) != 0) {
731 printerr(0, "WARNING: Failed to create krb5 context "
732 "for user with uid %d for server %s\n",
733 uid, clp->servername);
734 goto out_return_error;
738 if (!authgss_get_private_data(auth, &pd)) {
739 printerr(0, "WARNING: Failed to obtain authentication "
740 "data for user with uid %d for server %s\n",
741 uid, clp->servername);
742 goto out_return_error;
745 if (serialize_context_for_kernel(pd.pd_ctx, &token, &krb5oid)) {
746 printerr(0, "WARNING: Failed to serialize krb5 context for "
747 "user with uid %d for server %s\n",
748 uid, clp->servername);
749 goto out_return_error;
752 do_downcall(clp->krb5_fd, uid, &pd, &token);
757 if (pd.pd_ctx_hndl.length != 0)
758 authgss_free_private_data(&pd);
762 clnt_destroy(rpc_clnt);
766 do_error_downcall(clp->krb5_fd, uid, -1);
771 * this code uses the userland rpcsec gss library to create an spkm3
772 * context on behalf of the kernel
775 handle_spkm3_upcall(struct clnt_info *clp)
778 CLIENT *rpc_clnt = NULL;
780 struct authgss_private_data pd;
781 gss_buffer_desc token;
783 printerr(2, "handling spkm3 upcall\n");
788 if (read(clp->spkm3_fd, &uid, sizeof(uid)) < sizeof(uid)) {
789 printerr(0, "WARNING: failed reading uid from spkm3 "
790 "upcall pipe: %s\n", strerror(errno));
794 if (create_auth_rpc_client(clp, &rpc_clnt, &auth, uid, AUTHTYPE_SPKM3)) {
795 printerr(0, "WARNING: Failed to create spkm3 context for "
796 "user with uid %d\n", uid);
797 goto out_return_error;
800 if (!authgss_get_private_data(auth, &pd)) {
801 printerr(0, "WARNING: Failed to obtain authentication "
802 "data for user with uid %d for server %s\n",
803 uid, clp->servername);
804 goto out_return_error;
807 if (serialize_context_for_kernel(pd.pd_ctx, &token, &spkm3oid)) {
808 printerr(0, "WARNING: Failed to serialize spkm3 context for "
809 "user with uid %d for server\n",
810 uid, clp->servername);
811 goto out_return_error;
814 do_downcall(clp->spkm3_fd, uid, &pd, &token);
822 clnt_destroy(rpc_clnt);
826 do_error_downcall(clp->spkm3_fd, uid, -1);