mount.nfs: Don't allow the user to specify addr= or clientaddr=.
[nfs-utils.git] / utils / mount / stropts.c
1 /*
2  * stropts.c -- NFS mount using C string to pass options to kernel
3  *
4  * Copyright (C) 2007 Oracle.  All rights reserved.
5  * Copyright (C) 2007 Chuck Lever <chuck.lever@oracle.com>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public
18  * License along with this program; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 021110-1307, USA.
21  *
22  */
23
24 #include <ctype.h>
25 #include <unistd.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <stdlib.h>
29 #include <errno.h>
30 #include <netdb.h>
31 #include <time.h>
32 #include <sys/socket.h>
33 #include <sys/mount.h>
34
35 #include "xcommon.h"
36 #include "mount.h"
37 #include "nls.h"
38 #include "nfs_mount.h"
39 #include "mount_constants.h"
40 #include "stropts.h"
41 #include "error.h"
42 #include "network.h"
43
44 #ifdef HAVE_RPCSVC_NFS_PROT_H
45 #include <rpcsvc/nfs_prot.h>
46 #else
47 #include <linux/nfs.h>
48 #define nfsstat nfs_stat
49 #endif
50
51 #ifndef NFS_PORT
52 #define NFS_PORT 2049
53 #endif
54
55 extern int nfs_mount_data_version;
56 extern char *progname;
57 extern int verbose;
58
59 static int retry_opt = 10000;           /* 10,000 minutes ~= 1 week */
60 static int bg_opt = 0;
61 static int addr_opt = 0;
62 static int ca_opt = 0;
63
64 static int parse_devname(char *hostdir, char **hostname, char **dirname)
65 {
66         char *s;
67
68         if (!(s = strchr(hostdir, ':'))) {
69                 nfs_error(_("%s: directory to mount not in host:dir format"),
70                                 progname);
71                 return -1;
72         }
73         *hostname = hostdir;
74         *dirname = s + 1;
75         *s = '\0';
76         /* Ignore all but first hostname in replicated mounts
77            until they can be fully supported. (mack@sgi.com) */
78         if ((s = strchr(hostdir, ','))) {
79                 *s = '\0';
80                 nfs_error(_("%s: warning: multiple hostnames not supported"),
81                                 progname);
82         }
83         return 0;
84 }
85
86 static int fill_ipv4_sockaddr(const char *hostname, struct sockaddr_in *addr)
87 {
88         struct hostent *hp;
89         addr->sin_family = AF_INET;
90
91         if (inet_aton(hostname, &addr->sin_addr))
92                 return 1;
93         if ((hp = gethostbyname(hostname)) == NULL) {
94                 nfs_error(_("%s: can't get address for %s\n"),
95                                 progname, hostname);
96                 return 0;
97         }
98         if (hp->h_length > sizeof(struct in_addr)) {
99                 nfs_error(_("%s: got bad hp->h_length"), progname);
100                 hp->h_length = sizeof(struct in_addr);
101         }
102         memcpy(&addr->sin_addr, hp->h_addr, hp->h_length);
103         return 1;
104 }
105
106 /*
107  * XXX: This should really use the technique neil recently added
108  * to get the address off the local end of a socket connected to
109  * the server -- to get the right address to use on multi-homed
110  * clients
111  */
112 static int get_my_ipv4addr(char *ip_addr, int len)
113 {
114         char myname[1024];
115         struct sockaddr_in myaddr;
116
117         if (gethostname(myname, sizeof(myname))) {
118                 nfs_error(_("%s: can't determine client address\n"),
119                                 progname);
120                 return 0;
121         }
122         if (!fill_ipv4_sockaddr(myname, &myaddr))
123                 return 0;
124
125         snprintf(ip_addr, len, "%s", inet_ntoa(myaddr.sin_addr));
126         ip_addr[len - 1] = '\0';
127
128         return 1;
129 }
130
131 /*
132  * Walk through our mount options string, and indicate the presence
133  * of 'bg', 'retry=', 'addr=', and 'clientaddr='.
134  */
135 static void extract_interesting_options(char *opts)
136 {
137         char *opt, *opteq;
138         int val;
139
140         opts = xstrdup(opts);
141
142         for (opt = strtok(opts, ","); opt; opt = strtok(NULL, ",")) {
143                 if ((opteq = strchr(opt, '='))) {
144                         val = atoi(opteq + 1);
145                         *opteq = '\0';
146                         if (strcmp(opt, "bg") == 0)
147                                 bg_opt++;
148                         else if (strcmp(opt, "retry") == 0)
149                                 retry_opt = val;
150                         else if (strcmp(opt, "addr") == 0)
151                                 addr_opt++;
152                         else if (strcmp(opt, "clientaddr") == 0)
153                                 ca_opt++;
154                 } else {
155                         if (strcmp(opt, "bg") == 0)
156                                 bg_opt++;
157                 }
158         }
159
160         free(opts);
161 }
162
163 /*
164  * Append the 'addr=' option to the options string.
165  *
166  * Returns 1 if 'addr=' option created successfully;
167  * otherwise zero.
168  */
169 static int append_addr_opt(const char *spec, char **extra_opts)
170 {
171         static char hostdir[1024], new_opts[1024], ip_addr[255];
172         char *hostname, *dirname, *s, *old_opts;
173         struct sockaddr_in addr;
174
175         if (strlen(spec) >= sizeof(hostdir)) {
176                 nfs_error(_("%s: excessively long host:dir argument\n"),
177                                 progname);
178                 return 0;
179         }
180         strcpy(hostdir, spec);
181         if (parse_devname(hostdir, &hostname, &dirname)) {
182                 nfs_error(_("%s: parsing host:dir argument failed\n"),
183                                 progname);
184                 return 0;
185         }
186
187         if (!fill_ipv4_sockaddr(hostname, &addr))
188                 return 0;
189         if (!get_my_ipv4addr(ip_addr, sizeof(ip_addr)))
190                 return 0;
191
192         /* add IP address to mtab options for use when unmounting */
193         s = inet_ntoa(addr.sin_addr);
194         old_opts = *extra_opts;
195         if (!old_opts)
196                 old_opts = "";
197         if (strlen(old_opts) + strlen(s) + 10 >= sizeof(new_opts)) {
198                 nfs_error(_("%s: excessively long option argument\n"),
199                                 progname);
200                 return 0;
201         }
202         snprintf(new_opts, sizeof(new_opts), "%s%saddr=%s",
203                  old_opts, *old_opts ? "," : "", s);
204         *extra_opts = xstrdup(new_opts);
205
206         return 1;
207 }
208
209 /*
210  * Append the 'clientaddr=' option to the options string.
211  *
212  * Returns 1 if 'clientaddr=' option created successfully;
213  * otherwise zero.
214  */
215 static int append_clientaddr_opt(const char *spec, char **extra_opts)
216 {
217         static char new_opts[1024], cbuf[1024];
218         static char ip_addr[16] = "127.0.0.1";
219
220         if (!get_my_ipv4addr(ip_addr, sizeof(ip_addr)))
221                 return 0;
222
223         /* Ensure we have enough padding for the following strcat()s */
224         if (strlen(*extra_opts) + strlen(ip_addr) + 10 >= sizeof(new_opts)) {
225                 nfs_error(_("%s: excessively long option argument"),
226                                 progname);
227                 return 0;
228         }
229
230         strcat(new_opts, *extra_opts);
231
232         snprintf(cbuf, sizeof(cbuf) - 1, "%sclientaddr=%s",
233                         *extra_opts ? "," : "", ip_addr);
234         strcat(new_opts, cbuf);
235
236         *extra_opts = xstrdup(new_opts);
237
238         return 1;
239 }
240
241 /*
242  * nfsmount_s - Mount an NFSv2 or v3 file system using C string options
243  *
244  * @spec:       C string hostname:path specifying remoteshare to mount
245  * @node:       C string pathname of local mounted on directory
246  * @flags:      MS_ style flags
247  * @extra_opts: pointer to C string containing fs-specific mount options
248  *              (possibly also a return argument)
249  * @fake:       flag indicating whether to carry out the whole operation
250  * @bg:         one if this is a backgrounded mount attempt
251  *
252  * XXX: need to handle bg, fg, and retry options.
253  */
254 int nfsmount_s(const char *spec, const char *node, int flags,
255                 char **extra_opts, int fake, int bg)
256 {
257         int retval = EX_FAIL;
258
259         extract_interesting_options(*extra_opts);
260
261         if (!bg && addr_opt) {
262                 nfs_error(_("%s: Illegal option: 'addr='"), progname);
263                 goto fail;
264         }
265
266         if (!append_addr_opt(spec, extra_opts))
267                 goto fail;
268
269         if (verbose)
270                 printf(_("%s: text-based options: '%s'\n"),
271                         progname, *extra_opts);
272
273         if (!fake) {
274                 if (mount(spec, node, "nfs",
275                                 flags & ~(MS_USER|MS_USERS), *extra_opts)) {
276                         mount_error(spec, node, errno);
277                         goto fail;
278                 }
279         }
280
281         return 0;
282
283 fail:
284         return retval;
285 }
286
287 /*
288  * nfs4mount_s - Mount an NFSv4 file system using C string options
289  *
290  * @spec:       C string hostname:path specifying remoteshare to mount
291  * @node:       C string pathname of local mounted on directory
292  * @flags:      MS_ style flags
293  * @extra_opts: pointer to C string containing fs-specific mount options
294  *              (possibly also a return argument)
295  * @fake:       flag indicating whether to carry out the whole operation
296  * @child:      one if this is a backgrounded mount
297  *
298  * XXX: need to handle bg, fg, and retry options.
299  *
300  */
301 int nfs4mount_s(const char *spec, const char *node, int flags,
302                 char **extra_opts, int fake, int child)
303 {
304         int retval = EX_FAIL;
305
306         extract_interesting_options(*extra_opts);
307
308         if (addr_opt) {
309                 nfs_error(_("%s: Illegal option: 'addr='"), progname);
310                 goto fail;
311         }
312
313         if (ca_opt) {
314                 nfs_error(_("%s: Illegal option: 'clientaddr='"), progname);
315                 goto fail;
316         }
317
318         if (!append_addr_opt(spec, extra_opts))
319                 goto fail;
320
321         if (!append_clientaddr_opt(spec, extra_opts))
322                 goto fail;
323
324         if (verbose)
325                 printf(_("%s: text-based options: '%s'\n"),
326                         progname, *extra_opts);
327
328         if (!fake) {
329                 if (mount(spec, node, "nfs4",
330                                 flags & ~(MS_USER|MS_USERS), *extra_opts)) {
331                         mount_error(spec, node, errno);
332                         goto fail;
333                 }
334         }
335
336         return 0;
337
338 fail:
339         return retval;
340 }