Fix silly bug with gid lookup
[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         qword_printint(f, time(0)+30*60);
417         if (found)
418                 qword_print(f, found->e_path);
419         qword_eol(f);
420  out:
421         free(dom);
422         return;         
423 }
424
425 static void write_fsloc(FILE *f, struct exportent *ep, char *path)
426 {
427         struct servers *servers;
428
429         if (ep->e_fslocmethod == FSLOC_NONE)
430                 return;
431
432         servers = replicas_lookup(ep->e_fslocmethod, ep->e_fslocdata, path);
433         if (!servers)
434                 return;
435         qword_print(f, "fsloc");
436         qword_printint(f, servers->h_num);
437         if (servers->h_num >= 0) {
438                 int i;
439                 for (i=0; i<servers->h_num; i++) {
440                         qword_print(f, servers->h_mp[i]->h_host);
441                         qword_print(f, servers->h_mp[i]->h_path);
442                 }
443         }
444         qword_printint(f, servers->h_referral);
445         release_replicas(servers);
446 }
447
448 static int dump_to_cache(FILE *f, char *domain, char *path, struct exportent *exp)
449 {
450         qword_print(f, domain);
451         qword_print(f, path);
452         qword_printint(f, time(0)+30*60);
453         if (exp) {
454                 qword_printint(f, exp->e_flags);
455                 qword_printint(f, exp->e_anonuid);
456                 qword_printint(f, exp->e_anongid);
457                 qword_printint(f, exp->e_fsid);
458                 write_fsloc(f, exp, path);
459                 if (exp->e_uuid == NULL) {
460                         char u[16];
461                         if (get_uuid(exp->e_path, NULL, 16, u)) {
462                                 qword_print(f, "uuid");
463                                 qword_printhex(f, u, 16);
464                         }
465                 } else if (exp->e_uuid) {
466                         qword_print(f, "uuid");
467                         qword_printhex(f, exp->e_uuid, 16);
468                 }
469         }
470         return qword_eol(f);
471 }
472
473 void nfsd_export(FILE *f)
474 {
475         /* requests are:
476          *  domain path
477          * determine export options and return:
478          *  domain path expiry flags anonuid anongid fsid
479          */
480
481         char *cp;
482         int i;
483         char *dom, *path;
484         nfs_export *exp, *found = NULL;
485
486
487         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
488                 return;
489
490         cp = lbuf;
491         dom = malloc(strlen(cp));
492         path = malloc(strlen(cp));
493
494         if (!dom || !path)
495                 goto out;
496
497         if (qword_get(&cp, dom, strlen(lbuf)) <= 0)
498                 goto out;
499         if (qword_get(&cp, path, strlen(lbuf)) <= 0)
500                 goto out;
501
502         auth_reload();
503
504         /* now find flags for this export point in this domain */
505         for (i=0 ; i < MCL_MAXTYPES; i++) {
506                 for (exp = exportlist[i]; exp; exp = exp->m_next) {
507                         if (!client_member(dom, exp->m_client->m_hostname))
508                                 continue;
509                         if (strcmp(path, exp->m_export.e_path))
510                                 continue;
511                         if (!found)
512                                 found = exp;
513                         else {
514                                 xlog(L_WARNING, "%s exported to both %s and %s in %s",
515                                      path, exp->m_client->m_hostname, found->m_client->m_hostname,
516                                      dom);
517                         }
518                 }
519         }
520
521         if (found) {
522                 dump_to_cache(f, dom, path, &found->m_export);
523                 mountlist_add(dom, path);
524         } else {
525                 dump_to_cache(f, dom, path, NULL);
526         }
527  out:
528         if (dom) free(dom);
529         if (path) free(path);
530 }
531
532
533 struct {
534         char *cache_name;
535         void (*cache_handle)(FILE *f);
536         FILE *f;
537 } cachelist[] = {
538         { "auth.unix.ip", auth_unix_ip},
539         { "auth.unix.gid", auth_unix_gid},
540         { "nfsd.export", nfsd_export},
541         { "nfsd.fh", nfsd_fh},
542         { NULL, NULL }
543 };
544
545 extern int manage_gids;
546 void cache_open(void) 
547 {
548         int i;
549         for (i=0; cachelist[i].cache_name; i++ ) {
550                 char path[100];
551                 if (!manage_gids && cachelist[i].cache_handle == auth_unix_gid)
552                         continue;
553                 sprintf(path, "/proc/net/rpc/%s/channel", cachelist[i].cache_name);
554                 cachelist[i].f = fopen(path, "r+");
555         }
556 }
557
558 void cache_set_fds(fd_set *fdset)
559 {
560         int i;
561         for (i=0; cachelist[i].cache_name; i++) {
562                 if (cachelist[i].f)
563                         FD_SET(fileno(cachelist[i].f), fdset);
564         }
565 }
566
567 int cache_process_req(fd_set *readfds) 
568 {
569         int i;
570         int cnt = 0;
571         for (i=0; cachelist[i].cache_name; i++) {
572                 if (cachelist[i].f != NULL &&
573                     FD_ISSET(fileno(cachelist[i].f), readfds)) {
574                         cnt++;
575                         cachelist[i].cache_handle(cachelist[i].f);
576                         FD_CLR(fileno(cachelist[i].f), readfds);
577                 }
578         }
579         return cnt;
580 }
581
582
583 /*
584  * Give IP->domain and domain+path->options to kernel
585  * % echo nfsd $IP  $[now+30*60] $domain > /proc/net/rpc/auth.unix.ip/channel
586  * % echo $domain $path $[now+30*60] $options $anonuid $anongid $fsid > /proc/net/rpc/nfsd.export/channel
587  */
588
589 int cache_export_ent(char *domain, struct exportent *exp)
590 {
591         int err;
592         FILE *f = fopen("/proc/net/rpc/nfsd.export/channel", "w");
593         if (!f)
594                 return -1;
595
596         err = dump_to_cache(f, domain, exp->e_path, exp);
597         fclose(f);
598         mountlist_add(domain, exp->e_path);
599         return err;
600 }
601
602 int cache_export(nfs_export *exp)
603 {
604         int err;
605         FILE *f;
606
607         if (exp->m_export.e_maptype != CLE_MAP_IDENT) {
608                 xlog(L_ERROR, "%s: unsupported mapping; kernel supports only 'identity' (default)",
609                     exp->m_export.m_path);
610                 return -1;
611         }
612
613         f = fopen("/proc/net/rpc/auth.unix.ip/channel", "w");
614         if (!f)
615                 return -1;
616
617         qword_print(f, "nfsd");
618         qword_print(f, inet_ntoa(exp->m_client->m_addrlist[0]));
619         qword_printint(f, time(0)+30*60);
620         qword_print(f, exp->m_client->m_hostname);
621         err = qword_eol(f);
622         
623         fclose(f);
624
625         err = cache_export_ent(exp->m_client->m_hostname, &exp->m_export)
626                 || err;
627         return err;
628 }
629
630 /* Get a filehandle.
631  * { 
632  *   echo $domain $path $length 
633  *   read filehandle <&0
634  * } <> /proc/fs/nfsd/filehandle
635  */
636 struct nfs_fh_len *
637 cache_get_filehandle(nfs_export *exp, int len, char *p)
638 {
639         FILE *f = fopen("/proc/fs/nfsd/filehandle", "r+");
640         char buf[200];
641         char *bp = buf;
642         int failed;
643         static struct nfs_fh_len fh;
644
645         if (!f)
646                 f = fopen("/proc/fs/nfs/filehandle", "r+");
647         if (!f)
648                 return NULL;
649
650         qword_print(f, exp->m_client->m_hostname);
651         qword_print(f, p);
652         qword_printint(f, len); 
653         failed = qword_eol(f);
654         
655         if (!failed)
656                 failed = (fgets(buf, sizeof(buf), f) == NULL);
657         fclose(f);
658         if (failed)
659                 return NULL;
660         memset(fh.fh_handle, 0, sizeof(fh.fh_handle));
661         fh.fh_size = qword_get(&bp, (char *)fh.fh_handle, NFS3_FHSIZE);
662         return &fh;
663 }
664