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