Make warning about host matching multiple exports more helpful.
[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 <time.h>
18 #include <netinet/in.h>
19 #include <arpa/inet.h>
20 #include <unistd.h>
21 #include <fcntl.h>
22 #include <errno.h>
23 #include <ctype.h>
24 #include <pwd.h>
25 #include <grp.h>
26 #include "misc.h"
27 #include "nfslib.h"
28 #include "exportfs.h"
29 #include "mountd.h"
30 #include "xmalloc.h"
31 #include "fsloc.h"
32
33 #include "blkid/blkid.h"
34
35
36 enum nfsd_fsid {
37         FSID_DEV = 0,
38         FSID_NUM,
39         FSID_MAJOR_MINOR,
40         FSID_ENCODE_DEV,
41         FSID_UUID4_INUM,
42         FSID_UUID8,
43         FSID_UUID16,
44         FSID_UUID16_INUM,
45 };
46
47 /*
48  * Support routines for text-based upcalls.
49  * Fields are separated by spaces.
50  * Fields are either mangled to quote space tab newline slosh with slosh
51  * or a hexified with a leading \x
52  * Record is terminated with newline.
53  *
54  */
55 int cache_export_ent(char *domain, struct exportent *exp);
56
57
58 char *lbuf  = NULL;
59 int lbuflen = 0;
60
61 void auth_unix_ip(FILE *f)
62 {
63         /* requests are
64          *  class IP-ADDR
65          * Ignore if class != "nfsd"
66          * Otherwise find domainname and write back:
67          *
68          *  "nfsd" IP-ADDR expiry domainname
69          */
70         char *cp;
71         char class[20];
72         char ipaddr[20];
73         char *client;
74         struct in_addr addr;
75         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
76                 return;
77
78         cp = lbuf;
79
80         if (qword_get(&cp, class, 20) <= 0 ||
81             strcmp(class, "nfsd") != 0)
82                 return;
83
84         if (qword_get(&cp, ipaddr, 20) <= 0)
85                 return;
86
87         if (inet_aton(ipaddr, &addr)==0)
88                 return;
89
90         auth_reload();
91
92         /* addr is a valid, interesting address, find the domain name... */
93         client = client_compose(addr);
94
95         
96         qword_print(f, "nfsd");
97         qword_print(f, ipaddr);
98         qword_printint(f, time(0)+30*60);
99         if (client)
100                 qword_print(f, *client?client:"DEFAULT");
101         qword_eol(f);
102
103         if (client) free(client);
104         
105 }
106
107 void auth_unix_gid(FILE *f)
108 {
109         /* Request are
110          *  uid
111          * reply is
112          *  uid expiry count list of group ids
113          */
114         int uid;
115         struct passwd *pw;
116         gid_t glist[100], *groups = glist;
117         int ngroups = 100;
118         int rv, i;
119         char *cp;
120
121         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
122                 return;
123
124         cp = lbuf;
125         if (qword_get_int(&cp, &uid) != 0)
126                 return;
127
128         pw = getpwuid(uid);
129         if (!pw)
130                 rv = -1;
131         else {
132                 rv = getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups);
133                 if (rv == -1 && ngroups >= 100) {
134                         groups = malloc(sizeof(gid_t)*ngroups);
135                         if (!groups)
136                                 rv = -1;
137                         else
138                                 rv = getgrouplist(pw->pw_name, pw->pw_gid,
139                                                   groups, &ngroups);
140                 }
141         }
142         qword_printint(f, uid);
143         qword_printint(f, time(0)+30*60);
144         if (rv >= 0) {
145                 qword_printint(f, ngroups);
146                 for (i=0; i<ngroups; i++)
147                         qword_printint(f, groups[i]);
148         }
149         qword_eol(f);
150         if (groups != glist)
151                 free(groups);
152 }
153
154 int get_uuid(char *path, char *uuid, int uuidlen, char *u)
155 {
156         /* extract hex digits from uuidstr and compose a uuid
157          * of the given length (max 16), xoring bytes to make
158          * a smaller uuid.  Then compare with uuid
159          */
160         int i = 0;
161         const char *val;
162
163         if (path) {
164                 static blkid_cache cache = NULL;
165                 struct stat stb;
166                 char *devname;
167                 blkid_tag_iterate iter;
168                 blkid_dev dev;
169                 const char *type;
170                 if (cache == NULL)
171                         blkid_get_cache(&cache, NULL);
172
173                 blkid_probe_all_new(cache);
174
175                 if (stat(path, &stb) != 0)
176                         return 0;
177                 devname = blkid_devno_to_devname(stb.st_dev);
178                 if (!devname)
179                         return 0;
180                 dev = blkid_get_dev(cache, devname, BLKID_DEV_NORMAL);
181                 free(devname);
182                 if (!dev)
183                         return 0;
184                 iter = blkid_tag_iterate_begin(dev);
185                 if (!iter)
186                         return 0;
187                 while (blkid_tag_next(iter, &type, &val) == 0)
188                         if (strcmp(type, "UUID") == 0)
189                                 break;
190                 blkid_tag_iterate_end(iter);
191                 if (!type)
192                         return 0;
193         } else {
194                 val = uuid;
195         }
196         
197         memset(u, 0, uuidlen);
198         for ( ; *val ; val++) {
199                 char c = *val;
200                 if (!isxdigit(c))
201                         continue;
202                 if (isalpha(c)) {
203                         if (isupper(c))
204                                 c = c - 'A' + 10;
205                         else
206                                 c = c - 'a' + 10;
207                 } else
208                         c = c - '0' + 0;
209                 if ((i&1) == 0)
210                         c <<= 4;
211                 u[i/2] ^= c;
212                 i++;
213                 if (i == uuidlen*2)
214                         i = 0;
215         }
216         return 1;
217 }
218         
219
220 void nfsd_fh(FILE *f)
221 {
222         /* request are:
223          *  domain fsidtype fsid
224          * interpret fsid, find export point and options, and write:
225          *  domain fsidtype fsid expiry path
226          */
227         char *cp;
228         char *dom;
229         int fsidtype;
230         int fsidlen;
231         unsigned int dev, major=0, minor=0;
232         unsigned int inode=0;
233         unsigned long long inode64;
234         unsigned int fsidnum=0;
235         char fsid[32];
236         struct exportent *found = NULL;
237         nfs_export *exp;
238         int i;
239         int dev_missing = 0;
240         int uuidlen = 0;
241         char *fhuuid = NULL;
242
243         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
244                 return;
245
246         cp = lbuf;
247
248         dom = malloc(strlen(cp));
249         if (dom == NULL)
250                 return;
251         if (qword_get(&cp, dom, strlen(cp)) <= 0)
252                 goto out;
253         if (qword_get_int(&cp, &fsidtype) != 0)
254                 goto out;
255         if (fsidtype < 0 || fsidtype > 7)
256                 goto out; /* unknown type */
257         if ((fsidlen = qword_get(&cp, fsid, 32)) <= 0)
258                 goto out;
259         switch(fsidtype) {
260         case FSID_DEV: /* 4 bytes: 2 major, 2 minor, 4 inode */
261                 if (fsidlen != 8)
262                         goto out;
263                 memcpy(&dev, fsid, 4);
264                 memcpy(&inode, fsid+4, 4);
265                 major = ntohl(dev)>>16;
266                 minor = ntohl(dev) & 0xFFFF;
267                 break;
268
269         case FSID_NUM: /* 4 bytes - fsid */
270                 if (fsidlen != 4)
271                         goto out;
272                 memcpy(&fsidnum, fsid, 4);
273                 break;
274
275         case FSID_MAJOR_MINOR: /* 12 bytes: 4 major, 4 minor, 4 inode 
276                  * This format is never actually used but was
277                  * an historical accident
278                  */
279                 if (fsidlen != 12)
280                         goto out;
281                 memcpy(&dev, fsid, 4); major = ntohl(dev);
282                 memcpy(&dev, fsid+4, 4); minor = ntohl(dev);
283                 memcpy(&inode, fsid+8, 4);
284                 break;
285
286         case FSID_ENCODE_DEV: /* 8 bytes: 4 byte packed device number, 4 inode */
287                 /* This is *host* endian, not net-byte-order, because
288                  * no-one outside this host has any business interpreting it
289                  */
290                 if (fsidlen != 8)
291                         goto out;
292                 memcpy(&dev, fsid, 4);
293                 memcpy(&inode, fsid+4, 4);
294                 major = (dev & 0xfff00) >> 8;
295                 minor = (dev & 0xff) | ((dev >> 12) & 0xfff00);
296                 break;
297
298         case FSID_UUID4_INUM: /* 4 byte inode number and 4 byte uuid */
299                 if (fsidlen != 8)
300                         goto out;
301                 memcpy(&inode, fsid, 4);
302                 uuidlen = 4;
303                 fhuuid = fsid+4;
304                 break;
305         case FSID_UUID8: /* 8 byte uuid */
306                 if (fsidlen != 8)
307                         goto out;
308                 uuidlen = 8;
309                 fhuuid = fsid;
310                 break;
311         case FSID_UUID16: /* 16 byte uuid */
312                 if (fsidlen != 16)
313                         goto out;
314                 uuidlen = 16;
315                 fhuuid = fsid;
316                 break;
317         case FSID_UUID16_INUM: /* 8 byte inode number and 16 byte uuid */
318                 if (fsidlen != 24)
319                         goto out;
320                 memcpy(&inode64, fsid, 8);
321                 inode = inode64;
322                 uuidlen = 16;
323                 fhuuid = fsid+8;
324                 break;
325         }
326
327         auth_reload();
328
329         /* Now determine export point for this fsid/domain */
330         for (i=0 ; i < MCL_MAXTYPES; i++) {
331                 for (exp = exportlist[i]; exp; exp = exp->m_next) {
332                         struct stat stb;
333                         char u[16];                     
334
335                         if (!client_member(dom, exp->m_client->m_hostname))
336                                 continue;
337                         if (exp->m_export.e_mountpoint &&
338                             !is_mountpoint(exp->m_export.e_mountpoint[0]?
339                                            exp->m_export.e_mountpoint:
340                                            exp->m_export.e_path))
341                                 dev_missing ++;
342                         if (stat(exp->m_export.e_path, &stb) != 0)
343                                 continue;
344                         switch(fsidtype){
345                         case FSID_DEV:
346                         case FSID_MAJOR_MINOR:
347                         case FSID_ENCODE_DEV:
348                                 if (stb.st_ino != inode)
349                                         continue;
350                                 if (major != major(stb.st_dev) ||
351                                     minor != minor(stb.st_dev))
352                                         continue;
353                                 break;
354                         case FSID_NUM:
355                                 if (((exp->m_export.e_flags & NFSEXP_FSID) == 0 ||
356                                      exp->m_export.e_fsid != fsidnum))
357                                         continue;
358                                 break;
359                         case FSID_UUID4_INUM:
360                         case FSID_UUID16_INUM:
361                                 if (stb.st_ino != inode)
362                                         continue;
363                                 goto check_uuid;
364                         case FSID_UUID8:
365                         case FSID_UUID16:
366                                 if (!is_mountpoint(exp->m_export.e_path))
367                                         continue;
368                         check_uuid:
369                                 if (exp->m_export.e_uuid)
370                                         get_uuid(NULL, exp->m_export.e_uuid,
371                                                  uuidlen, u);
372                                 else if (get_uuid(exp->m_export.e_path, NULL,
373                                                   uuidlen, u) == 0)
374                                         continue;
375
376                                 if (memcmp(u, fhuuid, uuidlen) != 0)
377                                         continue;
378                                 break;
379                         }
380                         /* It's a match !! */
381                         if (!found)
382                                 found = &exp->m_export;
383                         else if (strcmp(found->e_path, exp->m_export.e_path)!= 0)
384                         {
385                                 xlog(L_WARNING, "%s and %s have same filehandle for %s, using first",
386                                      found->e_path, exp->m_export.e_path, dom);
387                         }
388                 }
389         }
390         if (found && 
391             found->e_mountpoint &&
392             !is_mountpoint(found->e_mountpoint[0]?
393                            found->e_mountpoint:
394                            found->e_path)) {
395                 /* Cannot export this yet 
396                  * should log a warning, but need to rate limit
397                    xlog(L_WARNING, "%s not exported as %d not a mountpoint",
398                    found->e_path, found->e_mountpoint);
399                  */
400                 /* FIXME we need to make sure we re-visit this later */
401                 goto out;
402         }
403         if (!found && dev_missing) {
404                 /* The missing dev could be what we want, so just be
405                  * quite rather than returning stale yet
406                  */
407                 goto out;
408         }
409
410         if (found)
411                 cache_export_ent(dom, found);
412
413         qword_print(f, dom);
414         qword_printint(f, fsidtype);
415         qword_printhex(f, fsid, fsidlen);
416         /* The fsid -> path lookup can be quite expensive as it
417          * potentially stats and reads lots of devices, and some of those
418          * might have spun-down.  The Answer is not likely to
419          * change underneath us, and an 'exportfs -f' can always
420          * remove this from the kernel, so use a really log
421          * timeout.  Maybe this should be configurable on the command
422          * line.
423          */
424         qword_printint(f, 0x7fffffff);
425         if (found)
426                 qword_print(f, found->e_path);
427         qword_eol(f);
428  out:
429         free(dom);
430         return;         
431 }
432
433 static void write_fsloc(FILE *f, struct exportent *ep, char *path)
434 {
435         struct servers *servers;
436
437         if (ep->e_fslocmethod == FSLOC_NONE)
438                 return;
439
440         servers = replicas_lookup(ep->e_fslocmethod, ep->e_fslocdata, path);
441         if (!servers)
442                 return;
443         qword_print(f, "fsloc");
444         qword_printint(f, servers->h_num);
445         if (servers->h_num >= 0) {
446                 int i;
447                 for (i=0; i<servers->h_num; i++) {
448                         qword_print(f, servers->h_mp[i]->h_host);
449                         qword_print(f, servers->h_mp[i]->h_path);
450                 }
451         }
452         qword_printint(f, servers->h_referral);
453         release_replicas(servers);
454 }
455
456 static int dump_to_cache(FILE *f, char *domain, char *path, struct exportent *exp)
457 {
458         qword_print(f, domain);
459         qword_print(f, path);
460         qword_printint(f, time(0)+30*60);
461         if (exp) {
462                 qword_printint(f, exp->e_flags);
463                 qword_printint(f, exp->e_anonuid);
464                 qword_printint(f, exp->e_anongid);
465                 qword_printint(f, exp->e_fsid);
466                 write_fsloc(f, exp, path);
467                 if (exp->e_uuid == NULL) {
468                         char u[16];
469                         if (get_uuid(exp->e_path, NULL, 16, u)) {
470                                 qword_print(f, "uuid");
471                                 qword_printhex(f, u, 16);
472                         }
473                 } else if (exp->e_uuid) {
474                         qword_print(f, "uuid");
475                         qword_printhex(f, exp->e_uuid, 16);
476                 }
477         }
478         return qword_eol(f);
479 }
480
481 void nfsd_export(FILE *f)
482 {
483         /* requests are:
484          *  domain path
485          * determine export options and return:
486          *  domain path expiry flags anonuid anongid fsid
487          */
488
489         char *cp;
490         int i;
491         char *dom, *path;
492         nfs_export *exp, *found = NULL;
493         int found_type = 0;
494
495
496         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
497                 return;
498
499         cp = lbuf;
500         dom = malloc(strlen(cp));
501         path = malloc(strlen(cp));
502
503         if (!dom || !path)
504                 goto out;
505
506         if (qword_get(&cp, dom, strlen(lbuf)) <= 0)
507                 goto out;
508         if (qword_get(&cp, path, strlen(lbuf)) <= 0)
509                 goto out;
510
511         auth_reload();
512
513         /* now find flags for this export point in this domain */
514         for (i=0 ; i < MCL_MAXTYPES; i++) {
515                 for (exp = exportlist[i]; exp; exp = exp->m_next) {
516                         if (!client_member(dom, exp->m_client->m_hostname))
517                                 continue;
518                         if (strcmp(path, exp->m_export.e_path))
519                                 continue;
520                         if (!found) {
521                                 found = exp;
522                                 found_type = i;
523                         } else if (found_type == i && found->m_warned == 0) {
524                                 xlog(L_WARNING, "%s exported to both %s and %s, "
525                                      "arbitrarily choosing options from first",
526                                      path, found->m_client->m_hostname, exp->m_client->m_hostname,
527                                      dom);
528                                 found->m_warned = 1;
529                         }
530                 }
531         }
532
533         if (found) {
534                 dump_to_cache(f, dom, path, &found->m_export);
535                 mountlist_add(dom, path);
536         } else {
537                 dump_to_cache(f, dom, path, NULL);
538         }
539  out:
540         if (dom) free(dom);
541         if (path) free(path);
542 }
543
544
545 struct {
546         char *cache_name;
547         void (*cache_handle)(FILE *f);
548         FILE *f;
549 } cachelist[] = {
550         { "auth.unix.ip", auth_unix_ip},
551         { "auth.unix.gid", auth_unix_gid},
552         { "nfsd.export", nfsd_export},
553         { "nfsd.fh", nfsd_fh},
554         { NULL, NULL }
555 };
556
557 extern int manage_gids;
558 void cache_open(void) 
559 {
560         int i;
561         for (i=0; cachelist[i].cache_name; i++ ) {
562                 char path[100];
563                 if (!manage_gids && cachelist[i].cache_handle == auth_unix_gid)
564                         continue;
565                 sprintf(path, "/proc/net/rpc/%s/channel", cachelist[i].cache_name);
566                 cachelist[i].f = fopen(path, "r+");
567         }
568 }
569
570 void cache_set_fds(fd_set *fdset)
571 {
572         int i;
573         for (i=0; cachelist[i].cache_name; i++) {
574                 if (cachelist[i].f)
575                         FD_SET(fileno(cachelist[i].f), fdset);
576         }
577 }
578
579 int cache_process_req(fd_set *readfds) 
580 {
581         int i;
582         int cnt = 0;
583         for (i=0; cachelist[i].cache_name; i++) {
584                 if (cachelist[i].f != NULL &&
585                     FD_ISSET(fileno(cachelist[i].f), readfds)) {
586                         cnt++;
587                         cachelist[i].cache_handle(cachelist[i].f);
588                         FD_CLR(fileno(cachelist[i].f), readfds);
589                 }
590         }
591         return cnt;
592 }
593
594
595 /*
596  * Give IP->domain and domain+path->options to kernel
597  * % echo nfsd $IP  $[now+30*60] $domain > /proc/net/rpc/auth.unix.ip/channel
598  * % echo $domain $path $[now+30*60] $options $anonuid $anongid $fsid > /proc/net/rpc/nfsd.export/channel
599  */
600
601 int cache_export_ent(char *domain, struct exportent *exp)
602 {
603         int err;
604         FILE *f = fopen("/proc/net/rpc/nfsd.export/channel", "w");
605         if (!f)
606                 return -1;
607
608         err = dump_to_cache(f, domain, exp->e_path, exp);
609         fclose(f);
610         mountlist_add(domain, exp->e_path);
611         return err;
612 }
613
614 int cache_export(nfs_export *exp)
615 {
616         int err;
617         FILE *f;
618
619         f = fopen("/proc/net/rpc/auth.unix.ip/channel", "w");
620         if (!f)
621                 return -1;
622
623         qword_print(f, "nfsd");
624         qword_print(f, inet_ntoa(exp->m_client->m_addrlist[0]));
625         qword_printint(f, time(0)+30*60);
626         qword_print(f, exp->m_client->m_hostname);
627         err = qword_eol(f);
628         
629         fclose(f);
630
631         err = cache_export_ent(exp->m_client->m_hostname, &exp->m_export)
632                 || err;
633         return err;
634 }
635
636 /* Get a filehandle.
637  * { 
638  *   echo $domain $path $length 
639  *   read filehandle <&0
640  * } <> /proc/fs/nfsd/filehandle
641  */
642 struct nfs_fh_len *
643 cache_get_filehandle(nfs_export *exp, int len, char *p)
644 {
645         FILE *f = fopen("/proc/fs/nfsd/filehandle", "r+");
646         char buf[200];
647         char *bp = buf;
648         int failed;
649         static struct nfs_fh_len fh;
650
651         if (!f)
652                 f = fopen("/proc/fs/nfs/filehandle", "r+");
653         if (!f)
654                 return NULL;
655
656         qword_print(f, exp->m_client->m_hostname);
657         qword_print(f, p);
658         qword_printint(f, len); 
659         failed = qword_eol(f);
660         
661         if (!failed)
662                 failed = (fgets(buf, sizeof(buf), f) == NULL);
663         fclose(f);
664         if (failed)
665                 return NULL;
666         memset(fh.fh_handle, 0, sizeof(fh.fh_handle));
667         fh.fh_size = qword_get(&bp, (char *)fh.fh_handle, NFS3_FHSIZE);
668         return &fh;
669 }
670