1 /*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1992, 1993, 1994
5 * The Regents of the University of California. All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Rick Macklem at The University of Guelph.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35 #include <sys/param.h>
36 #include <sys/linker.h>
37 #include <sys/module.h>
38 #include <sys/mount.h>
39 #include <sys/socket.h>
40 #include <sys/stat.h>
41 #include <sys/syslog.h>
42 #include <sys/uio.h>
43
44 #include <rpc/rpc.h>
45 #include <rpc/pmap_clnt.h>
46 #include <rpc/pmap_prot.h>
47 #include <rpcsvc/nfs_prot.h>
48 #include <rpcsvc/mount.h>
49
50 #include <fs/nfs/nfsproto.h>
51 #include <fs/nfs/nfsv4_errstr.h>
52
53 #include <arpa/inet.h>
54 #include <net/route.h>
55 #include <net/if.h>
56
57 #include <ctype.h>
58 #include <err.h>
59 #include <errno.h>
60 #include <fcntl.h>
61 #include <netdb.h>
62 #include <stdbool.h>
63 #include <stdio.h>
64 #include <stdlib.h>
65 #include <string.h>
66 #include <strings.h>
67 #include <sysexits.h>
68 #include <unistd.h>
69
70 #include "mntopts.h"
71 #include "mounttab.h"
72
73 /* Table for af,sotype -> netid conversions. */
74 static struct nc_protos {
75 const char *netid;
76 int af;
77 int sotype;
78 } nc_protos[] = {
79 {"udp", AF_INET, SOCK_DGRAM},
80 {"tcp", AF_INET, SOCK_STREAM},
81 {"udp6", AF_INET6, SOCK_DGRAM},
82 {"tcp6", AF_INET6, SOCK_STREAM},
83 {NULL, 0, 0}
84 };
85
86 struct nfhret {
87 u_long stat;
88 long vers;
89 long auth;
90 long fhsize;
91 u_char nfh[NFS3_FHSIZE];
92 };
93 #define BGRND 0x01
94 #define ISBGRND 0x02
95 #define OF_NOINET4 0x04
96 #define OF_NOINET6 0x08
97 #define BGRNDNOW 0x10
98 static int retrycnt = -1;
99 static int opflags = 0;
100 static int nfsproto = IPPROTO_TCP;
101 static int mnttcp_ok = 1;
102 static int noconn = 0;
103 /* The 'portspec' is the server nfs port; NULL means look up via rpcbind. */
104 static const char *portspec = NULL;
105 static struct sockaddr *addr;
106 static int addrlen = 0;
107 static u_char *fh = NULL;
108 static int fhsize = 0;
109 static int secflavor = -1;
110 static int got_principal = 0;
111
112 static enum mountmode {
113 ANY,
114 V2,
115 V3,
116 V4
117 } mountmode = ANY;
118
119 /* Return codes for nfs_tryproto. */
120 enum tryret {
121 TRYRET_SUCCESS,
122 TRYRET_TIMEOUT, /* No response received. */
123 TRYRET_REMOTEERR, /* Error received from remote server. */
124 TRYRET_LOCALERR /* Local failure. */
125 };
126
127 static int sec_name_to_num(const char *sec);
128 static const char *sec_num_to_name(int num);
129 static int getnfsargs(char **, char **, struct iovec **iov, int *iovlen);
130 /* void set_rpc_maxgrouplist(int); */
131 static struct netconfig *getnetconf_cached(const char *netid);
132 static const char *netidbytype(int af, int sotype);
133 static void usage(void) __dead2;
134 static int xdr_dir(XDR *, char *);
135 static int xdr_fh(XDR *, struct nfhret *);
136 static enum tryret nfs_tryproto(struct addrinfo *ai, char *hostp, char *spec,
137 char **errstr, struct iovec **iov, int *iovlen);
138 static enum tryret returncode(enum clnt_stat stat, struct rpc_err *rpcerr);
139
140 int
main(int argc,char * argv[])141 main(int argc, char *argv[])
142 {
143 int c;
144 struct iovec *iov;
145 int num, iovlen;
146 char *host, *mntname, *p, *spec, *tmp;
147 char mntpath[MAXPATHLEN], errmsg[255];
148 char hostname[MAXHOSTNAMELEN + 1], gssn[MAXHOSTNAMELEN + 50];
149 const char *gssname, *nmount_errstr;
150 bool softintr;
151
152 softintr = false;
153 iov = NULL;
154 iovlen = 0;
155 memset(errmsg, 0, sizeof(errmsg));
156 gssname = NULL;
157
158 while ((c = getopt(argc, argv,
159 "23a:bcdD:g:I:iLlNo:PR:r:sTt:w:x:U")) != -1)
160 switch (c) {
161 case '2':
162 mountmode = V2;
163 break;
164 case '3':
165 mountmode = V3;
166 break;
167 case 'a':
168 printf("-a deprecated, use -o readahead=<value>\n");
169 build_iovec(&iov, &iovlen, "readahead", optarg, (size_t)-1);
170 break;
171 case 'b':
172 opflags |= BGRND;
173 break;
174 case 'c':
175 printf("-c deprecated, use -o noconn\n");
176 build_iovec(&iov, &iovlen, "noconn", NULL, 0);
177 noconn = 1;
178 break;
179 case 'D':
180 printf("-D deprecated, use -o deadthresh=<value>\n");
181 build_iovec(&iov, &iovlen, "deadthresh", optarg, (size_t)-1);
182 break;
183 case 'd':
184 printf("-d deprecated, use -o dumbtimer");
185 build_iovec(&iov, &iovlen, "dumbtimer", NULL, 0);
186 break;
187 case 'g':
188 printf("-g deprecated, use -o maxgroups");
189 num = strtol(optarg, &p, 10);
190 if (*p || num <= 0)
191 errx(1, "illegal -g value -- %s", optarg);
192 //set_rpc_maxgrouplist(num);
193 build_iovec(&iov, &iovlen, "maxgroups", optarg, (size_t)-1);
194 break;
195 case 'I':
196 printf("-I deprecated, use -o readdirsize=<value>\n");
197 build_iovec(&iov, &iovlen, "readdirsize", optarg, (size_t)-1);
198 break;
199 case 'i':
200 printf("-i deprecated, use -o intr\n");
201 build_iovec(&iov, &iovlen, "intr", NULL, 0);
202 softintr = true;
203 break;
204 case 'L':
205 printf("-L deprecated, use -o nolockd\n");
206 build_iovec(&iov, &iovlen, "nolockd", NULL, 0);
207 break;
208 case 'l':
209 printf("-l deprecated, -o rdirplus\n");
210 build_iovec(&iov, &iovlen, "rdirplus", NULL, 0);
211 break;
212 case 'N':
213 printf("-N deprecated, do not specify -o resvport\n");
214 break;
215 case 'o': {
216 int pass_flag_to_nmount;
217 char *opt = optarg;
218 while (opt) {
219 char *pval = NULL;
220 char *pnextopt = NULL;
221 const char *val = "";
222 pass_flag_to_nmount = 1;
223 pnextopt = strchr(opt, ',');
224 if (pnextopt != NULL) {
225 *pnextopt = '\0';
226 pnextopt++;
227 }
228 pval = strchr(opt, '=');
229 if (pval != NULL) {
230 *pval = '\0';
231 val = pval + 1;
232 }
233 if (strcmp(opt, "bg") == 0) {
234 opflags |= BGRND;
235 pass_flag_to_nmount=0;
236 } else if (strcmp(opt, "bgnow") == 0) {
237 opflags |= BGRNDNOW;
238 pass_flag_to_nmount=0;
239 } else if (strcmp(opt, "fg") == 0) {
240 /* same as not specifying -o bg */
241 pass_flag_to_nmount=0;
242 } else if (strcmp(opt, "gssname") == 0) {
243 pass_flag_to_nmount = 0;
244 gssname = val;
245 } else if (strcmp(opt, "mntudp") == 0) {
246 mnttcp_ok = 0;
247 nfsproto = IPPROTO_UDP;
248 } else if (strcmp(opt, "udp") == 0) {
249 nfsproto = IPPROTO_UDP;
250 } else if (strcmp(opt, "tcp") == 0) {
251 nfsproto = IPPROTO_TCP;
252 } else if (strcmp(opt, "noinet4") == 0) {
253 pass_flag_to_nmount=0;
254 opflags |= OF_NOINET4;
255 } else if (strcmp(opt, "noinet6") == 0) {
256 pass_flag_to_nmount=0;
257 opflags |= OF_NOINET6;
258 } else if (strcmp(opt, "noconn") == 0) {
259 noconn = 1;
260 } else if (strcmp(opt, "nfsv2") == 0) {
261 pass_flag_to_nmount=0;
262 mountmode = V2;
263 } else if (strcmp(opt, "nfsv3") == 0) {
264 mountmode = V3;
265 } else if (strcmp(opt, "nfsv4") == 0) {
266 pass_flag_to_nmount=0;
267 mountmode = V4;
268 nfsproto = IPPROTO_TCP;
269 if (portspec == NULL)
270 portspec = "2049";
271 } else if (strcmp(opt, "port") == 0) {
272 pass_flag_to_nmount=0;
273 asprintf(&tmp, "%d", atoi(val));
274 if (tmp == NULL)
275 err(1, "asprintf");
276 portspec = tmp;
277 } else if (strcmp(opt, "principal") == 0) {
278 got_principal = 1;
279 } else if (strcmp(opt, "proto") == 0) {
280 pass_flag_to_nmount=0;
281 if (strcmp(val, "tcp") == 0) {
282 nfsproto = IPPROTO_TCP;
283 opflags |= OF_NOINET6;
284 build_iovec(&iov, &iovlen,
285 "tcp", NULL, 0);
286 } else if (strcmp(val, "udp") == 0) {
287 mnttcp_ok = 0;
288 nfsproto = IPPROTO_UDP;
289 opflags |= OF_NOINET6;
290 build_iovec(&iov, &iovlen,
291 "udp", NULL, 0);
292 } else if (strcmp(val, "tcp6") == 0) {
293 nfsproto = IPPROTO_TCP;
294 opflags |= OF_NOINET4;
295 build_iovec(&iov, &iovlen,
296 "tcp", NULL, 0);
297 } else if (strcmp(val, "udp6") == 0) {
298 mnttcp_ok = 0;
299 nfsproto = IPPROTO_UDP;
300 opflags |= OF_NOINET4;
301 build_iovec(&iov, &iovlen,
302 "udp", NULL, 0);
303 } else {
304 errx(1,
305 "illegal proto value -- %s",
306 val);
307 }
308 } else if (strcmp(opt, "sec") == 0) {
309 /*
310 * Don't add this option to
311 * the iovec yet - we will
312 * negotiate which sec flavor
313 * to use with the remote
314 * mountd.
315 */
316 pass_flag_to_nmount=0;
317 secflavor = sec_name_to_num(val);
318 if (secflavor < 0) {
319 errx(1,
320 "illegal sec value -- %s",
321 val);
322 }
323 } else if (strcmp(opt, "retrycnt") == 0) {
324 pass_flag_to_nmount=0;
325 num = strtol(val, &p, 10);
326 if (*p || num < 0)
327 errx(1, "illegal retrycnt value -- %s", val);
328 retrycnt = num;
329 } else if (strcmp(opt, "maxgroups") == 0) {
330 num = strtol(val, &p, 10);
331 if (*p || num <= 0)
332 errx(1, "illegal maxgroups value -- %s", val);
333 //set_rpc_maxgrouplist(num);
334 } else if (strcmp(opt, "vers") == 0) {
335 num = strtol(val, &p, 10);
336 if (*p || num <= 0)
337 errx(1, "illegal vers value -- "
338 "%s", val);
339 switch (num) {
340 case 2:
341 mountmode = V2;
342 break;
343 case 3:
344 mountmode = V3;
345 build_iovec(&iov, &iovlen,
346 "nfsv3", NULL, 0);
347 break;
348 case 4:
349 mountmode = V4;
350 nfsproto = IPPROTO_TCP;
351 if (portspec == NULL)
352 portspec = "2049";
353 break;
354 default:
355 errx(1, "illegal nfs version "
356 "value -- %s", val);
357 }
358 pass_flag_to_nmount=0;
359 } else if (strcmp(opt, "soft") == 0) {
360 softintr = true;
361 } else if (strcmp(opt, "intr") == 0) {
362 softintr = true;
363 }
364 if (pass_flag_to_nmount) {
365 build_iovec(&iov, &iovlen, opt,
366 __DECONST(void *, val),
367 strlen(val) + 1);
368 }
369 opt = pnextopt;
370 }
371 }
372 break;
373 case 'P':
374 /* obsolete for -o noresvport now default */
375 printf("-P deprecated, use -o noresvport\n");
376 build_iovec(&iov, &iovlen, "noresvport", NULL, 0);
377 break;
378 case 'R':
379 printf("-R deprecated, use -o retrycnt=<retrycnt>\n");
380 num = strtol(optarg, &p, 10);
381 if (*p || num < 0)
382 errx(1, "illegal -R value -- %s", optarg);
383 retrycnt = num;
384 break;
385 case 'r':
386 printf("-r deprecated, use -o rsize=<rsize>\n");
387 build_iovec(&iov, &iovlen, "rsize", optarg, (size_t)-1);
388 break;
389 case 's':
390 printf("-s deprecated, use -o soft\n");
391 build_iovec(&iov, &iovlen, "soft", NULL, 0);
392 softintr = true;
393 break;
394 case 'T':
395 nfsproto = IPPROTO_TCP;
396 printf("-T deprecated, use -o tcp\n");
397 break;
398 case 't':
399 printf("-t deprecated, use -o timeout=<value>\n");
400 build_iovec(&iov, &iovlen, "timeout", optarg, (size_t)-1);
401 break;
402 case 'w':
403 printf("-w deprecated, use -o wsize=<value>\n");
404 build_iovec(&iov, &iovlen, "wsize", optarg, (size_t)-1);
405 break;
406 case 'x':
407 printf("-x deprecated, use -o retrans=<value>\n");
408 build_iovec(&iov, &iovlen, "retrans", optarg, (size_t)-1);
409 break;
410 case 'U':
411 printf("-U deprecated, use -o mntudp\n");
412 mnttcp_ok = 0;
413 nfsproto = IPPROTO_UDP;
414 build_iovec(&iov, &iovlen, "mntudp", NULL, 0);
415 break;
416 default:
417 usage();
418 break;
419 }
420 argc -= optind;
421 argv += optind;
422
423 if ((opflags & (BGRND | BGRNDNOW)) == (BGRND | BGRNDNOW))
424 errx(1, "Options bg and bgnow are mutually exclusive");
425
426 if (argc != 2) {
427 usage();
428 /* NOTREACHED */
429 }
430
431 /* Warn that NFSv4 mounts only work correctly as hard mounts. */
432 if (mountmode == V4 && softintr)
433 warnx("Warning, options soft and/or intr cannot be safely used"
434 " for NFSv4. See the BUGS section of mount_nfs(8)");
435
436 spec = *argv++;
437 mntname = *argv;
438
439 if (retrycnt == -1)
440 /* The default is to keep retrying forever. */
441 retrycnt = 0;
442
443 if (modfind("nfscl") < 0) {
444 /* Not present in kernel, try loading it */
445 if (kldload("nfscl") < 0 ||
446 modfind("nfscl") < 0)
447 errx(1, "nfscl is not available");
448 }
449
450 /*
451 * Add the fqdn to the gssname, as required.
452 */
453 if (gssname != NULL) {
454 if (strchr(gssname, '@') == NULL &&
455 gethostname(hostname, MAXHOSTNAMELEN) == 0) {
456 snprintf(gssn, sizeof (gssn), "%s@%s", gssname,
457 hostname);
458 gssname = gssn;
459 }
460 build_iovec(&iov, &iovlen, "gssname",
461 __DECONST(void *, gssname), strlen(gssname) + 1);
462 }
463
464 if (!getnfsargs(&spec, &host, &iov, &iovlen))
465 exit(1);
466
467 /* resolve the mountpoint with realpath(3) */
468 if (checkpath(mntname, mntpath) != 0)
469 err(1, "%s", mntpath);
470
471 build_iovec_argf(&iov, &iovlen, "fstype", "nfs");
472 build_iovec(&iov, &iovlen, "fspath", mntpath, (size_t)-1);
473 build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
474
475 if (nmount(iov, iovlen, 0)) {
476 nmount_errstr = nfsv4_geterrstr(errno);
477 if (mountmode == V4 && nmount_errstr != NULL)
478 errx(1, "nmount: %s, %s", mntpath, nmount_errstr);
479 else
480 err(1, "nmount: %s%s%s", mntpath, errmsg[0] ? ", " : "",
481 errmsg);
482 } else if (mountmode != V4 && !add_mtab(host, spec)) {
483 /* Add mounted file system to PATH_MOUNTTAB */
484 warnx("can't update %s for %s:%s", PATH_MOUNTTAB, host, spec);
485 }
486
487 exit(0);
488 }
489
490 static int
sec_name_to_num(const char * sec)491 sec_name_to_num(const char *sec)
492 {
493 if (!strcmp(sec, "krb5"))
494 return (RPCSEC_GSS_KRB5);
495 if (!strcmp(sec, "krb5i"))
496 return (RPCSEC_GSS_KRB5I);
497 if (!strcmp(sec, "krb5p"))
498 return (RPCSEC_GSS_KRB5P);
499 if (!strcmp(sec, "sys"))
500 return (AUTH_SYS);
501 return (-1);
502 }
503
504 static const char *
sec_num_to_name(int flavor)505 sec_num_to_name(int flavor)
506 {
507 switch (flavor) {
508 case RPCSEC_GSS_KRB5:
509 return ("krb5");
510 case RPCSEC_GSS_KRB5I:
511 return ("krb5i");
512 case RPCSEC_GSS_KRB5P:
513 return ("krb5p");
514 case AUTH_SYS:
515 return ("sys");
516 }
517 return (NULL);
518 }
519
520 /*
521 * Wait for RTM_IFINFO message with interface that is IFF_UP and with
522 * link on, or until timeout expires. Returns seconds left.
523 */
524 static time_t
rtm_ifinfo_sleep(time_t sec)525 rtm_ifinfo_sleep(time_t sec)
526 {
527 char buf[2048] __aligned(__alignof(struct if_msghdr));
528 fd_set rfds;
529 struct timeval tv, start;
530 ssize_t nread;
531 int n, s;
532
533 s = socket(PF_ROUTE, SOCK_RAW, 0);
534 if (s < 0)
535 err(EX_OSERR, "socket");
536 (void)gettimeofday(&start, NULL);
537
538 for (tv.tv_sec = sec, tv.tv_usec = 0;
539 tv.tv_sec > 0;
540 (void)gettimeofday(&tv, NULL),
541 tv.tv_sec = sec - (tv.tv_sec - start.tv_sec)) {
542 FD_ZERO(&rfds);
543 FD_SET(s, &rfds);
544 n = select(s + 1, &rfds, NULL, NULL, &tv);
545 if (n == 0)
546 continue;
547 if (n == -1) {
548 if (errno == EINTR)
549 continue;
550 else
551 err(EX_SOFTWARE, "select");
552 }
553 nread = read(s, buf, 2048);
554 if (nread < 0)
555 err(EX_OSERR, "read");
556 if ((size_t)nread >= sizeof(struct if_msghdr)) {
557 struct if_msghdr *ifm;
558
559 ifm = (struct if_msghdr *)buf;
560 if (ifm->ifm_version == RTM_VERSION &&
561 ifm->ifm_type == RTM_IFINFO &&
562 (ifm->ifm_flags & IFF_UP) &&
563 ifm->ifm_data.ifi_link_state != LINK_STATE_DOWN)
564 break;
565 }
566 }
567
568 close(s);
569
570 return (tv.tv_sec);
571 }
572
573 static int
getnfsargs(char ** specp,char ** hostpp,struct iovec ** iov,int * iovlen)574 getnfsargs(char **specp, char **hostpp, struct iovec **iov, int *iovlen)
575 {
576 struct addrinfo hints, *ai_nfs, *ai;
577 enum tryret ret;
578 int ecode, speclen, remoteerr, offset, have_bracket = 0;
579 char *hostp, *delimp, *errstr, *spec;
580 size_t len;
581 static char nam[MNAMELEN + 1], pname[MAXHOSTNAMELEN + 5];
582
583 spec = *specp;
584 if (*spec == '[' && (delimp = strchr(spec + 1, ']')) != NULL &&
585 *(delimp + 1) == ':') {
586 hostp = spec + 1;
587 spec = delimp + 2;
588 have_bracket = 1;
589 } else if ((delimp = strrchr(spec, ':')) != NULL) {
590 hostp = spec;
591 spec = delimp + 1;
592 } else if ((delimp = strrchr(spec, '@')) != NULL) {
593 warnx("path@server syntax is deprecated, use server:path");
594 hostp = delimp + 1;
595 } else {
596 warnx("no <host>:<dirpath> nfs-name");
597 return (0);
598 }
599 *delimp = '\0';
600
601 /*
602 * If there has been a trailing slash at mounttime it seems
603 * that some mountd implementations fail to remove the mount
604 * entries from their mountlist while unmounting.
605 */
606 for (speclen = strlen(spec);
607 speclen > 1 && spec[speclen - 1] == '/';
608 speclen--)
609 spec[speclen - 1] = '\0';
610 if (strlen(hostp) + strlen(spec) + 1 > MNAMELEN) {
611 warnx("%s:%s: %s", hostp, spec, strerror(ENAMETOOLONG));
612 return (0);
613 }
614 /* Make both '@' and ':' notations equal */
615 if (*hostp != '\0') {
616 len = strlen(hostp);
617 offset = 0;
618 if (have_bracket)
619 nam[offset++] = '[';
620 memmove(nam + offset, hostp, len);
621 if (have_bracket)
622 nam[len + offset++] = ']';
623 nam[len + offset++] = ':';
624 memmove(nam + len + offset, spec, speclen);
625 nam[len + speclen + offset] = '\0';
626 }
627
628 /*
629 * Handle an internet host address.
630 */
631 memset(&hints, 0, sizeof hints);
632 hints.ai_flags = AI_NUMERICHOST;
633 if (nfsproto == IPPROTO_TCP)
634 hints.ai_socktype = SOCK_STREAM;
635 else if (nfsproto == IPPROTO_UDP)
636 hints.ai_socktype = SOCK_DGRAM;
637
638 if (getaddrinfo(hostp, portspec, &hints, &ai_nfs) != 0) {
639 hints.ai_flags = AI_CANONNAME;
640 if ((ecode = getaddrinfo(hostp, portspec, &hints, &ai_nfs))
641 != 0) {
642 if (portspec == NULL)
643 errx(1, "%s: %s", hostp, gai_strerror(ecode));
644 else
645 errx(1, "%s:%s: %s", hostp, portspec,
646 gai_strerror(ecode));
647 return (0);
648 }
649
650 /*
651 * For a Kerberized nfs mount where the "principal"
652 * argument has not been set, add it here.
653 */
654 if (got_principal == 0 && secflavor != AUTH_SYS &&
655 ai_nfs->ai_canonname != NULL) {
656 snprintf(pname, sizeof (pname), "nfs@%s",
657 ai_nfs->ai_canonname);
658 build_iovec(iov, iovlen, "principal", pname,
659 strlen(pname) + 1);
660 }
661 }
662
663 if ((opflags & (BGRNDNOW | ISBGRND)) == BGRNDNOW) {
664 warnx("Mount %s:%s, backgrounding",
665 hostp, spec);
666 opflags |= ISBGRND;
667 if (daemon(0, 0) != 0)
668 err(1, "daemon");
669 }
670
671 ret = TRYRET_LOCALERR;
672 for (;;) {
673 /*
674 * Try each entry returned by getaddrinfo(). Note the
675 * occurrence of remote errors by setting `remoteerr'.
676 */
677 remoteerr = 0;
678 for (ai = ai_nfs; ai != NULL; ai = ai->ai_next) {
679 if ((ai->ai_family == AF_INET6) &&
680 (opflags & OF_NOINET6))
681 continue;
682 if ((ai->ai_family == AF_INET) &&
683 (opflags & OF_NOINET4))
684 continue;
685 ret = nfs_tryproto(ai, hostp, spec, &errstr, iov,
686 iovlen);
687 if (ret == TRYRET_SUCCESS)
688 break;
689 if (ret != TRYRET_LOCALERR)
690 remoteerr = 1;
691 if ((opflags & ISBGRND) == 0)
692 fprintf(stderr, "%s\n", errstr);
693 }
694 if (ret == TRYRET_SUCCESS)
695 break;
696
697 /* Exit if all errors were local. */
698 if (!remoteerr)
699 exit(1);
700
701 /*
702 * If retrycnt == 0, we are to keep retrying forever.
703 * Otherwise decrement it, and exit if it hits zero.
704 */
705 if (retrycnt != 0 && --retrycnt == 0)
706 exit(1);
707
708 if ((opflags & (BGRND | ISBGRND)) == BGRND) {
709 warnx("Cannot immediately mount %s:%s, backgrounding",
710 hostp, spec);
711 opflags |= ISBGRND;
712 if (daemon(0, 0) != 0)
713 err(1, "daemon");
714 }
715 /*
716 * If rtm_ifinfo_sleep() returns non-zero, don't count
717 * that as a retry attempt.
718 */
719 if (rtm_ifinfo_sleep(60) && retrycnt != 0)
720 retrycnt++;
721 }
722 freeaddrinfo(ai_nfs);
723
724 build_iovec(iov, iovlen, "hostname", nam, (size_t)-1);
725
726 *specp = spec;
727 *hostpp = hostp;
728 return (1);
729 }
730
731 /*
732 * Try to set up the NFS arguments according to the address
733 * family, protocol (and possibly port) specified in `ai'.
734 *
735 * Returns TRYRET_SUCCESS if successful, or:
736 * TRYRET_TIMEOUT The server did not respond.
737 * TRYRET_REMOTEERR The server reported an error.
738 * TRYRET_LOCALERR Local failure.
739 *
740 * In all error cases, *errstr will be set to a statically-allocated string
741 * describing the error.
742 */
743 static enum tryret
nfs_tryproto(struct addrinfo * ai,char * hostp,char * spec,char ** errstr,struct iovec ** iov,int * iovlen)744 nfs_tryproto(struct addrinfo *ai, char *hostp, char *spec, char **errstr,
745 struct iovec **iov, int *iovlen)
746 {
747 static char errbuf[256];
748 struct sockaddr_storage nfs_ss;
749 struct netbuf nfs_nb;
750 struct nfhret nfhret;
751 struct timeval try;
752 struct rpc_err rpcerr;
753 CLIENT *clp;
754 struct netconfig *nconf, *nconf_mnt;
755 const char *netid, *netid_mnt, *secname;
756 int doconnect, nfsvers, mntvers, sotype;
757 enum clnt_stat clntstat;
758 enum mountmode trymntmode;
759
760 sotype = 0;
761 trymntmode = mountmode;
762 errbuf[0] = '\0';
763 *errstr = errbuf;
764
765 if (nfsproto == IPPROTO_TCP)
766 sotype = SOCK_STREAM;
767 else if (nfsproto == IPPROTO_UDP)
768 sotype = SOCK_DGRAM;
769
770 if ((netid = netidbytype(ai->ai_family, sotype)) == NULL) {
771 snprintf(errbuf, sizeof errbuf,
772 "af %d sotype %d not supported", ai->ai_family, sotype);
773 return (TRYRET_LOCALERR);
774 }
775 if ((nconf = getnetconf_cached(netid)) == NULL) {
776 snprintf(errbuf, sizeof errbuf, "%s: %s", netid, nc_sperror());
777 return (TRYRET_LOCALERR);
778 }
779 /* The RPCPROG_MNT netid may be different. */
780 if (mnttcp_ok) {
781 netid_mnt = netid;
782 nconf_mnt = nconf;
783 } else {
784 if ((netid_mnt = netidbytype(ai->ai_family, SOCK_DGRAM))
785 == NULL) {
786 snprintf(errbuf, sizeof errbuf,
787 "af %d sotype SOCK_DGRAM not supported",
788 ai->ai_family);
789 return (TRYRET_LOCALERR);
790 }
791 if ((nconf_mnt = getnetconf_cached(netid_mnt)) == NULL) {
792 snprintf(errbuf, sizeof errbuf, "%s: %s", netid_mnt,
793 nc_sperror());
794 return (TRYRET_LOCALERR);
795 }
796 }
797
798 tryagain:
799 if (trymntmode == V4) {
800 nfsvers = 4;
801 mntvers = 3; /* Workaround for GCC. */
802 } else if (trymntmode == V2) {
803 nfsvers = 2;
804 mntvers = 1;
805 } else {
806 nfsvers = 3;
807 mntvers = 3;
808 }
809
810 if (portspec != NULL) {
811 /* `ai' contains the complete nfsd sockaddr. */
812 nfs_nb.buf = ai->ai_addr;
813 nfs_nb.len = nfs_nb.maxlen = ai->ai_addrlen;
814 } else {
815 /* Ask the remote rpcbind. */
816 nfs_nb.buf = &nfs_ss;
817 nfs_nb.len = nfs_nb.maxlen = sizeof nfs_ss;
818
819 if (!rpcb_getaddr(NFS_PROGRAM, nfsvers, nconf, &nfs_nb,
820 hostp)) {
821 if (rpc_createerr.cf_stat == RPC_PROGVERSMISMATCH &&
822 trymntmode == ANY) {
823 trymntmode = V2;
824 goto tryagain;
825 }
826 snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s",
827 netid, hostp, spec,
828 clnt_spcreateerror("RPCPROG_NFS"));
829 return (returncode(rpc_createerr.cf_stat,
830 &rpc_createerr.cf_error));
831 }
832 }
833
834 /* Check that the server (nfsd) responds on the port we have chosen. */
835 clp = clnt_tli_create(RPC_ANYFD, nconf, &nfs_nb, NFS_PROGRAM, nfsvers,
836 0, 0);
837 if (clp == NULL) {
838 snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid,
839 hostp, spec, clnt_spcreateerror("nfsd: RPCPROG_NFS"));
840 return (returncode(rpc_createerr.cf_stat,
841 &rpc_createerr.cf_error));
842 }
843 if (sotype == SOCK_DGRAM && noconn == 0) {
844 /*
845 * Use connect(), to match what the kernel does. This
846 * catches cases where the server responds from the
847 * wrong source address.
848 */
849 doconnect = 1;
850 if (!clnt_control(clp, CLSET_CONNECT, (char *)&doconnect)) {
851 clnt_destroy(clp);
852 snprintf(errbuf, sizeof errbuf,
853 "[%s] %s:%s: CLSET_CONNECT failed", netid, hostp,
854 spec);
855 return (TRYRET_LOCALERR);
856 }
857 }
858
859 try.tv_sec = 10;
860 try.tv_usec = 0;
861 clntstat = clnt_call(clp, NFSPROC_NULL, (xdrproc_t)xdr_void, NULL,
862 (xdrproc_t)xdr_void, NULL, try);
863 if (clntstat != RPC_SUCCESS) {
864 if (clntstat == RPC_PROGVERSMISMATCH && trymntmode == ANY) {
865 clnt_destroy(clp);
866 trymntmode = V2;
867 goto tryagain;
868 }
869 clnt_geterr(clp, &rpcerr);
870 snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid,
871 hostp, spec, clnt_sperror(clp, "NFSPROC_NULL"));
872 clnt_destroy(clp);
873 return (returncode(clntstat, &rpcerr));
874 }
875 clnt_destroy(clp);
876
877 /*
878 * For NFSv4, there is no mount protocol.
879 */
880 if (trymntmode == V4) {
881 /*
882 * Store the server address in nfsargsp, making
883 * sure to copy any locally allocated structures.
884 */
885 addrlen = nfs_nb.len;
886 addr = malloc(addrlen);
887 if (addr == NULL)
888 err(1, "malloc");
889 bcopy(nfs_nb.buf, addr, addrlen);
890
891 build_iovec(iov, iovlen, "addr", addr, addrlen);
892 secname = sec_num_to_name(secflavor);
893 if (secname != NULL) {
894 build_iovec(iov, iovlen, "sec",
895 __DECONST(void *, secname), (size_t)-1);
896 }
897 build_iovec(iov, iovlen, "nfsv4", NULL, 0);
898 build_iovec(iov, iovlen, "dirpath", spec, (size_t)-1);
899
900 return (TRYRET_SUCCESS);
901 }
902
903 /* Send the MOUNTPROC_MNT RPC to get the root filehandle. */
904 try.tv_sec = 10;
905 try.tv_usec = 0;
906 clp = clnt_tp_create(hostp, MOUNTPROG, mntvers, nconf_mnt);
907 if (clp == NULL) {
908 snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt,
909 hostp, spec, clnt_spcreateerror("RPCMNT: clnt_create"));
910 return (returncode(rpc_createerr.cf_stat,
911 &rpc_createerr.cf_error));
912 }
913 clp->cl_auth = authsys_create_default();
914 nfhret.auth = secflavor;
915 nfhret.vers = mntvers;
916 clntstat = clnt_call(clp, MOUNTPROC_MNT, (xdrproc_t)xdr_dir, spec,
917 (xdrproc_t)xdr_fh, &nfhret,
918 try);
919 auth_destroy(clp->cl_auth);
920 if (clntstat != RPC_SUCCESS) {
921 if (clntstat == RPC_PROGVERSMISMATCH && trymntmode == ANY) {
922 clnt_destroy(clp);
923 trymntmode = V2;
924 goto tryagain;
925 }
926 clnt_geterr(clp, &rpcerr);
927 snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt,
928 hostp, spec, clnt_sperror(clp, "RPCPROG_MNT"));
929 clnt_destroy(clp);
930 return (returncode(clntstat, &rpcerr));
931 }
932 clnt_destroy(clp);
933
934 if (nfhret.stat != 0) {
935 snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt,
936 hostp, spec, strerror(nfhret.stat));
937 return (TRYRET_REMOTEERR);
938 }
939
940 /*
941 * Store the filehandle and server address in nfsargsp, making
942 * sure to copy any locally allocated structures.
943 */
944 addrlen = nfs_nb.len;
945 addr = malloc(addrlen);
946 fhsize = nfhret.fhsize;
947 fh = malloc(fhsize);
948 if (addr == NULL || fh == NULL)
949 err(1, "malloc");
950 bcopy(nfs_nb.buf, addr, addrlen);
951 bcopy(nfhret.nfh, fh, fhsize);
952
953 build_iovec(iov, iovlen, "addr", addr, addrlen);
954 build_iovec(iov, iovlen, "fh", fh, fhsize);
955 secname = sec_num_to_name(nfhret.auth);
956 if (secname) {
957 build_iovec(iov, iovlen, "sec",
958 __DECONST(void *, secname), (size_t)-1);
959 }
960 if (nfsvers == 3)
961 build_iovec(iov, iovlen, "nfsv3", NULL, 0);
962
963 return (TRYRET_SUCCESS);
964 }
965
966 /*
967 * Catagorise a RPC return status and error into an `enum tryret'
968 * return code.
969 */
970 static enum tryret
returncode(enum clnt_stat clntstat,struct rpc_err * rpcerr)971 returncode(enum clnt_stat clntstat, struct rpc_err *rpcerr)
972 {
973
974 switch (clntstat) {
975 case RPC_TIMEDOUT:
976 return (TRYRET_TIMEOUT);
977 case RPC_PMAPFAILURE:
978 case RPC_PROGNOTREGISTERED:
979 case RPC_PROGVERSMISMATCH:
980 /* XXX, these can be local or remote. */
981 case RPC_CANTSEND:
982 case RPC_CANTRECV:
983 return (TRYRET_REMOTEERR);
984 case RPC_SYSTEMERROR:
985 switch (rpcerr->re_errno) {
986 case ETIMEDOUT:
987 return (TRYRET_TIMEOUT);
988 case ENOMEM:
989 break;
990 default:
991 return (TRYRET_REMOTEERR);
992 }
993 /* FALLTHROUGH */
994 default:
995 break;
996 }
997 return (TRYRET_LOCALERR);
998 }
999
1000 /*
1001 * Look up a netid based on an address family and socket type.
1002 * `af' is the address family, and `sotype' is SOCK_DGRAM or SOCK_STREAM.
1003 *
1004 * XXX there should be a library function for this.
1005 */
1006 static const char *
netidbytype(int af,int sotype)1007 netidbytype(int af, int sotype)
1008 {
1009 struct nc_protos *p;
1010
1011 for (p = nc_protos; p->netid != NULL; p++) {
1012 if (af != p->af || sotype != p->sotype)
1013 continue;
1014 return (p->netid);
1015 }
1016 return (NULL);
1017 }
1018
1019 /*
1020 * Look up a netconfig entry based on a netid, and cache the result so
1021 * that we don't need to remember to call freenetconfigent().
1022 *
1023 * Otherwise it behaves just like getnetconfigent(), so nc_*error()
1024 * work on failure.
1025 */
1026 static struct netconfig *
getnetconf_cached(const char * netid)1027 getnetconf_cached(const char *netid)
1028 {
1029 static struct nc_entry {
1030 struct netconfig *nconf;
1031 struct nc_entry *next;
1032 } *head;
1033 struct nc_entry *p;
1034 struct netconfig *nconf;
1035
1036 for (p = head; p != NULL; p = p->next)
1037 if (strcmp(netid, p->nconf->nc_netid) == 0)
1038 return (p->nconf);
1039
1040 if ((nconf = getnetconfigent(netid)) == NULL)
1041 return (NULL);
1042 if ((p = malloc(sizeof(*p))) == NULL)
1043 err(1, "malloc");
1044 p->nconf = nconf;
1045 p->next = head;
1046 head = p;
1047
1048 return (p->nconf);
1049 }
1050
1051 /*
1052 * xdr routines for mount rpc's
1053 */
1054 static int
xdr_dir(XDR * xdrsp,char * dirp)1055 xdr_dir(XDR *xdrsp, char *dirp)
1056 {
1057 return (xdr_string(xdrsp, &dirp, MNTPATHLEN));
1058 }
1059
1060 static int
xdr_fh(XDR * xdrsp,struct nfhret * np)1061 xdr_fh(XDR *xdrsp, struct nfhret *np)
1062 {
1063 int i;
1064 long auth, authcnt, authfnd = 0;
1065
1066 if (!xdr_u_long(xdrsp, &np->stat))
1067 return (0);
1068 if (np->stat)
1069 return (1);
1070 switch (np->vers) {
1071 case 1:
1072 np->fhsize = NFS_FHSIZE;
1073 return (xdr_opaque(xdrsp, (caddr_t)np->nfh, NFS_FHSIZE));
1074 case 3:
1075 if (!xdr_long(xdrsp, &np->fhsize))
1076 return (0);
1077 if (np->fhsize <= 0 || np->fhsize > NFS3_FHSIZE)
1078 return (0);
1079 if (!xdr_opaque(xdrsp, (caddr_t)np->nfh, np->fhsize))
1080 return (0);
1081 if (!xdr_long(xdrsp, &authcnt))
1082 return (0);
1083 for (i = 0; i < authcnt; i++) {
1084 if (!xdr_long(xdrsp, &auth))
1085 return (0);
1086 if (np->auth == -1) {
1087 np->auth = auth;
1088 authfnd++;
1089 } else if (auth == np->auth) {
1090 authfnd++;
1091 }
1092 }
1093 /*
1094 * Some servers, such as DEC's OSF/1 return a nil authenticator
1095 * list to indicate RPCAUTH_UNIX.
1096 */
1097 if (authcnt == 0 && np->auth == -1)
1098 np->auth = AUTH_SYS;
1099 if (!authfnd && (authcnt > 0 || np->auth != AUTH_SYS))
1100 np->stat = EAUTH;
1101 return (1);
1102 }
1103 return (0);
1104 }
1105
1106 static void
usage(void)1107 usage(void)
1108 {
1109 (void)fprintf(stderr, "%s\n%s\n%s\n%s\n",
1110 "usage: mount_nfs [-23bcdiLlNPsTU] [-a maxreadahead] [-D deadthresh]",
1111 " [-g maxgroups] [-I readdirsize] [-o options] [-R retrycnt]",
1112 " [-r readsize] [-t timeout] [-w writesize] [-x retrans]",
1113 " rhost:path node");
1114 exit(1);
1115 }
1116