2005-08-26 Kevin Coffman <kwc@citi.umich.edu>
[nfs-utils.git] / utils / mountd / mountd.c
1 /*
2  * utils/mountd/mountd.c
3  *
4  * Authenticate mount requests and retrieve file handle.
5  *
6  * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
7  */
8
9 #include "config.h"
10
11 #include <signal.h>
12 #include <sys/stat.h>
13 #include <netinet/in.h>
14 #include <arpa/inet.h>
15 #include <unistd.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <getopt.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <sys/resource.h>
22 #include "xmalloc.h"
23 #include "misc.h"
24 #include "mountd.h"
25 #include "rpcmisc.h"
26
27 extern void     cache_open(void);
28 extern struct nfs_fh_len *cache_get_filehandle(nfs_export *exp, int len, char *p);
29 extern void cache_export(nfs_export *exp);
30
31 extern void my_svc_run(void);
32
33 static void             usage(const char *, int exitcode);
34 static exports          get_exportlist(void);
35 static struct nfs_fh_len *get_rootfh(struct svc_req *, dirpath *, int *, int v3);
36
37 int new_cache = 0;
38
39 /* PRC: a high-availability callout program can be specified with -H
40  * When this is done, the program will receive callouts whenever clients
41  * send mount or unmount requests -- the callout is not needed for 2.6 kernel */
42 char *ha_callout_prog = NULL;
43
44 static struct option longopts[] =
45 {
46         { "foreground", 0, 0, 'F' },
47         { "descriptors", 1, 0, 'o' },
48         { "debug", 1, 0, 'd' },
49         { "help", 0, 0, 'h' },
50         { "exports-file", 1, 0, 'f' },
51         { "nfs-version", 1, 0, 'V' },
52         { "no-nfs-version", 1, 0, 'N' },
53         { "version", 0, 0, 'v' },
54         { "port", 1, 0, 'p' },
55         { "no-tcp", 0, 0, 'n' },
56         { "ha-callout", 1, 0, 'H' },
57         { NULL, 0, 0, 0 }
58 };
59
60 static int nfs_version = -1;
61
62 /*
63  * Signal handler.
64  */
65 static void 
66 killer (int sig)
67 {
68   if (nfs_version & 0x1)
69     pmap_unset (MOUNTPROG, MOUNTVERS);
70   if (nfs_version & (0x1 << 1))
71     pmap_unset (MOUNTPROG, MOUNTVERS_POSIX);
72   if (nfs_version & (0x1 << 2))
73     pmap_unset (MOUNTPROG, MOUNTVERS_NFSV3);
74   xlog (L_FATAL, "Caught signal %d, un-registering and exiting.", sig);
75 }
76
77 static void
78 sig_hup (int sig)
79 {
80   /* don't exit on SIGHUP */
81   xlog (L_NOTICE, "Received SIGHUP... Ignoring.\n", sig);
82   return;
83 }
84
85 bool_t
86 mount_null_1_svc(struct svc_req *rqstp, void *argp, void *resp)
87 {
88         return 1;
89 }
90
91 bool_t
92 mount_mnt_1_svc(struct svc_req *rqstp, dirpath *path, fhstatus *res)
93 {
94         struct nfs_fh_len *fh;
95
96         xlog(D_CALL, "MNT1(%s) called", *path);
97         if ((fh = get_rootfh(rqstp, path, &res->fhs_status, 0)) != NULL)
98                 memcpy(&res->fhstatus_u.fhs_fhandle, fh->fh_handle, 32);
99         return 1;
100 }
101
102 bool_t
103 mount_dump_1_svc(struct svc_req *rqstp, void *argp, mountlist *res)
104 {
105         struct sockaddr_in *addr =
106                 (struct sockaddr_in *) svc_getcaller(rqstp->rq_xprt);
107         xlog(L_NOTICE, "dump request from %s",
108                 inet_ntoa(addr->sin_addr));
109
110         *res = mountlist_list();
111         return 1;
112 }
113
114 bool_t
115 mount_umnt_1_svc(struct svc_req *rqstp, dirpath *argp, void *resp)
116 {
117         struct sockaddr_in *sin
118                 = (struct sockaddr_in *) svc_getcaller(rqstp->rq_xprt);
119         nfs_export      *exp;
120         char            *p = *argp;
121         char            rpath[MAXPATHLEN+1];
122
123         if (*p == '\0')
124                 p = "/";
125
126         if (realpath(p, rpath) != NULL) {
127                 rpath[sizeof (rpath) - 1] = '\0';
128                 p = rpath;
129         }
130
131         if (!(exp = auth_authenticate("unmount", sin, p))) {
132                 return 1;
133         }
134         if (new_cache) {
135                 if (strcmp(inet_ntoa(exp->m_client->m_addrlist[0]), exp->m_client->m_hostname))
136                         mountlist_del(inet_ntoa(exp->m_client->m_addrlist[0]), exp->m_client->m_hostname);
137                 mountlist_del(exp->m_client->m_hostname, p);
138         } else {
139                 mountlist_del(exp->m_client->m_hostname, p);
140                 export_reset (exp);
141         }
142         return 1;
143 }
144
145 bool_t
146 mount_umntall_1_svc(struct svc_req *rqstp, void *argp, void *resp)
147 {
148         /* Reload /etc/xtab if necessary */
149         auth_reload();
150
151         mountlist_del_all((struct sockaddr_in *) svc_getcaller(rqstp->rq_xprt));
152         return 1;
153 }
154
155 bool_t
156 mount_export_1_svc(struct svc_req *rqstp, void *argp, exports *resp)
157 {
158         struct sockaddr_in *addr =
159                 (struct sockaddr_in *) svc_getcaller(rqstp->rq_xprt);
160         xlog(L_NOTICE, "export request from %s",
161                 inet_ntoa(addr->sin_addr));
162         *resp = get_exportlist();
163         return 1;
164 }
165
166 bool_t
167 mount_exportall_1_svc(struct svc_req *rqstp, void *argp, exports *resp)
168 {
169         struct sockaddr_in *addr =
170                 (struct sockaddr_in *) svc_getcaller(rqstp->rq_xprt);
171         xlog(L_NOTICE, "exportall request from %s",
172                 inet_ntoa(addr->sin_addr));
173         *resp = get_exportlist();
174         return 1;
175 }
176
177 /*
178  * MNTv2 pathconf procedure
179  *
180  * The protocol doesn't include a status field, so Sun apparently considers
181  * it good practice to let anyone snoop on your system, even if it's
182  * pretty harmless data such as pathconf. We don't.
183  *
184  * Besides, many of the pathconf values don't make much sense on NFS volumes.
185  * FIFOs and tty device files represent devices on the *client*, so there's
186  * no point in getting the server's buffer sizes etc.
187  */
188 bool_t
189 mount_pathconf_2_svc(struct svc_req *rqstp, dirpath *path, ppathcnf *res)
190 {
191         struct sockaddr_in *sin
192                 = (struct sockaddr_in *) svc_getcaller(rqstp->rq_xprt);
193         struct stat     stb;
194         nfs_export      *exp;
195         char            rpath[MAXPATHLEN+1];
196         char            *p = *path;
197
198         memset(res, 0, sizeof(*res));
199
200         if (*p == '\0')
201                 p = "/";
202
203         /* Reload /etc/xtab if necessary */
204         auth_reload();
205
206         /* Resolve symlinks */
207         if (realpath(p, rpath) != NULL) {
208                 rpath[sizeof (rpath) - 1] = '\0';
209                 p = rpath;
210         }
211
212         /* Now authenticate the intruder... */
213         if (!(exp = auth_authenticate("pathconf", sin, p))) {
214                 return 1;
215         } else if (stat(p, &stb) < 0) {
216                 xlog(L_WARNING, "can't stat exported dir %s: %s",
217                                 p, strerror(errno));
218                 export_reset (exp);
219                 return 1;
220         }
221
222         res->pc_link_max  = pathconf(p, _PC_LINK_MAX);
223         res->pc_max_canon = pathconf(p, _PC_MAX_CANON);
224         res->pc_max_input = pathconf(p, _PC_MAX_INPUT);
225         res->pc_name_max  = pathconf(p, _PC_NAME_MAX);
226         res->pc_path_max  = pathconf(p, _PC_PATH_MAX);
227         res->pc_pipe_buf  = pathconf(p, _PC_PIPE_BUF);
228         res->pc_vdisable  = pathconf(p, _PC_VDISABLE);
229
230         /* Can't figure out what to do with pc_mask */
231         res->pc_mask[0]   = 0;
232         res->pc_mask[1]   = 0;
233
234         export_reset (exp);
235
236         return 1;
237 }
238
239 /*
240  * NFSv3 MOUNT procedure
241  */
242 bool_t
243 mount_mnt_3_svc(struct svc_req *rqstp, dirpath *path, mountres3 *res)
244 {
245 #define AUTH_GSS_KRB5 390003
246 #define AUTH_GSS_KRB5I 390004
247 #define AUTH_GSS_KRB5P 390005
248         static int      flavors[] = { AUTH_NULL, AUTH_UNIX, AUTH_GSS_KRB5, AUTH_GSS_KRB5I, AUTH_GSS_KRB5P};
249         struct nfs_fh_len *fh;
250
251         xlog(D_CALL, "MNT3(%s) called", *path);
252         if ((fh = get_rootfh(rqstp, path, (int *) &res->fhs_status, 1)) != NULL) {
253                 struct mountres3_ok     *ok = &res->mountres3_u.mountinfo;
254
255                 ok->fhandle.fhandle3_len = fh->fh_size;
256                 ok->fhandle.fhandle3_val = fh->fh_handle;
257                 ok->auth_flavors.auth_flavors_len
258                         = sizeof(flavors)/sizeof(flavors[0]);
259                 ok->auth_flavors.auth_flavors_val = flavors;
260         }
261         return 1;
262 }
263
264 static struct nfs_fh_len *
265 get_rootfh(struct svc_req *rqstp, dirpath *path, int *error, int v3)
266 {
267         struct sockaddr_in *sin =
268                 (struct sockaddr_in *) svc_getcaller(rqstp->rq_xprt);
269         struct stat     stb, estb;
270         nfs_export      *exp;
271         char            rpath[MAXPATHLEN+1];
272         char            *p = *path;
273
274         if (*p == '\0')
275                 p = "/";
276
277         /* Reload /var/lib/nfs/etab if necessary */
278         auth_reload();
279
280         /* Resolve symlinks */
281         if (realpath(p, rpath) != NULL) {
282                 rpath[sizeof (rpath) - 1] = '\0';
283                 p = rpath;
284         }
285
286         /* Now authenticate the intruder... */
287         if (!(exp = auth_authenticate("mount", sin, p))) {
288                 *error = NFSERR_ACCES;
289         } else if (stat(p, &stb) < 0) {
290                 xlog(L_WARNING, "can't stat exported dir %s: %s",
291                                 p, strerror(errno));
292                 if (errno == ENOENT)
293                         *error = NFSERR_NOENT;
294                 else
295                         *error = NFSERR_ACCES;
296         } else if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) {
297                 xlog(L_WARNING, "%s is not a directory or regular file", p);
298                 *error = NFSERR_NOTDIR;
299         } else if (stat(exp->m_export.e_path, &estb) < 0) {
300                 xlog(L_WARNING, "can't stat export point %s: %s",
301                      p, strerror(errno));
302                 *error = NFSERR_NOENT;
303         } else if (estb.st_dev != stb.st_dev
304                    /* && (!new_cache || !(exp->m_export.e_flags & NFSEXP_CROSSMOUNT)) */
305                 ) {
306                 xlog(L_WARNING, "request to export directory %s below nearest filesystem %s",
307                      p, exp->m_export.e_path);
308                 *error = NFSERR_ACCES;
309         } else if (exp->m_export.e_mountpoint &&
310                    !is_mountpoint(exp->m_export.e_mountpoint[0]?
311                                   exp->m_export.e_mountpoint:
312                                   exp->m_export.e_path)) {
313                 xlog(L_WARNING, "request to export an unmounted filesystem: %s",
314                      p);
315                 *error = NFSERR_NOENT;
316         } else if (new_cache) {
317                 /* This will be a static private nfs_export with just one
318                  * address.  We feed it to kernel then extract the filehandle,
319                  * 
320                  */
321                 struct nfs_fh_len  *fh;
322
323                 cache_export(exp);
324                 fh = cache_get_filehandle(exp, v3?64:32, p);
325                 if (fh == NULL) 
326                         *error = NFSERR_ACCES;
327                 else
328                         *error = NFS_OK;
329                 return fh;
330         } else {
331                 struct nfs_fh_len  *fh;
332
333                 if (exp->m_exported<1)
334                         export_export(exp);
335                 if (!exp->m_xtabent)
336                         xtab_append(exp);
337
338                 if (v3)
339                         fh = getfh_size ((struct sockaddr *) sin, p, 64);
340                 if (!v3 || (fh == NULL && errno == EINVAL)) {
341                         /* We first try the new nfs syscall. */
342                         fh = getfh ((struct sockaddr *) sin, p);
343                         if (fh == NULL && errno == EINVAL)
344                                 /* Let's try the old one. */
345                                 fh = getfh_old ((struct sockaddr *) sin,
346                                                 stb.st_dev, stb.st_ino);
347                 }
348                 if (fh != NULL) {
349                         mountlist_add(exp->m_client->m_hostname, p);
350                         *error = NFS_OK;
351                         export_reset (exp);
352                         return fh;
353                 }
354                 xlog(L_WARNING, "getfh failed: %s", strerror(errno));
355                 *error = NFSERR_ACCES;
356         }
357         export_reset (exp);
358         return NULL;
359 }
360
361 static exports
362 get_exportlist(void)
363 {
364         static exports          elist = NULL;
365         struct exportnode       *e, *ne;
366         struct groupnode        *g, *ng, *c, **cp;
367         nfs_export              *exp;
368         int                     i;
369
370         if (!auth_reload() && elist)
371                 return elist;
372
373         for (e = elist; e != NULL; e = ne) {
374                 ne = e->ex_next;
375                 for (g = e->ex_groups; g != NULL; g = ng) {
376                         ng = g->gr_next;
377                         xfree(g->gr_name);
378                         xfree(g);
379                 }
380                 xfree(e->ex_dir);
381                 xfree(e);
382         }
383         elist = NULL;
384
385         for (i = 0; i < MCL_MAXTYPES; i++) {
386                 for (exp = exportlist[i]; exp; exp = exp->m_next) {
387                         for (e = elist; e != NULL; e = e->ex_next) {
388                                 if (!strcmp(exp->m_export.m_path, e->ex_dir))
389                                         break;
390                         }
391                         if (!e) {
392                                 e = (struct exportnode *) xmalloc(sizeof(*e));
393                                 e->ex_next = elist;
394                                 e->ex_groups = NULL;
395                                 e->ex_dir = xstrdup(exp->m_export.m_path);
396                                 elist = e;
397                         }
398
399                         /* We need to check if we should remove
400                            previous ones. */
401                         if (i == MCL_ANONYMOUS && e->ex_groups) {
402                                 for (g = e->ex_groups; g; g = ng) {
403                                         ng = g->gr_next;
404                                         xfree(g->gr_name);
405                                         xfree(g);
406                                 }
407                                 e->ex_groups = NULL;
408                                 continue;
409                         }
410
411                         if (i != MCL_FQDN && e->ex_groups) {
412                           struct hostent        *hp;
413
414                           cp = &e->ex_groups;
415                           while ((c = *cp) != NULL) {
416                             if (client_gettype (c->gr_name) == MCL_FQDN
417                                 && (hp = gethostbyname(c->gr_name))) {
418                               hp = hostent_dup (hp);
419                               if (client_check(exp->m_client, hp)) {
420                                 *cp = c->gr_next;
421                                 xfree(c->gr_name);
422                                 xfree(c);
423                                 xfree (hp);
424                                 if ((c = *cp) == NULL)
425                                   break;
426                               }
427                               else
428                                 xfree (hp);
429                             }
430                             cp = &(c->gr_next);
431                           }
432                         }
433
434                         if (exp->m_export.e_hostname [0] != '\0') {
435                                 for (g = e->ex_groups; g; g = g->gr_next)
436                                         if (strcmp (exp->m_export.e_hostname,
437                                                     g->gr_name) == 0)
438                                                 break;
439                                 if (g)
440                                         continue;
441                                 g = (struct groupnode *) xmalloc(sizeof(*g));
442                                 g->gr_name = xstrdup(exp->m_export.e_hostname);
443                                 g->gr_next = e->ex_groups;
444                                 e->ex_groups = g;
445                         }
446                 }
447         }
448
449         return elist;
450 }
451
452 int
453 main(int argc, char **argv)
454 {
455         char    *export_file = _PATH_EXPORTS;
456         int     foreground = 0;
457         int     port = 0;
458         int     descriptors = 0;
459         int     c;
460         struct sigaction sa;
461         struct rlimit rlim;
462
463         /* Parse the command line options and arguments. */
464         opterr = 0;
465         while ((c = getopt_long(argc, argv, "o:n:Fd:f:p:P:hH:N:V:v", longopts, NULL)) != EOF)
466                 switch (c) {
467                 case 'o':
468                         descriptors = atoi(optarg);
469                         if (descriptors <= 0) {
470                                 fprintf(stderr, "%s: bad descriptors: %s\n",
471                                         argv [0], optarg);
472                                 usage(argv [0], 1);
473                         }
474                         break;
475                 case 'F':
476                         foreground = 1;
477                         break;
478                 case 'd':
479                         xlog_sconfig(optarg, 1);
480                         break;
481                 case 'f':
482                         export_file = optarg;
483                         break;
484                 case 'H': /* PRC: specify a high-availability callout program */
485                         ha_callout_prog = optarg;
486                         break;
487                 case 'h':
488                         usage(argv [0], 0);
489                         break;
490                 case 'P':       /* XXX for nfs-server compatibility */
491                 case 'p':
492                         port = atoi(optarg);
493                         if (port <= 0 || port > 65535) {
494                                 fprintf(stderr, "%s: bad port number: %s\n",
495                                         argv [0], optarg);
496                                 usage(argv [0], 1);
497                         }
498                         break;
499                 case 'N':
500                         nfs_version &= ~(1 << (atoi (optarg) - 1));
501                         break;
502                 case 'n':
503                         _rpcfdtype = SOCK_DGRAM;
504                         break;
505                 case 'V':
506                         nfs_version |= 1 << (atoi (optarg) - 1);
507                         break;
508                 case 'v':
509                         printf("kmountd %s\n", VERSION);
510                         exit(0);
511                 case 0:
512                         break;
513                 case '?':
514                 default:
515                         usage(argv [0], 1);
516                 }
517
518         /* No more arguments allowed. */
519         if (optind != argc || !(nfs_version & 0x7))
520                 usage(argv [0], 1);
521
522         if (chdir(NFS_STATEDIR)) {
523                 fprintf(stderr, "%s: chdir(%s) failed: %s\n",
524                         argv [0], NFS_STATEDIR, strerror(errno));
525                 exit(1);
526         }
527
528         if (getrlimit (RLIMIT_NOFILE, &rlim) != 0)
529                 fprintf(stderr, "%s: getrlimit (RLIMIT_NOFILE) failed: %s\n",
530                                 argv [0], strerror(errno));
531         else {
532                 /* glibc sunrpc code dies if getdtablesize > FD_SETSIZE */
533                 if ((descriptors == 0 && rlim.rlim_cur > FD_SETSIZE) ||
534                     descriptors > FD_SETSIZE)
535                         descriptors = FD_SETSIZE;
536                 if (descriptors) {
537                         rlim.rlim_cur = descriptors;
538                         if (setrlimit (RLIMIT_NOFILE, &rlim) != 0) {
539                                 fprintf(stderr, "%s: setrlimit (RLIMIT_NOFILE) failed: %s\n",
540                                         argv [0], strerror(errno));
541                                 exit(1);
542                         }
543                 }
544         }
545         /* Initialize logging. */
546         if (!foreground) xlog_stderr(0);
547         xlog_open("mountd");
548
549         sa.sa_handler = SIG_IGN;
550         sa.sa_flags = 0;
551         sigemptyset(&sa.sa_mask);
552         sigaction(SIGHUP, &sa, NULL);
553         sigaction(SIGINT, &sa, NULL);
554         sigaction(SIGTERM, &sa, NULL);
555         sigaction(SIGPIPE, &sa, NULL);
556         /* WARNING: the following works on Linux and SysV, but not BSD! */
557         sigaction(SIGCHLD, &sa, NULL);
558
559         /* Daemons should close all extra filehandles ... *before* RPC init. */
560         if (!foreground) {
561                 int fd = sysconf (_SC_OPEN_MAX);
562                 while (--fd > 2)
563                         (void) close(fd);
564         }
565
566         new_cache = check_new_cache();
567         if (new_cache)
568                 cache_open();
569
570         if (nfs_version & 0x1)
571                 rpc_init("mountd", MOUNTPROG, MOUNTVERS,
572                          mount_dispatch, port);
573         if (nfs_version & (0x1 << 1))
574                 rpc_init("mountd", MOUNTPROG, MOUNTVERS_POSIX,
575                          mount_dispatch, port);
576         if (nfs_version & (0x1 << 2))
577                 rpc_init("mountd", MOUNTPROG, MOUNTVERS_NFSV3,
578                          mount_dispatch, port);
579
580         sa.sa_handler = killer;
581         sigaction(SIGINT, &sa, NULL);
582         sigaction(SIGTERM, &sa, NULL);
583         sa.sa_handler = sig_hup;
584         sigaction(SIGHUP, &sa, NULL);
585
586         auth_init(export_file);
587
588         if (!foreground) {
589                 /* We first fork off a child. */
590                 if ((c = fork()) > 0)
591                         exit(0);
592                 if (c < 0) {
593                         xlog(L_FATAL, "mountd: cannot fork: %s\n",
594                                                 strerror(errno));
595                 }
596                 /* Now we remove ourselves from the foreground.
597                    Redirect stdin/stdout/stderr first. */
598                 {
599                         int fd = open("/dev/null", O_RDWR);
600                         (void) dup2(fd, 0);
601                         (void) dup2(fd, 1);
602                         (void) dup2(fd, 2);
603                         if (fd > 2) (void) close(fd);
604                 }
605                 setsid();
606         }
607
608         my_svc_run();
609
610         xlog(L_ERROR, "Ack! Gack! svc_run returned!\n");
611         exit(1);
612 }
613
614 static void
615 usage(const char *prog, int n)
616 {
617         fprintf(stderr,
618 "Usage: %s [-F|--foreground] [-h|--help] [-v|--version] [-d kind|--debug kind]\n"
619 "       [-o num|--descriptors num] [-f exports-file|--exports-file=file]\n"
620 "       [-p|--port port] [-V version|--nfs-version version]\n"
621 "       [-N version|--no-nfs-version version] [-n|--no-tcp]\n"
622 "       [-H ha-callout-prog]\n", prog);
623         exit(n);
624 }