nfsidmap: Added Error Logging
[nfs-utils.git] / utils / exportfs / exportfs.c
1 /*
2  * utils/exportfs/exportfs.c
3  *
4  * Export file systems to knfsd
5  *
6  * Copyright (C) 1995, 1996, 1997 Olaf Kirch <okir@monad.swb.de>
7  *
8  * Extensive changes, 1999, Neil Brown <neilb@cse.unsw.edu.au>
9  */
10
11 #ifdef HAVE_CONFIG_H
12 #include <config.h>
13 #endif
14
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <sys/vfs.h>
18 #include <sys/stat.h>
19 #include <unistd.h>
20 #include <stdbool.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <stdarg.h>
24 #include <getopt.h>
25 #include <fcntl.h>
26 #include <netdb.h>
27 #include <errno.h>
28 #include <dirent.h>
29
30 #include "sockaddr.h"
31 #include "misc.h"
32 #include "nfslib.h"
33 #include "exportfs.h"
34 #include "xlog.h"
35
36 static void     export_all(int verbose);
37 static void     exportfs(char *arg, char *options, int verbose);
38 static void     unexportfs(char *arg, int verbose);
39 static void     exports_update(int verbose);
40 static void     dump(int verbose);
41 static void     error(nfs_export *exp, int err);
42 static void     usage(const char *progname);
43 static void     validate_export(nfs_export *exp);
44 static int      matchhostname(const char *hostname1, const char *hostname2);
45 static void     export_d_read(const char *dname);
46
47 int
48 main(int argc, char **argv)
49 {
50         char    *options = NULL;
51         char    *progname = NULL;
52         int     f_export = 1;
53         int     f_all = 0;
54         int     f_verbose = 0;
55         int     f_reexport = 0;
56         int     f_ignore = 0;
57         int     i, c;
58         int     new_cache = 0;
59         int     force_flush = 0;
60
61         if ((progname = strrchr(argv[0], '/')) != NULL)
62                 progname++;
63         else
64                 progname = argv[0];
65
66         xlog_open(progname);
67         xlog_stderr(1);
68         xlog_syslog(0);
69
70         export_errno = 0;
71
72         while ((c = getopt(argc, argv, "aio:ruvf")) != EOF) {
73                 switch(c) {
74                 case 'a':
75                         f_all = 1;
76                         break;
77                 case 'i':
78                         f_ignore = 1;
79                         break;
80                 case 'o':
81                         options = optarg;
82                         break;
83                 case 'r':
84                         f_reexport = 1;
85                         f_all = 1;
86                         break;
87                 case 'u':
88                         f_export = 0;
89                         break;
90                 case 'v':
91                         f_verbose = 1;
92                         break;
93                 case 'f':
94                         force_flush = 1;
95                         break;
96                 default:
97                         usage(progname);
98                         break;
99                 }
100         }
101
102         if (optind != argc && f_all) {
103                 xlog(L_ERROR, "extra arguments are not permitted with -a or -r");
104                 return 1;
105         }
106         if (f_ignore && (f_all || ! f_export)) {
107                 xlog(L_ERROR, "-i not meaningful with -a, -r or -u");
108                 return 1;
109         }
110         if (f_reexport && ! f_export) {
111                 xlog(L_ERROR, "-r and -u are incompatible");
112                 return 1;
113         }
114         new_cache = check_new_cache();
115         if (optind == argc && ! f_all) {
116                 if (force_flush) {
117                         if (new_cache)
118                                 cache_flush(1);
119                         else {
120                                 xlog(L_ERROR, "-f is available only "
121                                         "with new cache controls. "
122                                         "Mount /proc/fs/nfsd first");
123                                 return 1;
124                         }
125                         return 0;
126                 } else {
127                         xtab_export_read();
128                         dump(f_verbose);
129                         return 0;
130                 }
131         }
132         if (f_export && ! f_ignore) {
133                 export_read(_PATH_EXPORTS);
134                 export_d_read(_PATH_EXPORTS_D);
135         }
136         if (f_export) {
137                 if (f_all)
138                         export_all(f_verbose);
139                 else
140                         for (i = optind; i < argc ; i++)
141                                 exportfs(argv[i], options, f_verbose);
142         }
143         /* If we are unexporting everything, then
144          * don't care about what should be exported, as that
145          * may require DNS lookups..
146          */
147         if (! ( !f_export && f_all)) {
148                 /* note: xtab_*_read does not update entries if they already exist,
149                  * so this will not lose new options
150                  */
151                 if (!f_reexport)
152                         xtab_export_read();
153                 if (!f_export)
154                         for (i = optind ; i < argc ; i++)
155                                 unexportfs(argv[i], f_verbose);
156                 if (!new_cache)
157                         rmtab_read();
158         }
159         if (!new_cache) {
160                 xtab_mount_read();
161                 exports_update(f_verbose);
162         }
163         xtab_export_write();
164         if (new_cache)
165                 cache_flush(force_flush);
166         if (!new_cache)
167                 xtab_mount_write();
168
169         return export_errno;
170 }
171
172 static void
173 exports_update_one(nfs_export *exp, int verbose)
174 {
175                 /* check mountpoint option */
176         if (exp->m_mayexport &&
177             exp->m_export.e_mountpoint &&
178             !is_mountpoint(exp->m_export.e_mountpoint[0]?
179                            exp->m_export.e_mountpoint:
180                            exp->m_export.e_path)) {
181                 printf("%s not exported as %s not a mountpoint.\n",
182                        exp->m_export.e_path, exp->m_export.e_mountpoint);
183                 exp->m_mayexport = 0;
184         }
185         if (exp->m_mayexport && ((exp->m_exported<1) || exp->m_changed)) {
186                 if (verbose)
187                         printf("%sexporting %s:%s to kernel\n",
188                                exp->m_exported ?"re":"",
189                                exp->m_client->m_hostname,
190                                exp->m_export.e_path);
191                 if (!export_export(exp))
192                         error(exp, errno);
193         }
194         if (exp->m_exported && ! exp->m_mayexport) {
195                 if (verbose)
196                         printf("unexporting %s:%s from kernel\n",
197                                exp->m_client->m_hostname,
198                                exp->m_export.e_path);
199                 if (!export_unexport(exp))
200                         error(exp, errno);
201         }
202 }
203
204
205 /* we synchronise intention with reality.
206  * entries with m_mayexport get exported
207  * entries with m_exported but not m_mayexport get unexported
208  * looking at m_client->m_type == MCL_FQDN and m_client->m_type == MCL_GSS only
209  */
210 static void
211 exports_update(int verbose)
212 {
213         nfs_export      *exp;
214
215         for (exp = exportlist[MCL_FQDN].p_head; exp; exp=exp->m_next) {
216                 exports_update_one(exp, verbose);
217         }
218         for (exp = exportlist[MCL_GSS].p_head; exp; exp=exp->m_next) {
219                 exports_update_one(exp, verbose);
220         }
221 }
222                         
223 /*
224  * export_all finds all entries and
225  *    marks them xtabent and mayexport so that they get exported
226  */
227 static void
228 export_all(int verbose)
229 {
230         nfs_export      *exp;
231         int             i;
232
233         for (i = 0; i < MCL_MAXTYPES; i++) {
234                 for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
235                         if (verbose)
236                                 printf("exporting %s:%s\n",
237                                        exp->m_client->m_hostname, 
238                                        exp->m_export.e_path);
239                         exp->m_xtabent = 1;
240                         exp->m_mayexport = 1;
241                         exp->m_changed = 1;
242                         exp->m_warned = 0;
243                         validate_export(exp);
244                 }
245         }
246 }
247
248
249 static void
250 exportfs(char *arg, char *options, int verbose)
251 {
252         struct exportent *eep;
253         nfs_export      *exp = NULL;
254         struct addrinfo *ai = NULL;
255         char            *path;
256         char            *hname = arg;
257         int             htype;
258
259         if ((path = strchr(arg, ':')) != NULL)
260                 *path++ = '\0';
261
262         if (!path || *path != '/') {
263                 xlog(L_ERROR, "Invalid exporting option: %s", arg);
264                 return;
265         }
266
267         if ((htype = client_gettype(hname)) == MCL_FQDN) {
268                 ai = host_addrinfo(hname);
269                 if (ai != NULL) {
270                         exp = export_find(ai, path);
271                         hname = ai->ai_canonname;
272                 }
273         } else
274                 exp = export_lookup(hname, path, 0);
275
276         if (!exp) {
277                 if (!(eep = mkexportent(hname, path, options)) ||
278                     !(exp = export_create(eep, 0)))
279                         goto out;
280         } else if (!updateexportent(&exp->m_export, options))
281                 goto out;
282
283         if (verbose)
284                 printf("exporting %s:%s\n", exp->m_client->m_hostname, 
285                         exp->m_export.e_path);
286         exp->m_xtabent = 1;
287         exp->m_mayexport = 1;
288         exp->m_changed = 1;
289         exp->m_warned = 0;
290         validate_export(exp);
291
292 out:
293         freeaddrinfo(ai);
294 }
295
296 static void
297 unexportfs(char *arg, int verbose)
298 {
299         nfs_export      *exp;
300         struct addrinfo *ai = NULL;
301         char            *path;
302         char            *hname = arg;
303         int             htype;
304
305         if ((path = strchr(arg, ':')) != NULL)
306                 *path++ = '\0';
307
308         if (!path || *path != '/') {
309                 xlog(L_ERROR, "Invalid unexporting option: %s", arg);
310                 return;
311         }
312
313         if ((htype = client_gettype(hname)) == MCL_FQDN) {
314                 ai = host_addrinfo(hname);
315                 if (ai)
316                         hname = ai->ai_canonname;
317         }
318
319         for (exp = exportlist[htype].p_head; exp; exp = exp->m_next) {
320                 if (path && strcmp(path, exp->m_export.e_path))
321                         continue;
322                 if (htype != exp->m_client->m_type)
323                         continue;
324                 if (htype == MCL_FQDN
325                     && !matchhostname(exp->m_export.e_hostname,
326                                           hname))
327                         continue;
328                 if (htype != MCL_FQDN
329                     && strcasecmp(exp->m_export.e_hostname, hname))
330                         continue;
331                 if (verbose) {
332 #if 0
333                         if (exp->m_exported) {
334                                 printf("unexporting %s:%s from kernel\n",
335                                        exp->m_client->m_hostname,
336                                        exp->m_export.e_path);
337                         }
338                         else
339 #endif
340                                 printf("unexporting %s:%s\n",
341                                         exp->m_client->m_hostname, 
342                                         exp->m_export.e_path);
343                 }
344 #if 0
345                 if (exp->m_exported && !export_unexport(exp))
346                         error(exp, errno);
347 #endif
348                 exp->m_xtabent = 0;
349                 exp->m_mayexport = 0;
350         }
351
352         freeaddrinfo(ai);
353 }
354
355 static int can_test(void)
356 {
357         int fd;
358         int n;
359         char *setup = "nfsd 0.0.0.0 2147483647 -test-client-\n";
360         fd = open("/proc/net/rpc/auth.unix.ip/channel", O_WRONLY);
361         if ( fd < 0) return 0;
362         n = write(fd, setup, strlen(setup));
363         close(fd);
364         if (n < 0)
365                 return 0;
366         fd = open("/proc/net/rpc/nfsd.export/channel", O_WRONLY);
367         if ( fd < 0) return 0;
368         close(fd);
369         return 1;
370 }
371
372 static int test_export(char *path, int with_fsid)
373 {
374         char buf[1024];
375         int fd, n;
376
377         sprintf(buf, "-test-client- %s 3 %d -1 -1 0\n",
378                 path,
379                 with_fsid ? NFSEXP_FSID : 0);
380         fd = open("/proc/net/rpc/nfsd.export/channel", O_WRONLY);
381         if (fd < 0)
382                 return 0;
383         n = write(fd, buf, strlen(buf));
384         close(fd);
385         if (n < 0)
386                 return 0;
387         return 1;
388 }
389
390 static void
391 validate_export(nfs_export *exp)
392 {
393         /* Check that the given export point is potentially exportable.
394          * We just give warnings here, don't cause anything to fail.
395          * If a path doesn't exist, or is not a dir or file, give an warning
396          * otherwise trial-export to '-test-client-' and check for failure.
397          */
398         struct stat stb;
399         char *path = exp->m_export.e_path;
400         struct statfs64 stf;
401         int fs_has_fsid = 0;
402
403         if (stat(path, &stb) < 0) {
404                 xlog(L_ERROR, "Failed to stat %s: %m", path);
405                 return;
406         }
407         if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) {
408                 xlog(L_ERROR, "%s is neither a directory nor a file. "
409                         "Remote access will fail", path);
410                 return;
411         }
412         if (!can_test())
413                 return;
414
415         if (!statfs64(path, &stf) &&
416             (stf.f_fsid.__val[0] || stf.f_fsid.__val[1]))
417                 fs_has_fsid = 1;
418
419         if ((exp->m_export.e_flags & NFSEXP_FSID) || exp->m_export.e_uuid ||
420             fs_has_fsid) {
421                 if ( !test_export(path, 1)) {
422                         xlog(L_ERROR, "%s does not support NFS export", path);
423                         return;
424                 }
425         } else if ( ! test_export(path, 0)) {
426                 if (test_export(path, 1))
427                         xlog(L_ERROR, "%s requires fsid= for NFS export", path);
428                 else
429                         xlog(L_ERROR, "%s does not support NFS export", path);
430                 return;
431
432         }
433 }
434
435 static _Bool
436 is_hostname(const char *sp)
437 {
438         if (*sp == '\0' || *sp == '@')
439                 return false;
440
441         for (; *sp != '\0'; sp++) {
442                 if (*sp == '*' || *sp == '?' || *sp == '[' || *sp == '/')
443                         return false;
444                 if (*sp == '\\' && sp[1] != '\0')
445                         sp++;
446         }
447
448         return true;
449 }
450
451 /*
452  * Take care to perform an explicit reverse lookup on presentation
453  * addresses.  Otherwise we don't get a real canonical name or a
454  * complete list of addresses.
455  */
456 static struct addrinfo *
457 address_list(const char *hostname)
458 {
459         struct addrinfo *ai;
460         char *cname;
461
462         ai = host_pton(hostname);
463         if (ai != NULL) {
464                 /* @hostname was a presentation address */
465                 cname = host_canonname(ai->ai_addr);
466                 freeaddrinfo(ai);
467                 if (cname != NULL)
468                         goto out;
469         }
470         /* @hostname was a hostname or had no reverse mapping */
471         cname = strdup(hostname);
472         if (cname == NULL)
473                 return NULL;
474
475 out:
476         ai = host_addrinfo(cname);
477         free(cname);
478         return ai;
479 }
480
481 static int
482 matchhostname(const char *hostname1, const char *hostname2)
483 {
484         struct addrinfo *results1 = NULL, *results2 = NULL;
485         struct addrinfo *ai1, *ai2;
486         int result = 0;
487
488         if (strcasecmp(hostname1, hostname2) == 0)
489                 return 1;
490
491         /*
492          * Don't pass export wildcards or netgroup names to DNS
493          */
494         if (!is_hostname(hostname1) || !is_hostname(hostname2))
495                 return 0;
496
497         results1 = address_list(hostname1);
498         if (results1 == NULL)
499                 goto out;
500         results2 = address_list(hostname2);
501         if (results2 == NULL)
502                 goto out;
503
504         if (strcasecmp(results1->ai_canonname, results2->ai_canonname) == 0) {
505                 result = 1;
506                 goto out;
507         }
508
509         for (ai1 = results1; ai1 != NULL; ai1 = ai1->ai_next)
510                 for (ai2 = results2; ai2 != NULL; ai2 = ai2->ai_next)
511                         if (nfs_compare_sockaddr(ai1->ai_addr, ai2->ai_addr)) {
512                                 result = 1;
513                                 break;
514                         }
515
516 out:
517         freeaddrinfo(results1);
518         freeaddrinfo(results2);
519         return result;
520 }
521
522 /* Based on mnt_table_parse_dir() in
523    util-linux-ng/shlibs/mount/src/tab_parse.c */
524 static void
525 export_d_read(const char *dname)
526 {
527         int n = 0, i;
528         struct dirent **namelist = NULL;
529
530
531         n = scandir(dname, &namelist, NULL, versionsort);
532         if (n < 0) {
533                 if (errno == ENOENT)
534                         /* Silently return */
535                         return;
536                 xlog(L_NOTICE, "scandir %s: %s", dname, strerror(errno));
537         } else if (n == 0)
538                 return;
539
540         for (i = 0; i < n; i++) {
541                 struct dirent *d = namelist[i];
542                 size_t namesz;
543                 char fname[PATH_MAX + 1];
544                 int fname_len;
545
546
547                 if (d->d_type != DT_UNKNOWN 
548                     && d->d_type != DT_REG
549                     && d->d_type != DT_LNK)
550                         continue;
551                 if (*d->d_name == '.')
552                         continue;
553
554 #define _EXT_EXPORT_SIZ   (sizeof(_EXT_EXPORT) - 1)
555                 namesz = strlen(d->d_name);
556                 if (!namesz 
557                     || namesz < _EXT_EXPORT_SIZ + 1
558                     || strcmp(d->d_name + (namesz - _EXT_EXPORT_SIZ),
559                               _EXT_EXPORT))
560                         continue;
561
562                 fname_len = snprintf(fname, PATH_MAX +1, "%s/%s", dname, d->d_name);
563                 if (fname_len > PATH_MAX) {
564                         xlog(L_WARNING, "Too long file name: %s in %s", d->d_name, dname);
565                         continue;
566                 }
567
568                 export_read(fname);
569         }
570                 
571         for (i = 0; i < n; i++)
572                 free(namelist[i]);
573         free(namelist);
574
575         return;
576 }
577
578 static char
579 dumpopt(char c, char *fmt, ...)
580 {
581         va_list ap;
582
583         va_start(ap, fmt);
584         printf("%c", c);
585         vprintf(fmt, ap);
586         va_end(ap);
587         return ',';
588 }
589
590 static void
591 dump(int verbose)
592 {
593         nfs_export      *exp;
594         struct exportent *ep;
595         int             htype;
596         char            *hname, c;
597
598         for (htype = 0; htype < MCL_MAXTYPES; htype++) {
599                 for (exp = exportlist[htype].p_head; exp; exp = exp->m_next) {
600                         ep = &exp->m_export;
601                         if (!exp->m_xtabent)
602                             continue; /* neilb */
603                         if (htype == MCL_ANONYMOUS)
604                                 hname = "<world>";
605                         else
606                                 hname = ep->e_hostname;
607                         if (strlen(ep->e_path) > 14)
608                                 printf("%-14s\n\t\t%s", ep->e_path, hname);
609                         else
610                                 printf("%-14s\t%s", ep->e_path, hname);
611                         if (!verbose) {
612                                 printf("\n");
613                                 continue;
614                         }
615                         c = '(';
616                         if (ep->e_flags & NFSEXP_READONLY)
617                                 c = dumpopt(c, "ro");
618                         else
619                                 c = dumpopt(c, "rw");
620                         if (ep->e_flags & NFSEXP_ASYNC)
621                                 c = dumpopt(c, "async");
622                         if (ep->e_flags & NFSEXP_GATHERED_WRITES)
623                                 c = dumpopt(c, "wdelay");
624                         if (ep->e_flags & NFSEXP_NOHIDE)
625                                 c = dumpopt(c, "nohide");
626                         if (ep->e_flags & NFSEXP_CROSSMOUNT)
627                                 c = dumpopt(c, "crossmnt");
628                         if (ep->e_flags & NFSEXP_INSECURE_PORT)
629                                 c = dumpopt(c, "insecure");
630                         if (ep->e_flags & NFSEXP_ROOTSQUASH)
631                                 c = dumpopt(c, "root_squash");
632                         else
633                                 c = dumpopt(c, "no_root_squash");
634                         if (ep->e_flags & NFSEXP_ALLSQUASH)
635                                 c = dumpopt(c, "all_squash");
636                         if (ep->e_flags & NFSEXP_NOSUBTREECHECK)
637                                 c = dumpopt(c, "no_subtree_check");
638                         if (ep->e_flags & NFSEXP_NOAUTHNLM)
639                                 c = dumpopt(c, "insecure_locks");
640                         if (ep->e_flags & NFSEXP_NOACL)
641                                 c = dumpopt(c, "no_acl");
642                         if (ep->e_flags & NFSEXP_FSID)
643                                 c = dumpopt(c, "fsid=%d", ep->e_fsid);
644                         if (ep->e_uuid)
645                                 c = dumpopt(c, "fsid=%s", ep->e_uuid);
646                         if (ep->e_mountpoint)
647                                 c = dumpopt(c, "mountpoint%s%s", 
648                                             ep->e_mountpoint[0]?"=":"", 
649                                             ep->e_mountpoint);
650                         if (ep->e_anonuid != 65534)
651                                 c = dumpopt(c, "anonuid=%d", ep->e_anonuid);
652                         if (ep->e_anongid != 65534)
653                                 c = dumpopt(c, "anongid=%d", ep->e_anongid);
654                         switch(ep->e_fslocmethod) {
655                         case FSLOC_NONE:
656                                 break;
657                         case FSLOC_REFER:
658                                 c = dumpopt(c, "refer=%s", ep->e_fslocdata);
659                                 break;
660                         case FSLOC_REPLICA:
661                                 c = dumpopt(c, "replicas=%s", ep->e_fslocdata);
662                                 break;
663 #ifdef DEBUG
664                         case FSLOC_STUB:
665                                 c = dumpopt(c, "fsloc=stub");
666                                 break;
667 #endif
668                         }
669                         secinfo_show(stdout, ep);
670                         printf("%c\n", (c != '(')? ')' : ' ');
671                 }
672         }
673 }
674
675 static void
676 error(nfs_export *exp, int err)
677 {
678         xlog(L_ERROR, "%s:%s: %s", exp->m_client->m_hostname,
679                 exp->m_export.e_path, strerror(err));
680 }
681
682 static void
683 usage(const char *progname)
684 {
685         fprintf(stderr, "usage: %s [-aruv] [host:/path]\n", progname);
686         exit(1);
687 }