4 * Userland daemon for idmap.
6 * Copyright (c) 2002 The Regents of the University of Michigan.
9 * Marius Aamodt Eriksen <marius@umich.edu>
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
25 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
26 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
31 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 #include <sys/types.h>
40 #include <sys/socket.h>
44 #include "nfs_idmap.h"
65 #endif /* HAVE_CONFIG_H */
72 #define PIPEFS_DIR "/var/lib/nfs/rpc_pipefs/"
76 #define NFSD_DIR "/proc/net/rpc"
79 #ifndef NFS4NOBODY_USER
80 #define NFS4NOBODY_USER "nobody"
83 #ifndef NFS4NOBODY_GROUP
84 #define NFS4NOBODY_GROUP "nobody"
88 #define CONF_SAVE(w, f) do { \
95 #define IC_IDNAME_CHAN NFSD_DIR "/nfs4.idtoname/channel"
96 #define IC_IDNAME_FLUSH NFSD_DIR "/nfs4.idtoname/flush"
99 #define IC_NAMEID_CHAN NFSD_DIR "/nfs4.nametoid/channel"
100 #define IC_NAMEID_FLUSH NFSD_DIR "/nfs4.nametoid/flush"
102 struct idmap_client {
106 char ic_path[PATH_MAX];
110 struct event ic_event;
111 TAILQ_ENTRY(idmap_client) ic_next;
113 static struct idmap_client nfsd_ic[2] = {
114 {IC_IDNAME, "Server", "", IC_IDNAME_CHAN, -1, -1, 0},
115 {IC_NAMEID, "Server", "", IC_NAMEID_CHAN, -1, -1, 0},
118 TAILQ_HEAD(idmap_clientq, idmap_client);
120 static void dirscancb(int, short, void *);
121 static void clntscancb(int, short, void *);
122 static void svrreopen(int, short, void *);
123 static int nfsopen(struct idmap_client *);
124 static void nfscb(int, short, void *);
125 static void nfsdcb(int, short, void *);
126 static int validateascii(char *, u_int32_t);
127 static int addfield(char **, ssize_t *, char *);
128 static int getfield(char **, char *, size_t);
130 static void imconv(struct idmap_client *, struct idmap_msg *);
131 static void idtonameres(struct idmap_msg *);
132 static void nametoidres(struct idmap_msg *);
134 static int nfsdopen(char *);
135 static int nfsdopenone(struct idmap_client *);
136 static void nfsdreopen(void);
138 size_t strlcat(char *, const char *, size_t);
139 size_t strlcpy(char *, const char *, size_t);
140 ssize_t atomicio(ssize_t (*)(), int, void *, size_t);
141 void mydaemon(int, int);
142 void release_parent();
144 static int verbose = 0;
145 static char pipefsdir[PATH_MAX];
146 static char *nobodyuser, *nobodygroup;
147 static uid_t nobodyuid;
148 static gid_t nobodygid;
154 flush_nfsd_cache(char *path, time_t now)
159 sprintf(stime, "%ld\n", now);
160 fd = open(path, O_RDWR);
163 write(fd, stime, strlen(stime));
169 flush_nfsd_idmap_cache(void)
171 time_t now = time(NULL);
174 ret = flush_nfsd_cache(IC_IDNAME_FLUSH, now);
177 ret = flush_nfsd_cache(IC_NAMEID_FLUSH, now);
182 main(int argc, char **argv)
184 int fd = 0, opt, fg = 0, nfsdret = -1;
185 struct idmap_clientq icq;
186 struct event rootdirev, clntdirev, svrdirev;
187 struct event initialize;
191 char *xpipefsdir = NULL;
192 int serverstart = 1, clientstart = 1;
195 conf_path = _PATH_IDMAPDCONF;
196 nobodyuser = NFS4NOBODY_USER;
197 nobodygroup = NFS4NOBODY_GROUP;
198 strlcpy(pipefsdir, PIPEFS_DIR, sizeof(pipefsdir));
200 #define GETOPTSTR "vfd:p:U:G:c:CS"
201 opterr=0; /* Turn off error messages */
202 while ((opt = getopt(argc, argv, GETOPTSTR)) != -1) {
206 if (strchr(GETOPTSTR, optopt))
207 errx(1, "'-%c' option requires an argument.", optopt);
209 errx(1, "'-%c' is an invalid argument.", optopt);
214 if (stat(conf_path, &sb) == -1 && (errno == ENOENT || errno == EACCES)) {
215 warn("Skipping configuration file \"%s\"", conf_path);
218 verbose = conf_get_num("General", "Verbosity", 0);
219 CONF_SAVE(xpipefsdir, conf_get_str("General", "Pipefs-Directory"));
220 if (xpipefsdir != NULL)
221 strlcpy(pipefsdir, xpipefsdir, sizeof(pipefsdir));
222 CONF_SAVE(nobodyuser, conf_get_str("Mapping", "Nobody-User"));
223 CONF_SAVE(nobodygroup, conf_get_str("Mapping", "Nobody-Group"));
224 nfs4_init_name_mapping(conf_path);
227 while ((opt = getopt(argc, argv, GETOPTSTR)) != -1)
236 strlcpy(pipefsdir, optarg, sizeof(pipefsdir));
241 errx(1, "the -d, -U, and -G options have been removed;"
242 " please use the configuration file instead.");
253 if (!serverstart && !clientstart)
254 errx(1, "it is illegal to specify both -C and -S");
256 strncat(pipefsdir, "/nfs", sizeof(pipefsdir));
258 if ((pw = getpwnam(nobodyuser)) == NULL)
259 errx(1, "Could not find user \"%s\"", nobodyuser);
260 nobodyuid = pw->pw_uid;
262 if ((gr = getgrnam(nobodygroup)) == NULL)
263 errx(1, "Could not find group \"%s\"", nobodygroup);
264 nobodygid = gr->gr_gid;
272 nfsdret = nfsdopen(NFSD_DIR);
274 ret = flush_nfsd_idmap_cache();
276 errx(1, "Failed to flush nfsd idmap cache\n");
281 struct timeval now = {
286 if ((fd = open(pipefsdir, O_RDONLY)) == -1)
287 err(1, "open(%s)", pipefsdir);
289 if (fcntl(fd, F_SETSIG, SIGUSR1) == -1)
290 err(1, "fcntl(%s)", pipefsdir);
291 if (fcntl(fd, F_NOTIFY,
292 DN_CREATE | DN_DELETE | DN_MODIFY | DN_MULTISHOT) == -1)
293 err(1, "fcntl(%s)", pipefsdir);
297 /* These events are persistent */
298 signal_set(&rootdirev, SIGUSR1, dirscancb, &icq);
299 signal_add(&rootdirev, NULL);
300 signal_set(&clntdirev, SIGUSR2, clntscancb, &icq);
301 signal_add(&clntdirev, NULL);
302 signal_set(&svrdirev, SIGHUP, svrreopen, NULL);
303 signal_add(&svrdirev, NULL);
305 /* Fetch current state */
306 /* (Delay till start of event_dispatch to avoid possibly losing
307 * a SIGUSR1 between here and the call to event_dispatch().) */
308 evtimer_set(&initialize, dirscancb, &icq);
309 evtimer_add(&initialize, &now);
312 if (nfsdret != 0 && fd == 0)
313 errx(1, "Neither NFS client nor NFSd found");
317 if (event_dispatch() < 0)
318 errx(1, "event_dispatch: returns errno %d (%s)", errno, strerror(errno));
324 dirscancb(int fd, short which, void *data)
327 struct dirent **ents;
328 struct idmap_client *ic;
330 struct idmap_clientq *icq = data;
332 nent = scandir(pipefsdir, &ents, NULL, alphasort);
334 warn("scandir(%s)", pipefsdir);
338 for (i = 0; i < nent; i++) {
339 if (ents[i]->d_reclen > 4 &&
340 strncmp(ents[i]->d_name, "clnt", 4) == 0) {
341 TAILQ_FOREACH(ic, icq, ic_next)
342 if (strcmp(ents[i]->d_name + 4, ic->ic_clid) == 0)
347 if ((ic = calloc(1, sizeof(*ic))) == NULL)
349 strlcpy(ic->ic_clid, ents[i]->d_name + 4,
350 sizeof(ic->ic_clid));
352 snprintf(path, sizeof(path), "%s/%s",
353 pipefsdir, ents[i]->d_name);
355 if ((ic->ic_dirfd = open(path, O_RDONLY, 0)) == -1) {
356 warn("open(%s)", path);
361 strlcat(path, "/idmap", sizeof(path));
362 strlcpy(ic->ic_path, path, sizeof(ic->ic_path));
365 warnx("New client: %s", ic->ic_clid);
367 if (nfsopen(ic) == -1) {
373 ic->ic_id = "Client";
375 TAILQ_INSERT_TAIL(icq, ic, ic_next);
382 TAILQ_FOREACH(ic, icq, ic_next) {
383 if (!ic->ic_scanned) {
384 event_del(&ic->ic_event);
387 TAILQ_REMOVE(icq, ic, ic_next);
389 warnx("Stale client: %s", ic->ic_clid);
390 warnx("\t-> closed %s", ic->ic_path);
400 svrreopen(int fd, short which, void *data)
406 clntscancb(int fd, short which, void *data)
408 struct idmap_clientq *icq = data;
409 struct idmap_client *ic;
411 TAILQ_FOREACH(ic, icq, ic_next)
412 if (ic->ic_fd == -1 && nfsopen(ic) == -1) {
414 TAILQ_REMOVE(icq, ic, ic_next);
420 nfsdcb(int fd, short which, void *data)
422 struct idmap_client *ic = data;
424 u_char buf[IDMAP_MAXMSGSZ + 1];
426 char *bp, typebuf[IDMAP_MAXMSGSZ],
427 buf1[IDMAP_MAXMSGSZ], authbuf[IDMAP_MAXMSGSZ], *p;
429 if (which != EV_READ)
432 if ((len = read(ic->ic_fd, buf, sizeof(buf))) == -1) {
433 warnx("nfsdcb: read(%s) failed: errno %d (%s)",
434 ic->ic_path, errno, strerror(errno));
438 /* Get rid of newline and terminate buffer*/
442 memset(&im, 0, sizeof(im));
444 /* Authentication name -- ignored for now*/
445 if (getfield(&bp, authbuf, sizeof(authbuf)) == -1) {
446 warnx("nfsdcb: bad authentication name in upcall\n");
449 if (getfield(&bp, typebuf, sizeof(typebuf)) == -1) {
450 warnx("nfsdcb: bad type in upcall\n");
454 warnx("nfsdcb: authbuf=%s authtype=%s", authbuf, typebuf);
456 im.im_type = strcmp(typebuf, "user") == 0 ?
457 IDMAP_TYPE_USER : IDMAP_TYPE_GROUP;
459 switch (ic->ic_which) {
461 im.im_conv = IDMAP_CONV_NAMETOID;
462 if (getfield(&bp, im.im_name, sizeof(im.im_name)) == -1) {
463 warnx("nfsdcb: bad name in upcall\n");
468 im.im_conv = IDMAP_CONV_IDTONAME;
469 if (getfield(&bp, buf1, sizeof(buf1)) == -1) {
470 warnx("nfsdcb: bad id in upcall\n");
473 if ((im.im_id = strtoul(buf1, (char **)NULL, 10)) == ULONG_MAX &&
475 warnx("nfsdcb: id '%s' too big!\n", buf1);
481 warnx("Unknown which type %d", ic->ic_which);
491 /* Authentication name */
492 addfield(&bp, &bsiz, authbuf);
494 switch (ic->ic_which) {
497 p = im.im_type == IDMAP_TYPE_USER ? "user" : "group";
498 addfield(&bp, &bsiz, p);
500 addfield(&bp, &bsiz, im.im_name);
501 #define NFSD_EXPIRY 300 /* seconds */
503 snprintf(buf1, sizeof(buf1), "%lu", time(NULL) + NFSD_EXPIRY);
504 addfield(&bp, &bsiz, buf1);
506 snprintf(buf1, sizeof(buf1), "%u", im.im_id);
507 addfield(&bp, &bsiz, buf1);
509 //if (bsiz == sizeof(buf)) /* XXX */
516 p = im.im_type == IDMAP_TYPE_USER ? "user" : "group";
517 addfield(&bp, &bsiz, p);
519 snprintf(buf1, sizeof(buf1), "%u", im.im_id);
520 addfield(&bp, &bsiz, buf1);
522 snprintf(buf1, sizeof(buf1), "%lu", time(NULL) + NFSD_EXPIRY);
523 addfield(&bp, &bsiz, buf1);
525 addfield(&bp, &bsiz, im.im_name);
531 warnx("Unknown which type %d", ic->ic_which);
535 bsiz = sizeof(buf) - bsiz;
537 if (atomicio(write, ic->ic_fd, buf, bsiz) != bsiz)
538 warnx("nfsdcb: write(%s) failed: errno %d (%s)",
539 ic->ic_path, errno, strerror(errno));
542 event_add(&ic->ic_event, NULL);
546 imconv(struct idmap_client *ic, struct idmap_msg *im)
548 switch (im->im_conv) {
549 case IDMAP_CONV_IDTONAME:
552 warnx("%s %s: (%s) id \"%d\" -> name \"%s\"",
553 ic->ic_id, ic->ic_clid,
554 im->im_type == IDMAP_TYPE_USER ? "user" : "group",
555 im->im_id, im->im_name);
557 case IDMAP_CONV_NAMETOID:
558 if (validateascii(im->im_name, sizeof(im->im_name)) == -1) {
559 im->im_status |= IDMAP_STATUS_INVALIDMSG;
564 warnx("%s %s: (%s) name \"%s\" -> id \"%d\"",
565 ic->ic_id, ic->ic_clid,
566 im->im_type == IDMAP_TYPE_USER ? "user" : "group",
567 im->im_name, im->im_id);
570 warnx("Invalid conversion type (%d) in message", im->im_conv);
571 im->im_status |= IDMAP_STATUS_INVALIDMSG;
577 nfscb(int fd, short which, void *data)
579 struct idmap_client *ic = data;
582 if (which != EV_READ)
585 if (atomicio(read, ic->ic_fd, &im, sizeof(im)) != sizeof(im)) {
587 warn("read(%s)", ic->ic_path);
595 if (atomicio(write, ic->ic_fd, &im, sizeof(im)) != sizeof(im))
596 warn("write(%s)", ic->ic_path);
598 event_add(&ic->ic_event, NULL);
602 nfsdreopen_one(struct idmap_client *ic)
607 warnx("ReOpening %s", ic->ic_path);
609 if ((fd = open(ic->ic_path, O_RDWR, 0)) != -1) {
612 if ((ic->ic_event.ev_flags & EVLIST_INIT))
613 event_del(&ic->ic_event);
615 ic->ic_event.ev_fd = ic->ic_fd = fd;
616 event_set(&ic->ic_event, ic->ic_fd, EV_READ, nfsdcb, ic);
617 event_add(&ic->ic_event, NULL);
619 warnx("nfsdreopen: Opening '%s' failed: errno %d (%s)",
620 ic->ic_path, errno, strerror(errno));
627 nfsdreopen_one(&nfsd_ic[IC_NAMEID]);
628 nfsdreopen_one(&nfsd_ic[IC_IDNAME]);
635 return ((nfsdopenone(&nfsd_ic[IC_NAMEID]) == 0 &&
636 nfsdopenone(&nfsd_ic[IC_IDNAME]) == 0) ? 0 : -1);
640 nfsdopenone(struct idmap_client *ic)
642 if ((ic->ic_fd = open(ic->ic_path, O_RDWR, 0)) == -1) {
644 warnx("Opening %s failed: errno %d (%s)",
645 ic->ic_path, errno, strerror(errno));
649 event_set(&ic->ic_event, ic->ic_fd, EV_READ, nfsdcb, ic);
650 event_add(&ic->ic_event, NULL);
653 warnx("Opened %s", ic->ic_path);
659 nfsopen(struct idmap_client *ic)
661 if ((ic->ic_fd = open(ic->ic_path, O_RDWR, 0)) == -1) {
664 fcntl(ic->ic_dirfd, F_SETSIG, SIGUSR2);
665 fcntl(ic->ic_dirfd, F_NOTIFY,
666 DN_CREATE | DN_DELETE | DN_MULTISHOT);
669 warn("open(%s)", ic->ic_path);
673 event_set(&ic->ic_event, ic->ic_fd, EV_READ, nfscb, ic);
674 event_add(&ic->ic_event, NULL);
675 fcntl(ic->ic_dirfd, F_SETSIG, 0);
676 fcntl(ic->ic_dirfd, F_NOTIFY, 0);
678 warnx("Opened %s", ic->ic_path);
684 static int write_name(char *dest, char *localname, char *domain, size_t len)
686 if (strlen(localname) + 1 + strlen(domain) + 1 > len) {
687 return -ENOMEM; /* XXX: Is there an -ETOOLONG? */
689 strcpy(dest, localname);
691 strcat(dest, domain);
696 idtonameres(struct idmap_msg *im)
698 char domain[NFS4_MAX_DOMAIN_LEN];
701 ret = nfs4_get_default_domain(NULL, domain, sizeof(domain));
702 switch (im->im_type) {
703 case IDMAP_TYPE_USER:
704 ret = nfs4_uid_to_name(im->im_id, domain, im->im_name,
705 sizeof(im->im_name));
707 write_name(im->im_name, nobodyuser, domain,
708 sizeof(im->im_name));
710 case IDMAP_TYPE_GROUP:
711 ret = nfs4_gid_to_name(im->im_id, domain, im->im_name,
712 sizeof(im->im_name));
714 write_name(im->im_name, nobodygroup, domain,
715 sizeof(im->im_name));
719 im->im_status = IDMAP_STATUS_SUCCESS;
723 nametoidres(struct idmap_msg *im)
727 /* XXX: nobody fallbacks shouldn't always happen:
728 * server id -> name should be OK
729 * client name -> id should be OK
730 * but not otherwise */
731 /* XXX: move nobody stuff to library calls
732 * (nfs4_get_nobody_user(domain), nfs4_get_nobody_group(domain)) */
733 /* XXX: should make this call higher up in the call chain (so we'd
734 * have a chance on looking up server/whatever. */
735 switch (im->im_type) {
736 case IDMAP_TYPE_USER:
737 ret = nfs4_name_to_uid(im->im_name, &im->im_id);
739 im->im_id = nobodyuid;
741 case IDMAP_TYPE_GROUP:
742 ret = nfs4_name_to_gid(im->im_name, &im->im_id);
744 im->im_id = nobodygid;
748 im->im_status = IDMAP_STATUS_SUCCESS;
752 validateascii(char *string, u_int32_t len)
756 for (i = 0; i < len; i++) {
757 if (string[i] == '\0')
760 if (string[i] & 0x80)
764 if (string[i] != '\0')
771 addfield(char **bpp, ssize_t *bsizp, char *fld)
774 ssize_t bsiz = *bsizp;
776 while ((ch = *fld++) != '\0' && bsiz > 0) {
783 bp += snprintf(bp, bsiz, "\\%03o", ch);
794 if (bsiz < 1 || ch != '\0')
807 getfield(char **bpp, char *fld, size_t fldsz)
812 while ((bp = strsep(bpp, " ")) != NULL && bp[0] == '\0')
815 if (bp == NULL || bp[0] == '\0' || bp[0] == '\n')
818 while (*bp != '\0' && fldsz > 1) {
820 if ((n = sscanf(bp, "\\%03o", &val)) != 1)
840 * mydaemon creates a pipe between the partent and child
841 * process. The parent process will wait until the
842 * child dies or writes a '1' on the pipe signaling
843 * that it started successfully.
845 int pipefds[2] = { -1, -1};
848 mydaemon(int nochdir, int noclose)
850 int pid, status, tempfd;
852 if (pipe(pipefds) < 0)
853 err(1, "mydaemon: pipe() failed: errno %d (%s)\n", errno, strerror(errno));
855 if ((pid = fork ()) < 0)
856 err(1, "mydaemon: fork() failed: errno %d (%s)\n", errno, strerror(errno));
860 * Parent. Wait for status from child.
863 if (read(pipefds[0], &status, 1) != 1)
871 if (chdir ("/") == -1)
872 err(1, "mydaemon: chdir() failed: errno %d (%s)\n", errno, strerror(errno));
875 while (pipefds[1] <= 2) {
876 pipefds[1] = dup(pipefds[1]);
878 err(1, "mydaemon: dup() failed: errno %d (%s)\n", errno, strerror(errno));
882 tempfd = open("/dev/null", O_RDWR);
896 if (pipefds[1] > 0) {
897 write(pipefds[1], &status, 1);