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