]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/mountd/cache.c
Merge branch 'sid'
[nfs-utils.git] / utils / mountd / cache.c
1
2 /*
3  * Handle communication with knfsd internal cache
4  *
5  * We open /proc/net/rpc/{auth.unix.ip,nfsd.export,nfsd.fh}/channel
6  * and listen for requests (using my_svc_run)
7  * 
8  */
9
10 #ifdef HAVE_CONFIG_H
11 #include <config.h>
12 #endif
13
14 #include <sys/types.h>
15 #include <sys/select.h>
16 #include <sys/stat.h>
17 #include <sys/vfs.h>
18 #include <time.h>
19 #include <netinet/in.h>
20 #include <arpa/inet.h>
21 #include <unistd.h>
22 #include <fcntl.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <pwd.h>
26 #include <grp.h>
27 #include <mntent.h>
28 #include "misc.h"
29 #include "nfslib.h"
30 #include "exportfs.h"
31 #include "mountd.h"
32 #include "fsloc.h"
33 #include "pseudoflavors.h"
34
35 #ifdef USE_BLKID
36 #include "blkid/blkid.h"
37 #endif
38
39 /*
40  * Invoked by RPC service loop
41  */
42 void    cache_set_fds(fd_set *fdset);
43 int     cache_process_req(fd_set *readfds);
44
45 enum nfsd_fsid {
46         FSID_DEV = 0,
47         FSID_NUM,
48         FSID_MAJOR_MINOR,
49         FSID_ENCODE_DEV,
50         FSID_UUID4_INUM,
51         FSID_UUID8,
52         FSID_UUID16,
53         FSID_UUID16_INUM,
54 };
55
56 /*
57  * Support routines for text-based upcalls.
58  * Fields are separated by spaces.
59  * Fields are either mangled to quote space tab newline slosh with slosh
60  * or a hexified with a leading \x
61  * Record is terminated with newline.
62  *
63  */
64 static int cache_export_ent(char *domain, struct exportent *exp, char *p);
65
66 #define INITIAL_MANAGED_GROUPS 100
67
68 char *lbuf  = NULL;
69 int lbuflen = 0;
70 extern int use_ipaddr;
71
72 static void auth_unix_ip(FILE *f)
73 {
74         /* requests are
75          *  class IP-ADDR
76          * Ignore if class != "nfsd"
77          * Otherwise find domainname and write back:
78          *
79          *  "nfsd" IP-ADDR expiry domainname
80          */
81         char *cp;
82         char class[20];
83         char ipaddr[INET6_ADDRSTRLEN + 1];
84         char *client = NULL;
85         struct addrinfo *tmp = NULL;
86         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
87                 return;
88
89         xlog(D_CALL, "auth_unix_ip: inbuf '%s'", lbuf);
90
91         cp = lbuf;
92
93         if (qword_get(&cp, class, 20) <= 0 ||
94             strcmp(class, "nfsd") != 0)
95                 return;
96
97         if (qword_get(&cp, ipaddr, sizeof(ipaddr) - 1) <= 0)
98                 return;
99
100         tmp = host_pton(ipaddr);
101         if (tmp == NULL)
102                 return;
103
104         auth_reload();
105
106         /* addr is a valid, interesting address, find the domain name... */
107         if (!use_ipaddr) {
108                 struct addrinfo *ai = NULL;
109
110                 ai = client_resolve(tmp->ai_addr);
111                 if (ai) {
112                         client = client_compose(ai);
113                         freeaddrinfo(ai);
114                 }
115         }
116         qword_print(f, "nfsd");
117         qword_print(f, ipaddr);
118         qword_printtimefrom(f, DEFAULT_TTL);
119         if (use_ipaddr) {
120                 memmove(ipaddr + 1, ipaddr, strlen(ipaddr) + 1);
121                 ipaddr[0] = '$';
122                 qword_print(f, ipaddr);
123         } else if (client)
124                 qword_print(f, *client?client:"DEFAULT");
125         qword_eol(f);
126         xlog(D_CALL, "auth_unix_ip: client %p '%s'", client, client?client: "DEFAULT");
127
128         free(client);
129         freeaddrinfo(tmp);
130
131 }
132
133 static void auth_unix_gid(FILE *f)
134 {
135         /* Request are
136          *  uid
137          * reply is
138          *  uid expiry count list of group ids
139          */
140         uid_t uid;
141         struct passwd *pw;
142         static gid_t *groups = NULL;
143         static int groups_len = 0;
144         gid_t *more_groups;
145         int ngroups;
146         int rv, i;
147         char *cp;
148
149         if (groups_len == 0) {
150                 groups = malloc(sizeof(gid_t) * INITIAL_MANAGED_GROUPS);
151                 if (!groups)
152                         return;
153
154                 groups_len = INITIAL_MANAGED_GROUPS;
155         }
156
157         ngroups = groups_len;
158
159         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
160                 return;
161
162         cp = lbuf;
163         if (qword_get_uint(&cp, &uid) != 0)
164                 return;
165
166         pw = getpwuid(uid);
167         if (!pw)
168                 rv = -1;
169         else {
170                 rv = getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups);
171                 if (rv == -1 && ngroups >= groups_len) {
172                         more_groups = realloc(groups, sizeof(gid_t)*ngroups);
173                         if (!more_groups)
174                                 rv = -1;
175                         else {
176                                 groups = more_groups;
177                                 groups_len = ngroups;
178                                 rv = getgrouplist(pw->pw_name, pw->pw_gid,
179                                                   groups, &ngroups);
180                         }
181                 }
182         }
183         qword_printuint(f, uid);
184         qword_printtimefrom(f, DEFAULT_TTL);
185         if (rv >= 0) {
186                 qword_printuint(f, ngroups);
187                 for (i=0; i<ngroups; i++)
188                         qword_printuint(f, groups[i]);
189         } else
190                 qword_printuint(f, 0);
191         qword_eol(f);
192 }
193
194 #if USE_BLKID
195 static const char *get_uuid_blkdev(char *path)
196 {
197         /* We set *safe if we know that we need the
198          * fsid from statfs too.
199          */
200         static blkid_cache cache = NULL;
201         struct stat stb;
202         char *devname;
203         blkid_tag_iterate iter;
204         blkid_dev dev;
205         const char *type;
206         const char *val, *uuid = NULL;
207
208         if (cache == NULL)
209                 blkid_get_cache(&cache, NULL);
210
211         if (stat(path, &stb) != 0)
212                 return NULL;
213         devname = blkid_devno_to_devname(stb.st_dev);
214         if (!devname)
215                 return NULL;
216         dev = blkid_get_dev(cache, devname, BLKID_DEV_NORMAL);
217         free(devname);
218         if (!dev)
219                 return NULL;
220         iter = blkid_tag_iterate_begin(dev);
221         if (!iter)
222                 return NULL;
223         while (blkid_tag_next(iter, &type, &val) == 0) {
224                 if (strcmp(type, "UUID") == 0)
225                         uuid = val;
226                 if (strcmp(type, "TYPE") == 0 &&
227                     strcmp(val, "btrfs") == 0) {
228                         uuid = NULL;
229                         break;
230                 }
231         }
232         blkid_tag_iterate_end(iter);
233         return uuid;
234 }
235 #else
236 #define get_uuid_blkdev(path) (NULL)
237 #endif
238
239 static int get_uuid(const char *val, size_t uuidlen, char *u)
240 {
241         /* extract hex digits from uuidstr and compose a uuid
242          * of the given length (max 16), xoring bytes to make
243          * a smaller uuid.
244          */
245         size_t i = 0;
246         
247         memset(u, 0, uuidlen);
248         for ( ; *val ; val++) {
249                 int c = *val;
250                 if (!isxdigit(c))
251                         continue;
252                 if (isalpha(c)) {
253                         if (isupper(c))
254                                 c = c - 'A' + 10;
255                         else
256                                 c = c - 'a' + 10;
257                 } else
258                         c = c - '0' + 0;
259                 if ((i&1) == 0)
260                         c <<= 4;
261                 u[i/2] ^= (char)c;
262                 i++;
263                 if (i == uuidlen*2)
264                         i = 0;
265         }
266         return 1;
267 }
268
269 static int uuid_by_path(char *path, int type, size_t uuidlen, char *uuid)
270 {
271         /* get a uuid for the filesystem found at 'path'.
272          * There are several possible ways of generating the
273          * uuids (types).
274          * Type 0 is used for new filehandles, while other types
275          * may be used to interpret old filehandle - to ensure smooth
276          * forward migration.
277          * We return 1 if a uuid was found (and it might be worth 
278          * trying the next type) or 0 if no more uuid types can be
279          * extracted.
280          */
281
282         /* Possible sources of uuid are
283          * - blkid uuid
284          * - statfs64 uuid
285          *
286          * On some filesystems (e.g. vfat) the statfs64 uuid is simply an
287          * encoding of the device that the filesystem is mounted from, so
288          * it we be very bad to use that (as device numbers change).  blkid
289          * must be preferred.
290          * On other filesystems (e.g. btrfs) the statfs64 uuid contains
291          * important info that the blkid uuid cannot contain:  This happens
292          * when multiple subvolumes are exported (they have the same
293          * blkid uuid but different statfs64 uuids).
294          * We rely on get_uuid_blkdev *knowing* which is which and not returning
295          * a uuid for filesystems where the statfs64 uuid is better.
296          *
297          */
298         struct statfs64 st;
299         char fsid_val[17];
300         const char *blkid_val;
301         const char *val;
302
303         blkid_val = get_uuid_blkdev(path);
304
305         if (statfs64(path, &st) == 0 &&
306             (st.f_fsid.__val[0] || st.f_fsid.__val[1]))
307                 snprintf(fsid_val, 17, "%08x%08x",
308                          st.f_fsid.__val[0], st.f_fsid.__val[1]);
309         else
310                 fsid_val[0] = 0;
311
312         if (blkid_val && (type--) == 0)
313                 val = blkid_val;
314         else if (fsid_val[0] && (type--) == 0)
315                 val = fsid_val;
316         else
317                 return 0;
318
319         get_uuid(val, uuidlen, uuid);
320         return 1;
321 }
322
323 /* Iterate through /etc/mtab, finding mountpoints
324  * at or below a given path
325  */
326 static char *next_mnt(void **v, char *p)
327 {
328         FILE *f;
329         struct mntent *me;
330         size_t l = strlen(p);
331         if (*v == NULL) {
332                 f = setmntent("/etc/mtab", "r");
333                 *v = f;
334         } else
335                 f = *v;
336         while ((me = getmntent(f)) != NULL &&
337                (strncmp(me->mnt_dir, p, l) != 0 ||
338                 me->mnt_dir[l] != '/'))
339                 ;
340         if (me == NULL) {
341                 endmntent(f);
342                 *v = NULL;
343                 return NULL;
344         }
345         return me->mnt_dir;
346 }
347
348 static int is_subdirectory(char *child, char *parent)
349 {
350         size_t l = strlen(parent);
351
352         if (strcmp(parent, "/") == 0)
353                 return 1;
354
355         return strcmp(child, parent) == 0
356                 || (strncmp(child, parent, l) == 0 && child[l] == '/');
357 }
358
359 static int path_matches(nfs_export *exp, char *path)
360 {
361         if (exp->m_export.e_flags & NFSEXP_CROSSMOUNT)
362                 return is_subdirectory(path, exp->m_export.e_path);
363         return strcmp(path, exp->m_export.e_path) == 0;
364 }
365
366 static int
367 export_matches(nfs_export *exp, char *dom, char *path, struct addrinfo *ai)
368 {
369         return path_matches(exp, path) && client_matches(exp, dom, ai);
370 }
371
372 /* True iff e1 is a child of e2 and e2 has crossmnt set: */
373 static bool subexport(struct exportent *e1, struct exportent *e2)
374 {
375         char *p1 = e1->e_path, *p2 = e2->e_path;
376         size_t l2 = strlen(p2);
377
378         return e2->e_flags & NFSEXP_CROSSMOUNT
379                 && strncmp(p1, p2, l2) == 0
380                 && p1[l2] == '/';
381 }
382
383 struct parsed_fsid {
384         int fsidtype;
385         /* We could use a union for this, but it would be more
386          * complicated; why bother? */
387         unsigned int inode;
388         unsigned int minor;
389         unsigned int major;
390         unsigned int fsidnum;
391         size_t uuidlen;
392         char *fhuuid;
393 };
394
395 static int parse_fsid(int fsidtype, int fsidlen, char *fsid,
396                 struct parsed_fsid *parsed)
397 {
398         unsigned int dev;
399         unsigned long long inode64;
400
401         memset(parsed, 0, sizeof(*parsed));
402         parsed->fsidtype = fsidtype;
403         switch(fsidtype) {
404         case FSID_DEV: /* 4 bytes: 2 major, 2 minor, 4 inode */
405                 if (fsidlen != 8)
406                         return -1;
407                 memcpy(&dev, fsid, 4);
408                 memcpy(&parsed->inode, fsid+4, 4);
409                 parsed->major = ntohl(dev)>>16;
410                 parsed->minor = ntohl(dev) & 0xFFFF;
411                 break;
412
413         case FSID_NUM: /* 4 bytes - fsid */
414                 if (fsidlen != 4)
415                         return -1;
416                 memcpy(&parsed->fsidnum, fsid, 4);
417                 break;
418
419         case FSID_MAJOR_MINOR: /* 12 bytes: 4 major, 4 minor, 4 inode 
420                  * This format is never actually used but was
421                  * an historical accident
422                  */
423                 if (fsidlen != 12)
424                         return -1;
425                 memcpy(&dev, fsid, 4);
426                 parsed->major = ntohl(dev);
427                 memcpy(&dev, fsid+4, 4);
428                 parsed->minor = ntohl(dev);
429                 memcpy(&parsed->inode, fsid+8, 4);
430                 break;
431
432         case FSID_ENCODE_DEV: /* 8 bytes: 4 byte packed device number, 4 inode */
433                 /* This is *host* endian, not net-byte-order, because
434                  * no-one outside this host has any business interpreting it
435                  */
436                 if (fsidlen != 8)
437                         return -1;
438                 memcpy(&dev, fsid, 4);
439                 memcpy(&parsed->inode, fsid+4, 4);
440                 parsed->major = (dev & 0xfff00) >> 8;
441                 parsed->minor = (dev & 0xff) | ((dev >> 12) & 0xfff00);
442                 break;
443
444         case FSID_UUID4_INUM: /* 4 byte inode number and 4 byte uuid */
445                 if (fsidlen != 8)
446                         return -1;
447                 memcpy(&parsed->inode, fsid, 4);
448                 parsed->uuidlen = 4;
449                 parsed->fhuuid = fsid+4;
450                 break;
451         case FSID_UUID8: /* 8 byte uuid */
452                 if (fsidlen != 8)
453                         return -1;
454                 parsed->uuidlen = 8;
455                 parsed->fhuuid = fsid;
456                 break;
457         case FSID_UUID16: /* 16 byte uuid */
458                 if (fsidlen != 16)
459                         return -1;
460                 parsed->uuidlen = 16;
461                 parsed->fhuuid = fsid;
462                 break;
463         case FSID_UUID16_INUM: /* 8 byte inode number and 16 byte uuid */
464                 if (fsidlen != 24)
465                         return -1;
466                 memcpy(&inode64, fsid, 8);
467                 parsed->inode = inode64;
468                 parsed->uuidlen = 16;
469                 parsed->fhuuid = fsid+8;
470                 break;
471         }
472         return 0;
473 }
474
475 static bool match_fsid(struct parsed_fsid *parsed, nfs_export *exp, char *path)
476 {
477         struct stat stb;
478         int type;
479         char u[16];
480
481         if (stat(path, &stb) != 0)
482                 return false;
483         if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode))
484                 return false;
485
486         switch (parsed->fsidtype) {
487         case FSID_DEV:
488         case FSID_MAJOR_MINOR:
489         case FSID_ENCODE_DEV:
490                 if (stb.st_ino != parsed->inode)
491                         return false;
492                 if (parsed->major != major(stb.st_dev) ||
493                     parsed->minor != minor(stb.st_dev))
494                         return false;
495                 return true;
496         case FSID_NUM:
497                 if (((exp->m_export.e_flags & NFSEXP_FSID) == 0 ||
498                      exp->m_export.e_fsid != parsed->fsidnum))
499                         return false;
500                 return true;
501         case FSID_UUID4_INUM:
502         case FSID_UUID16_INUM:
503                 if (stb.st_ino != parsed->inode)
504                         return false;
505                 goto check_uuid;
506         case FSID_UUID8:
507         case FSID_UUID16:
508                 if (!is_mountpoint(path))
509                         return false;
510         check_uuid:
511                 if (exp->m_export.e_uuid)
512                         get_uuid(exp->m_export.e_uuid, parsed->uuidlen, u);
513                 else
514                         for (type = 0;
515                              uuid_by_path(path, type, parsed->uuidlen, u);
516                              type++)
517                                 if (memcmp(u, parsed->fhuuid, parsed->uuidlen) == 0)
518                                         return true;
519
520                 if (memcmp(u, parsed->fhuuid, parsed->uuidlen) != 0)
521                         return false;
522                 return true;
523         }
524         /* Well, unreachable, actually: */
525         return false;
526 }
527
528 static struct addrinfo *lookup_client_addr(char *dom)
529 {
530         struct addrinfo *ret;
531         struct addrinfo *tmp;
532
533         dom++; /* skip initial "$" */
534
535         tmp = host_pton(dom);
536         if (tmp == NULL)
537                 return NULL;
538         ret = client_resolve(tmp->ai_addr);
539         freeaddrinfo(tmp);
540         return ret;
541 }
542
543 static void nfsd_fh(FILE *f)
544 {
545         /* request are:
546          *  domain fsidtype fsid
547          * interpret fsid, find export point and options, and write:
548          *  domain fsidtype fsid expiry path
549          */
550         char *cp;
551         char *dom;
552         int fsidtype;
553         int fsidlen;
554         char fsid[32];
555         struct parsed_fsid parsed;
556         struct exportent *found = NULL;
557         struct addrinfo *ai = NULL;
558         char *found_path = NULL;
559         nfs_export *exp;
560         int i;
561         int dev_missing = 0;
562
563         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
564                 return;
565
566         xlog(D_CALL, "nfsd_fh: inbuf '%s'", lbuf);
567
568         cp = lbuf;
569
570         dom = malloc(strlen(cp));
571         if (dom == NULL)
572                 return;
573         if (qword_get(&cp, dom, strlen(cp)) <= 0)
574                 goto out;
575         if (qword_get_int(&cp, &fsidtype) != 0)
576                 goto out;
577         if (fsidtype < 0 || fsidtype > 7)
578                 goto out; /* unknown type */
579         if ((fsidlen = qword_get(&cp, fsid, 32)) <= 0)
580                 goto out;
581         if (parse_fsid(fsidtype, fsidlen, fsid, &parsed))
582                 goto out;
583
584         auth_reload();
585
586         if (is_ipaddr_client(dom)) {
587                 ai = lookup_client_addr(dom);
588                 if (!ai)
589                         goto out;
590         }
591
592         /* Now determine export point for this fsid/domain */
593         for (i=0 ; i < MCL_MAXTYPES; i++) {
594                 nfs_export *next_exp;
595                 for (exp = exportlist[i].p_head; exp; exp = next_exp) {
596                         char *path;
597
598                         if (exp->m_export.e_flags & NFSEXP_CROSSMOUNT) {
599                                 static nfs_export *prev = NULL;
600                                 static void *mnt = NULL;
601                                 
602                                 if (prev == exp) {
603                                         /* try a submount */
604                                         path = next_mnt(&mnt, exp->m_export.e_path);
605                                         if (!path) {
606                                                 next_exp = exp->m_next;
607                                                 prev = NULL;
608                                                 continue;
609                                         }
610                                         next_exp = exp;
611                                 } else {
612                                         prev = exp;
613                                         mnt = NULL;
614                                         path = exp->m_export.e_path;
615                                         next_exp = exp;
616                                 }
617                         } else {
618                                 path = exp->m_export.e_path;
619                                 next_exp = exp->m_next;
620                         }
621
622                         if (!is_ipaddr_client(dom)
623                                         && !namelist_client_matches(exp, dom))
624                                 continue;
625                         if (exp->m_export.e_mountpoint &&
626                             !is_mountpoint(exp->m_export.e_mountpoint[0]?
627                                            exp->m_export.e_mountpoint:
628                                            exp->m_export.e_path))
629                                 dev_missing ++;
630
631                         if (!match_fsid(&parsed, exp, path))
632                                 continue;
633                         if (is_ipaddr_client(dom)
634                                         && !ipaddr_client_matches(exp, ai))
635                                 continue;
636                         if (!found || subexport(&exp->m_export, found)) {
637                                 found = &exp->m_export;
638                                 free(found_path);
639                                 found_path = strdup(path);
640                                 if (found_path == NULL)
641                                         goto out;
642                         } else if (strcmp(found->e_path, exp->m_export.e_path) != 0
643                                    && !subexport(found, &exp->m_export))
644                         {
645                                 xlog(L_WARNING, "%s and %s have same filehandle for %s, using first",
646                                      found_path, path, dom);
647                         } else {
648                                 /* same path, if one is V4ROOT, choose the other */
649                                 if (found->e_flags & NFSEXP_V4ROOT) {
650                                         found = &exp->m_export;
651                                         free(found_path);
652                                         found_path = strdup(path);
653                                         if (found_path == NULL)
654                                                 goto out;
655                                 }
656                         }
657                 }
658         }
659         if (found && 
660             found->e_mountpoint &&
661             !is_mountpoint(found->e_mountpoint[0]?
662                            found->e_mountpoint:
663                            found->e_path)) {
664                 /* Cannot export this yet 
665                  * should log a warning, but need to rate limit
666                    xlog(L_WARNING, "%s not exported as %d not a mountpoint",
667                    found->e_path, found->e_mountpoint);
668                  */
669                 /* FIXME we need to make sure we re-visit this later */
670                 goto out;
671         }
672         if (!found && dev_missing) {
673                 /* The missing dev could be what we want, so just be
674                  * quite rather than returning stale yet
675                  */
676                 goto out;
677         }
678
679         if (found)
680                 if (cache_export_ent(dom, found, found_path) < 0)
681                         found = 0;
682
683         qword_print(f, dom);
684         qword_printint(f, fsidtype);
685         qword_printhex(f, fsid, fsidlen);
686         /* The fsid -> path lookup can be quite expensive as it
687          * potentially stats and reads lots of devices, and some of those
688          * might have spun-down.  The Answer is not likely to
689          * change underneath us, and an 'exportfs -f' can always
690          * remove this from the kernel, so use a really log
691          * timeout.  Maybe this should be configurable on the command
692          * line.
693          */
694         qword_printint(f, 0x7fffffff);
695         if (found)
696                 qword_print(f, found_path);
697         qword_eol(f);
698  out:
699         if (found_path)
700                 free(found_path);
701         freeaddrinfo(ai);
702         free(dom);
703         xlog(D_CALL, "nfsd_fh: found %p path %s", found, found ? found->e_path : NULL);
704         return;         
705 }
706
707 static void write_fsloc(FILE *f, struct exportent *ep)
708 {
709         struct servers *servers;
710
711         if (ep->e_fslocmethod == FSLOC_NONE)
712                 return;
713
714         servers = replicas_lookup(ep->e_fslocmethod, ep->e_fslocdata);
715         if (!servers)
716                 return;
717         qword_print(f, "fsloc");
718         qword_printint(f, servers->h_num);
719         if (servers->h_num >= 0) {
720                 int i;
721                 for (i=0; i<servers->h_num; i++) {
722                         qword_print(f, servers->h_mp[i]->h_host);
723                         qword_print(f, servers->h_mp[i]->h_path);
724                 }
725         }
726         qword_printint(f, servers->h_referral);
727         release_replicas(servers);
728 }
729
730 static void write_secinfo(FILE *f, struct exportent *ep, int flag_mask)
731 {
732         struct sec_entry *p;
733
734         for (p = ep->e_secinfo; p->flav; p++)
735                 ; /* Do nothing */
736         if (p == ep->e_secinfo) {
737                 /* There was no sec= option */
738                 return;
739         }
740         fix_pseudoflavor_flags(ep);
741         qword_print(f, "secinfo");
742         qword_printint(f, p - ep->e_secinfo);
743         for (p = ep->e_secinfo; p->flav; p++) {
744                 qword_printint(f, p->flav->fnum);
745                 qword_printint(f, p->flags & flag_mask);
746         }
747
748 }
749
750 static int dump_to_cache(FILE *f, char *domain, char *path, struct exportent *exp)
751 {
752         qword_print(f, domain);
753         qword_print(f, path);
754         if (exp) {
755                 int different_fs = strcmp(path, exp->e_path) != 0;
756                 int flag_mask = different_fs ? ~NFSEXP_FSID : ~0;
757
758                 qword_printtimefrom(f, exp->e_ttl);
759                 qword_printint(f, exp->e_flags & flag_mask);
760                 qword_printint(f, exp->e_anonuid);
761                 qword_printint(f, exp->e_anongid);
762                 qword_printint(f, exp->e_fsid);
763                 write_fsloc(f, exp);
764                 write_secinfo(f, exp, flag_mask);
765                 if (exp->e_uuid == NULL || different_fs) {
766                         char u[16];
767                         if (uuid_by_path(path, 0, 16, u)) {
768                                 qword_print(f, "uuid");
769                                 qword_printhex(f, u, 16);
770                         }
771                 } else {
772                         char u[16];
773                         get_uuid(exp->e_uuid, 16, u);
774                         qword_print(f, "uuid");
775                         qword_printhex(f, u, 16);
776                 }
777         } else
778                 qword_printtimefrom(f, DEFAULT_TTL);
779         return qword_eol(f);
780 }
781
782 static nfs_export *
783 lookup_export(char *dom, char *path, struct addrinfo *ai)
784 {
785         nfs_export *exp;
786         nfs_export *found = NULL;
787         int found_type = 0;
788         int i;
789
790         for (i=0 ; i < MCL_MAXTYPES; i++) {
791                 for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
792                         if (!export_matches(exp, dom, path, ai))
793                                 continue;
794                         if (!found) {
795                                 found = exp;
796                                 found_type = i;
797                                 continue;
798                         }
799                         /* Always prefer non-V4ROOT exports */
800                         if (exp->m_export.e_flags & NFSEXP_V4ROOT)
801                                 continue;
802                         if (found->m_export.e_flags & NFSEXP_V4ROOT) {
803                                 found = exp;
804                                 found_type = i;
805                                 continue;
806                         }
807
808                         /* If one is a CROSSMOUNT, then prefer the longest path */
809                         if (((found->m_export.e_flags & NFSEXP_CROSSMOUNT) ||
810                              (exp->m_export.e_flags & NFSEXP_CROSSMOUNT)) &&
811                             strlen(found->m_export.e_path) !=
812                             strlen(exp->m_export.e_path)) {
813
814                                 if (strlen(exp->m_export.e_path) >
815                                     strlen(found->m_export.e_path)) {
816                                         found = exp;
817                                         found_type = i;
818                                 }
819                                 continue;
820
821                         } else if (found_type == i && found->m_warned == 0) {
822                                 xlog(L_WARNING, "%s exported to both %s and %s, "
823                                      "arbitrarily choosing options from first",
824                                      path, found->m_client->m_hostname, exp->m_client->m_hostname,
825                                      dom);
826                                 found->m_warned = 1;
827                         }
828                 }
829         }
830         return found;
831 }
832
833 #ifdef HAVE_NFS_PLUGIN_H
834 #include <dlfcn.h>
835 #include <link.h>
836 #include <nfs-plugin.h>
837
838 /*
839  * Find the export entry for the parent of "pathname".
840  * Caller must not free returned exportent.
841  */
842 static struct exportent *lookup_parent_export(char *dom,
843                 const char *pathname, struct addrinfo *ai)
844 {
845         char *parent, *slash;
846         nfs_export *result;
847
848         parent = strdup(pathname);
849         if (parent == NULL) {
850                 xlog(D_GENERAL, "%s: failed to allocate parent path buffer",
851                         __func__);
852                 goto out_default;
853         }
854         xlog(D_CALL, "%s: pathname = '%s'", __func__, pathname);
855
856 again:
857         /* shorten pathname by one component */
858         slash = strrchr(parent, '/');
859         if (slash == NULL) {
860                 xlog(D_GENERAL, "%s: no slash found in pathname",
861                         __func__);
862                 goto out_default;
863         }
864         *slash = '\0';
865
866         if (strlen(parent) == 0) {
867                 result = lookup_export(dom, "/", ai);
868                 if (result == NULL) {
869                         xlog(L_ERROR, "%s: no root export found.", __func__);
870                         goto out_default;
871                 }
872                 goto out;
873         }
874
875         result = lookup_export(dom, parent, ai);
876         if (result == NULL) {
877                 xlog(D_GENERAL, "%s: lookup_export(%s) found nothing",
878                         __func__, parent);
879                 goto again;
880         }
881
882 out:
883         xlog(D_CALL, "%s: found export for %s", __func__, parent);
884         free(parent);
885         return &result->m_export;
886
887 out_default:
888         free(parent);
889         return mkexportent("*", "/", "insecure");
890 }
891
892 /*
893  * Walk through a set of FS locations and build an e_fslocdata string.
894  * Returns true if all went to plan; otherwise, false.
895  */
896 static bool locations_to_fslocdata(struct jp_ops *ops,
897                 nfs_fsloc_set_t locations, char *fslocdata,
898                 size_t remaining, int *ttl)
899 {
900         char *server, *last_path, *rootpath, *ptr;
901         _Bool seen = false;
902
903         last_path = NULL;
904         rootpath = NULL;
905         server = NULL;
906         ptr = fslocdata;
907         *ttl = 0;
908
909         for (;;) {
910                 enum jp_status status;
911                 int len;
912
913                 status = ops->jp_get_next_location(locations, &server,
914                                                         &rootpath, ttl);
915                 if (status == JP_EMPTY)
916                         break;
917                 if (status != JP_OK) {
918                         xlog(D_GENERAL, "%s: failed to parse location: %s",
919                                 __func__, ops->jp_error(status));
920                         goto out_false;
921                 }
922                 xlog(D_GENERAL, "%s: Location: %s:%s",
923                         __func__, server, rootpath);
924
925                 if (last_path && strcmp(rootpath, last_path) == 0) {
926                         len = snprintf(ptr, remaining, "+%s", server);
927                         if (len < 0) {
928                                 xlog(D_GENERAL, "%s: snprintf: %m", __func__);
929                                 goto out_false;
930                         }
931                         if ((size_t)len >= remaining) {
932                                 xlog(D_GENERAL, "%s: fslocdata buffer overflow", __func__);
933                                 goto out_false;
934                         }
935                         remaining -= (size_t)len;
936                         ptr += len;
937                 } else {
938                         if (last_path == NULL)
939                                 len = snprintf(ptr, remaining, "%s@%s",
940                                                         rootpath, server);
941                         else
942                                 len = snprintf(ptr, remaining, ":%s@%s",
943                                                         rootpath, server);
944                         if (len < 0) {
945                                 xlog(D_GENERAL, "%s: snprintf: %m", __func__);
946                                 goto out_false;
947                         }
948                         if ((size_t)len >= remaining) {
949                                 xlog(D_GENERAL, "%s: fslocdata buffer overflow",
950                                         __func__);
951                                 goto out_false;
952                         }
953                         remaining -= (size_t)len;
954                         ptr += len;
955                         last_path = rootpath;
956                 }
957
958                 seen = true;
959                 free(rootpath);
960                 free(server);
961         }
962
963         xlog(D_CALL, "%s: fslocdata='%s', ttl=%d",
964                 __func__, fslocdata, *ttl);
965         return seen;
966
967 out_false:
968         free(rootpath);
969         free(server);
970         return false;
971 }
972
973 /*
974  * Duplicate the junction's parent's export options and graft in
975  * the fslocdata we constructed from the locations list.
976  */
977 static struct exportent *create_junction_exportent(struct exportent *parent,
978                 const char *junction, const char *fslocdata, int ttl)
979 {
980         static struct exportent *eep;
981
982         eep = (struct exportent *)malloc(sizeof(*eep));
983         if (eep == NULL)
984                 goto out_nomem;
985
986         dupexportent(eep, parent);
987         strcpy(eep->e_path, junction);
988         eep->e_hostname = strdup(parent->e_hostname);
989         if (eep->e_hostname == NULL) {
990                 free(eep);
991                 goto out_nomem;
992         }
993         free(eep->e_uuid);
994         eep->e_uuid = NULL;
995         eep->e_ttl = (unsigned int)ttl;
996
997         free(eep->e_fslocdata);
998         eep->e_fslocdata = strdup(fslocdata);
999         if (eep->e_fslocdata == NULL) {
1000                 free(eep->e_hostname);
1001                 free(eep);
1002                 goto out_nomem;
1003         }
1004         eep->e_fslocmethod = FSLOC_REFER;
1005         return eep;
1006
1007 out_nomem:
1008         xlog(L_ERROR, "%s: No memory", __func__);
1009         return NULL;
1010 }
1011
1012 /*
1013  * Walk through the set of FS locations and build an exportent.
1014  * Returns pointer to an exportent if "junction" refers to a junction.
1015  */
1016 static struct exportent *locations_to_export(struct jp_ops *ops,
1017                 nfs_fsloc_set_t locations, const char *junction,
1018                 struct exportent *parent)
1019 {
1020         static char fslocdata[BUFSIZ];
1021         int ttl;
1022
1023         fslocdata[0] = '\0';
1024         if (!locations_to_fslocdata(ops, locations,
1025                                         fslocdata, sizeof(fslocdata), &ttl))
1026                 return NULL;
1027         return create_junction_exportent(parent, junction, fslocdata, ttl);
1028 }
1029
1030 /*
1031  * Retrieve locations information in "junction" and dump it to the
1032  * kernel.  Returns pointer to an exportent if "junction" refers
1033  * to a junction.
1034  */
1035 static struct exportent *invoke_junction_ops(void *handle, char *dom,
1036                 const char *junction, struct addrinfo *ai)
1037 {
1038         struct exportent *parent, *exp = NULL;
1039         nfs_fsloc_set_t locations;
1040         enum jp_status status;
1041         struct jp_ops *ops;
1042         char *error;
1043
1044         ops = (struct jp_ops *)dlsym(handle, "nfs_junction_ops");
1045         error = dlerror();
1046         if (error != NULL) {
1047                 xlog(D_GENERAL, "%s: dlsym(jp_junction_ops): %s",
1048                         __func__, error);
1049                 return NULL;
1050         }
1051         if (ops->jp_api_version != JP_API_VERSION) {
1052                 xlog(D_GENERAL, "%s: unrecognized junction API version: %u",
1053                         __func__, ops->jp_api_version);
1054                 return NULL;
1055         }
1056
1057         status = ops->jp_init(false);
1058         if (status != JP_OK) {
1059                 xlog(D_GENERAL, "%s: failed to resolve %s: %s",
1060                         __func__, junction, ops->jp_error(status));
1061                 return NULL;
1062         }
1063
1064         status = ops->jp_get_locations(junction, &locations);
1065         switch (status) {
1066         case JP_OK:
1067                 break;
1068         case JP_NOTJUNCTION:
1069                 xlog(D_GENERAL, "%s: %s is not a junction",
1070                         __func__, junction);
1071                 goto out;
1072         default:
1073                 xlog(L_WARNING, "Dangling junction %s: %s",
1074                         junction, ops->jp_error(status));
1075                 goto out;
1076         }
1077
1078         parent = lookup_parent_export(dom, junction, ai);
1079         if (parent == NULL)
1080                 goto out;
1081
1082         exp = locations_to_export(ops, locations, junction, parent);
1083
1084         ops->jp_put_locations(locations);
1085
1086 out:
1087         ops->jp_done();
1088         return exp;
1089 }
1090
1091 /*
1092  * Load the junction plug-in, then try to resolve "pathname".
1093  * Returns pointer to an initialized exportent if "junction"
1094  * refers to a junction, or NULL if not.
1095  */
1096 static struct exportent *lookup_junction(char *dom, const char *pathname,
1097                 struct addrinfo *ai)
1098 {
1099         struct exportent *exp;
1100         struct link_map *map;
1101         void *handle;
1102
1103         handle = dlopen("libnfsjunct.so", RTLD_NOW);
1104         if (handle == NULL) {
1105                 xlog(D_GENERAL, "%s: dlopen: %s", __func__, dlerror());
1106                 return NULL;
1107         }
1108
1109         if (dlinfo(handle, RTLD_DI_LINKMAP, &map) == 0)
1110                 xlog(D_GENERAL, "%s: loaded plug-in %s",
1111                         __func__, map->l_name);
1112
1113         (void)dlerror();        /* Clear any error */
1114
1115         exp = invoke_junction_ops(handle, dom, pathname, ai);
1116
1117         /* We could leave it loaded to make junction resolution
1118          * faster next time.  However, if we want to replace the
1119          * library, that would require restarting mountd. */
1120         (void)dlclose(handle);
1121         return exp;
1122 }
1123
1124 static void lookup_nonexport(FILE *f, char *dom, char *path,
1125                 struct addrinfo *ai)
1126 {
1127         struct exportent *eep;
1128
1129         eep = lookup_junction(dom, path, ai);
1130         dump_to_cache(f, dom, path, eep);
1131         if (eep == NULL)
1132                 return;
1133         exportent_release(eep);
1134         free(eep);
1135 }
1136 #else   /* !HAVE_NFS_PLUGIN_H */
1137 static void lookup_nonexport(FILE *f, char *dom, char *path,
1138                 struct addrinfo *UNUSED(ai))
1139 {
1140         dump_to_cache(f, dom, path, NULL);
1141 }
1142 #endif  /* !HAVE_NFS_PLUGIN_H */
1143
1144 static void nfsd_export(FILE *f)
1145 {
1146         /* requests are:
1147          *  domain path
1148          * determine export options and return:
1149          *  domain path expiry flags anonuid anongid fsid
1150          */
1151
1152         char *cp;
1153         char *dom, *path;
1154         nfs_export *found = NULL;
1155         struct addrinfo *ai = NULL;
1156
1157         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
1158                 return;
1159
1160         xlog(D_CALL, "nfsd_export: inbuf '%s'", lbuf);
1161
1162         cp = lbuf;
1163         dom = malloc(strlen(cp));
1164         path = malloc(strlen(cp));
1165
1166         if (!dom || !path)
1167                 goto out;
1168
1169         if (qword_get(&cp, dom, strlen(lbuf)) <= 0)
1170                 goto out;
1171         if (qword_get(&cp, path, strlen(lbuf)) <= 0)
1172                 goto out;
1173
1174         auth_reload();
1175
1176         if (is_ipaddr_client(dom)) {
1177                 ai = lookup_client_addr(dom);
1178                 if (!ai)
1179                         goto out;
1180         }
1181
1182         found = lookup_export(dom, path, ai);
1183
1184         if (found) {
1185                 if (dump_to_cache(f, dom, path, &found->m_export) < 0) {
1186                         xlog(L_WARNING,
1187                              "Cannot export %s, possibly unsupported filesystem"
1188                              " or fsid= required", path);
1189                         dump_to_cache(f, dom, path, NULL);
1190                 }
1191         } else
1192                 lookup_nonexport(f, dom, path, ai);
1193
1194  out:
1195         xlog(D_CALL, "nfsd_export: found %p path %s", found, path ? path : NULL);
1196         if (dom) free(dom);
1197         if (path) free(path);
1198         freeaddrinfo(ai);
1199 }
1200
1201
1202 struct {
1203         char *cache_name;
1204         void (*cache_handle)(FILE *f);
1205         FILE *f;
1206         char vbuf[RPC_CHAN_BUF_SIZE];
1207 } cachelist[] = {
1208         { "auth.unix.ip", auth_unix_ip, NULL, ""},
1209         { "auth.unix.gid", auth_unix_gid, NULL, ""},
1210         { "nfsd.export", nfsd_export, NULL, ""},
1211         { "nfsd.fh", nfsd_fh, NULL, ""},
1212         { NULL, NULL, NULL, ""}
1213 };
1214
1215 extern int manage_gids;
1216
1217 /**
1218  * cache_open - prepare communications channels with kernel RPC caches
1219  *
1220  */
1221 void cache_open(void) 
1222 {
1223         int i;
1224         for (i=0; cachelist[i].cache_name; i++ ) {
1225                 char path[100];
1226                 if (!manage_gids && cachelist[i].cache_handle == auth_unix_gid)
1227                         continue;
1228                 sprintf(path, "/proc/net/rpc/%s/channel", cachelist[i].cache_name);
1229                 cachelist[i].f = fopen(path, "r+");
1230                 if (cachelist[i].f != NULL) {
1231                         setvbuf(cachelist[i].f, cachelist[i].vbuf, _IOLBF, 
1232                                 RPC_CHAN_BUF_SIZE);
1233                 }
1234         }
1235 }
1236
1237 /**
1238  * cache_set_fds - prepare cache file descriptors for one iteration of the service loop
1239  * @fdset: pointer to fd_set to prepare
1240  */
1241 void cache_set_fds(fd_set *fdset)
1242 {
1243         int i;
1244         for (i=0; cachelist[i].cache_name; i++) {
1245                 if (cachelist[i].f)
1246                         FD_SET(fileno(cachelist[i].f), fdset);
1247         }
1248 }
1249
1250 /**
1251  * cache_process_req - process any active cache file descriptors during service loop iteration
1252  * @fdset: pointer to fd_set to examine for activity
1253  */
1254 int cache_process_req(fd_set *readfds) 
1255 {
1256         int i;
1257         int cnt = 0;
1258         for (i=0; cachelist[i].cache_name; i++) {
1259                 if (cachelist[i].f != NULL &&
1260                     FD_ISSET(fileno(cachelist[i].f), readfds)) {
1261                         cnt++;
1262                         cachelist[i].cache_handle(cachelist[i].f);
1263                         FD_CLR(fileno(cachelist[i].f), readfds);
1264                 }
1265         }
1266         return cnt;
1267 }
1268
1269
1270 /*
1271  * Give IP->domain and domain+path->options to kernel
1272  * % echo nfsd $IP  $[now+DEFAULT_TTL] $domain > /proc/net/rpc/auth.unix.ip/channel
1273  * % echo $domain $path $[now+DEFAULT_TTL] $options $anonuid $anongid $fsid > /proc/net/rpc/nfsd.export/channel
1274  */
1275
1276 static int cache_export_ent(char *domain, struct exportent *exp, char *path)
1277 {
1278         int err;
1279         FILE *f = fopen("/proc/net/rpc/nfsd.export/channel", "w");
1280         if (!f)
1281                 return -1;
1282
1283         err = dump_to_cache(f, domain, exp->e_path, exp);
1284         if (err) {
1285                 xlog(L_WARNING,
1286                      "Cannot export %s, possibly unsupported filesystem or"
1287                      " fsid= required", exp->e_path);
1288         }
1289
1290         while (err == 0 && (exp->e_flags & NFSEXP_CROSSMOUNT) && path) {
1291                 /* really an 'if', but we can break out of
1292                  * a 'while' more easily */
1293                 /* Look along 'path' for other filesystems
1294                  * and export them with the same options
1295                  */
1296                 struct stat stb;
1297                 size_t l = strlen(exp->e_path);
1298                 __dev_t dev;
1299
1300                 if (strlen(path) <= l || path[l] != '/' ||
1301                     strncmp(exp->e_path, path, l) != 0)
1302                         break;
1303                 if (stat(exp->e_path, &stb) != 0)
1304                         break;
1305                 dev = stb.st_dev;
1306                 while(path[l] == '/') {
1307                         char c;
1308                         /* errors for submount should fail whole filesystem */
1309                         int err2;
1310
1311                         l++;
1312                         while (path[l] != '/' && path[l])
1313                                 l++;
1314                         c = path[l];
1315                         path[l] = 0;
1316                         err2 = lstat(path, &stb);
1317                         path[l] = c;
1318                         if (err2 < 0)
1319                                 break;
1320                         if (stb.st_dev == dev)
1321                                 continue;
1322                         dev = stb.st_dev;
1323                         path[l] = 0;
1324                         dump_to_cache(f, domain, path, exp);
1325                         path[l] = c;
1326                 }
1327                 break;
1328         }
1329
1330         fclose(f);
1331         return err;
1332 }
1333
1334 /**
1335  * cache_export - Inform kernel of a new nfs_export
1336  * @exp: target nfs_export
1337  * @path: NUL-terminated C string containing export path
1338  */
1339 int cache_export(nfs_export *exp, char *path)
1340 {
1341         char buf[INET6_ADDRSTRLEN];
1342         int err;
1343         FILE *f;
1344
1345         f = fopen("/proc/net/rpc/auth.unix.ip/channel", "w");
1346         if (!f)
1347                 return -1;
1348
1349
1350         qword_print(f, "nfsd");
1351         qword_print(f,
1352                 host_ntop(get_addrlist(exp->m_client, 0), buf, sizeof(buf)));
1353         qword_printtimefrom(f, exp->m_export.e_ttl);
1354         qword_print(f, exp->m_client->m_hostname);
1355         err = qword_eol(f);
1356         
1357         fclose(f);
1358
1359         err = cache_export_ent(exp->m_client->m_hostname, &exp->m_export, path)
1360                 || err;
1361         return err;
1362 }
1363
1364 /**
1365  * cache_get_filehandle - given an nfs_export, get its root filehandle
1366  * @exp: target nfs_export
1367  * @len: length of requested file handle
1368  * @p: NUL-terminated C string containing export path
1369  *
1370  * Returns pointer to NFS file handle of root directory of export
1371  *
1372  * { 
1373  *   echo $domain $path $length 
1374  *   read filehandle <&0
1375  * } <> /proc/fs/nfsd/filehandle
1376  */
1377 struct nfs_fh_len *
1378 cache_get_filehandle(nfs_export *exp, int len, char *p)
1379 {
1380         FILE *f = fopen("/proc/fs/nfsd/filehandle", "r+");
1381         char buf[200];
1382         char *bp = buf;
1383         int failed;
1384         static struct nfs_fh_len fh;
1385
1386         if (!f)
1387                 f = fopen("/proc/fs/nfs/filehandle", "r+");
1388         if (!f)
1389                 return NULL;
1390
1391         qword_print(f, exp->m_client->m_hostname);
1392         qword_print(f, p);
1393         qword_printint(f, len); 
1394         failed = qword_eol(f);
1395         
1396         if (!failed)
1397                 failed = (fgets(buf, sizeof(buf), f) == NULL);
1398         fclose(f);
1399         if (failed)
1400                 return NULL;
1401         memset(fh.fh_handle, 0, sizeof(fh.fh_handle));
1402         fh.fh_size = qword_get(&bp, (char *)fh.fh_handle, NFS3_FHSIZE);
1403         return &fh;
1404 }