xref: /netbsd/usr.sbin/puffs/mount_psshfs/psshfs.c (revision d714e605)
1 /*	$NetBSD: psshfs.c,v 1.67 2021/12/05 08:11:39 msaitoh Exp $	*/
2 
3 /*
4  * Copyright (c) 2006-2009  Antti Kantee.  All Rights Reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
16  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18  * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 /*
29  * psshfs: puffs sshfs
30  *
31  * psshfs implements sshfs functionality on top of puffs making it
32  * possible to mount a filesystem through the sftp service.
33  *
34  * psshfs can execute multiple operations in "parallel" by using the
35  * puffs_cc framework for continuations.
36  *
37  * Concurrency control is handled currently by vnode locking (this
38  * will change in the future).  Context switch locations are easy to
39  * find by grepping for puffs_framebuf_enqueue_cc().
40  */
41 
42 #include <sys/cdefs.h>
43 #ifndef lint
44 __RCSID("$NetBSD: psshfs.c,v 1.67 2021/12/05 08:11:39 msaitoh Exp $");
45 #endif /* !lint */
46 
47 #include <sys/types.h>
48 #include <sys/wait.h>
49 #include <sys/socket.h>
50 
51 #include <stdio.h>
52 #include <assert.h>
53 #include <err.h>
54 #include <errno.h>
55 #include <mntopts.h>
56 #include <paths.h>
57 #include <poll.h>
58 #include <puffs.h>
59 #include <signal.h>
60 #include <stdlib.h>
61 #include <util.h>
62 #include <unistd.h>
63 
64 #include "psshfs.h"
65 
66 static int	pssh_connect(struct puffs_usermount *, int);
67 static void	psshfs_loopfn(struct puffs_usermount *);
68 __dead static void	usage(void);
69 static char *	cleanhostname(char *);
70 static char *	colon(char *);
71 static void	add_ssharg(char ***, int *, const char *);
72 static void	psshfs_notify(struct puffs_usermount *, int, int);
73 
74 #define SSH_PATH "/usr/bin/ssh"
75 
76 unsigned int max_reads;
77 static int sighup;
78 
79 static char *
cleanhostname(char * host)80 cleanhostname(char *host)
81 {
82 	if (*host == '[' && host[strlen(host) - 1] == ']') {
83 		host[strlen(host) - 1] = '\0';
84 		return (host + 1);
85 	} else
86 		return host;
87 }
88 
89 static char *
colon(char * cp)90 colon(char *cp)
91 {
92 	int flag = 0;
93 
94 	if (*cp == '[')
95 		flag = 1;
96 
97 	for (; *cp; ++cp) {
98 		if (*cp == '@' && *(cp+1) == '[')
99 			flag = 1;
100 		if (*cp == ']' && *(cp+1) == ':' && flag)
101 			return (cp+1);
102 		if (*cp == ':' && !flag)
103 			return (cp);
104 		if (*cp == '/')
105 			return NULL;
106 	}
107 	return NULL;
108 }
109 
110 static void
add_ssharg(char *** sshargs,int * nargs,const char * arg)111 add_ssharg(char ***sshargs, int *nargs, const char *arg)
112 {
113 
114 	*sshargs = realloc(*sshargs, (*nargs + 2) * sizeof(char*));
115 	if (!*sshargs)
116 		err(1, "realloc");
117 	(*sshargs)[(*nargs)++] = estrdup(arg);
118 	(*sshargs)[*nargs] = NULL;
119 }
120 
121 static void
usage(void)122 usage(void)
123 {
124 
125 	fprintf(stderr, "usage: %s "
126 	    "[-ceprst] [-F configfile] [-O sshopt=value] [-o opts] "
127 	    "user@host:path mountpath\n",
128 	    getprogname());
129 	exit(1);
130 }
131 
132 static void
takehup(int sig)133 takehup(int sig)
134 {
135 
136 	sighup = 1;
137 }
138 
139 int
main(int argc,char * argv[])140 main(int argc, char *argv[])
141 {
142 	struct psshfs_ctx pctx;
143 	struct puffs_usermount *pu;
144 	struct puffs_ops *pops;
145 	struct psshfs_node *root = &pctx.psn_root;
146 	struct puffs_node *pn_root;
147 	puffs_framev_fdnotify_fn notfn;
148 	struct vattr *rva;
149 	mntoptparse_t mp;
150 	char **sshargs;
151 	char *user;
152 	char *host;
153 	char *path;
154 	int mntflags, pflags, ch;
155 	int detach;
156 	int exportfs, refreshival, numconnections;
157 	int nargs;
158 
159 	setprogname(argv[0]);
160 	puffs_unmountonsignal(SIGINT, true);
161 	puffs_unmountonsignal(SIGTERM, true);
162 
163 	if (argc < 3)
164 		usage();
165 
166 	memset(&pctx, 0, sizeof(pctx));
167 	mntflags = pflags = exportfs = nargs = 0;
168 	numconnections = 1;
169 	detach = 1;
170 	refreshival = DEFAULTREFRESH;
171 	notfn = puffs_framev_unmountonclose;
172 	sshargs = NULL;
173 	add_ssharg(&sshargs, &nargs, SSH_PATH);
174 	add_ssharg(&sshargs, &nargs, "-axs");
175 	add_ssharg(&sshargs, &nargs, "-oClearAllForwardings=yes");
176 
177 	while ((ch = getopt(argc, argv, "c:eF:g:o:O:pr:st:u:")) != -1) {
178 		switch (ch) {
179 		case 'c':
180 			numconnections = atoi(optarg);
181 			if (numconnections < 1 || numconnections > 2) {
182 				fprintf(stderr, "%s: only 1 or 2 connections "
183 				    "permitted currently\n", getprogname());
184 				usage();
185 				/*NOTREACHED*/
186 			}
187 			break;
188 		case 'e':
189 			exportfs = 1;
190 			break;
191 		case 'F':
192 			add_ssharg(&sshargs, &nargs, "-F");
193 			add_ssharg(&sshargs, &nargs, optarg);
194 			break;
195 		case 'g':
196 			pctx.domanglegid = 1;
197 			pctx.manglegid = atoi(optarg);
198 			if (pctx.manglegid == (gid_t)-1)
199 				errx(1, "-1 not allowed for -g");
200 			pctx.mygid = getegid();
201 			break;
202 		case 'O':
203 			add_ssharg(&sshargs, &nargs, "-o");
204 			add_ssharg(&sshargs, &nargs, optarg);
205 			break;
206 		case 'o':
207 			mp = getmntopts(optarg, puffsmopts, &mntflags, &pflags);
208 			if (mp == NULL)
209 				err(1, "getmntopts");
210 			freemntopts(mp);
211 			break;
212 		case 'p':
213 			notfn = psshfs_notify;
214 			break;
215 		case 'r':
216 			max_reads = atoi(optarg);
217 			break;
218 		case 's':
219 			detach = 0;
220 			break;
221 		case 't':
222 			refreshival = atoi(optarg);
223 			if (refreshival < 0 && refreshival != -1)
224 				errx(1, "invalid timeout %d", refreshival);
225 			break;
226 		case 'u':
227 			pctx.domangleuid = 1;
228 			pctx.mangleuid = atoi(optarg);
229 			if (pctx.mangleuid == (uid_t)-1)
230 				errx(1, "-1 not allowed for -u");
231 			pctx.myuid = geteuid();
232 			break;
233 		default:
234 			usage();
235 			/*NOTREACHED*/
236 		}
237 	}
238 	argc -= optind;
239 	argv += optind;
240 
241 	if (pflags & PUFFS_FLAG_OPDUMP)
242 		detach = 0;
243 	pflags |= PUFFS_FLAG_BUILDPATH;
244 	pflags |= PUFFS_KFLAG_WTCACHE | PUFFS_KFLAG_IAONDEMAND;
245 
246 	if (argc != 2)
247 		usage();
248 
249 	PUFFSOP_INIT(pops);
250 
251 	PUFFSOP_SET(pops, psshfs, fs, unmount);
252 	PUFFSOP_SETFSNOP(pops, sync); /* XXX */
253 	PUFFSOP_SET(pops, psshfs, fs, statvfs);
254 	PUFFSOP_SET(pops, psshfs, fs, nodetofh);
255 	PUFFSOP_SET(pops, psshfs, fs, fhtonode);
256 
257 	PUFFSOP_SET(pops, psshfs, node, lookup);
258 	PUFFSOP_SET(pops, psshfs, node, create);
259 	PUFFSOP_SET(pops, psshfs, node, open);
260 	PUFFSOP_SET(pops, psshfs, node, inactive);
261 	PUFFSOP_SET(pops, psshfs, node, readdir);
262 	PUFFSOP_SET(pops, psshfs, node, getattr);
263 	PUFFSOP_SET(pops, psshfs, node, setattr);
264 	PUFFSOP_SET(pops, psshfs, node, mkdir);
265 	PUFFSOP_SET(pops, psshfs, node, remove);
266 	PUFFSOP_SET(pops, psshfs, node, readlink);
267 	PUFFSOP_SET(pops, psshfs, node, rmdir);
268 	PUFFSOP_SET(pops, psshfs, node, symlink);
269 	PUFFSOP_SET(pops, psshfs, node, rename);
270 	PUFFSOP_SET(pops, psshfs, node, read);
271 	PUFFSOP_SET(pops, psshfs, node, write);
272 	PUFFSOP_SET(pops, psshfs, node, reclaim);
273 
274 	pu = puffs_init(pops, argv[0], "psshfs", &pctx, pflags);
275 	if (pu == NULL)
276 		err(1, "puffs_init");
277 
278 	pctx.mounttime = time(NULL);
279 	pctx.refreshival = refreshival;
280 	pctx.numconnections = numconnections;
281 
282 	user = strdup(argv[0]);
283 	if ((host = strrchr(user, '@')) == NULL) {
284 		host = user;
285 	} else {
286 		*host++ = '\0';		/* break at the '@' */
287 		if (user[0] == '\0') {
288 			fprintf(stderr, "Missing username\n");
289 			usage();
290 		}
291 		add_ssharg(&sshargs, &nargs, "-l");
292 		add_ssharg(&sshargs, &nargs, user);
293 	}
294 
295 	if ((path = colon(host)) != NULL) {
296 		*path++ = '\0';		/* break at the ':' */
297 		pctx.mountpath = path;
298 	} else {
299 		pctx.mountpath = ".";
300 	}
301 
302 	host = cleanhostname(host);
303 	if (host[0] == '\0') {
304 		fprintf(stderr, "Missing hostname\n");
305 		usage();
306 	}
307 
308 	add_ssharg(&sshargs, &nargs, host);
309 	add_ssharg(&sshargs, &nargs, "sftp");
310 	pctx.sshargs = sshargs;
311 
312 	pctx.nextino = 2;
313 	memset(root, 0, sizeof(struct psshfs_node));
314 	TAILQ_INIT(&root->pw);
315 	pn_root = puffs_pn_new(pu, root);
316 	if (pn_root == NULL)
317 		return errno;
318 	puffs_setroot(pu, pn_root);
319 
320 	puffs_framev_init(pu, psbuf_read, psbuf_write, psbuf_cmp, NULL, notfn);
321 
322 	signal(SIGHUP, takehup);
323 	puffs_ml_setloopfn(pu, psshfs_loopfn);
324 	if (pssh_connect(pu, PSSHFD_META) == -1)
325 		err(1, "can't connect meta");
326 	if (puffs_framev_addfd(pu, pctx.sshfd,
327 	    PUFFS_FBIO_READ | PUFFS_FBIO_WRITE) == -1)
328 		err(1, "framebuf addfd meta");
329 	if (numconnections == 2) {
330 		if (pssh_connect(pu, PSSHFD_DATA) == -1)
331 			err(1, "can't connect data");
332 		if (puffs_framev_addfd(pu, pctx.sshfd_data,
333 		    PUFFS_FBIO_READ | PUFFS_FBIO_WRITE) == -1)
334 			err(1, "framebuf addfd data");
335 	} else {
336 		pctx.sshfd_data = pctx.sshfd;
337 	}
338 
339 	if (exportfs)
340 		puffs_setfhsize(pu, sizeof(struct psshfs_fid),
341 		    PUFFS_FHFLAG_NFSV2 | PUFFS_FHFLAG_NFSV3);
342 
343 	rva = &pn_root->pn_va;
344 	rva->va_fileid = pctx.nextino++;
345 
346 	/*
347 	 * For root link count, just guess something ridiculously high.
348 	 * Guessing too high has no known adverse effects, but fts(3)
349 	 * doesn't like too low values.  This guess will be replaced
350 	 * with the real value when readdir is first called for
351 	 * the root directory.
352 	 */
353 	rva->va_nlink = 8811;
354 
355 	if (detach)
356 		if (puffs_daemon(pu, 1, 1) == -1)
357 			err(1, "puffs_daemon");
358 
359 	if (puffs_mount(pu, argv[1], mntflags, puffs_getroot(pu)) == -1)
360 		err(1, "puffs_mount");
361 	if (puffs_setblockingmode(pu, PUFFSDEV_NONBLOCK) == -1)
362 		err(1, "setblockingmode");
363 
364 	if (puffs_mainloop(pu) == -1)
365 		err(1, "mainloop");
366 	puffs_exit(pu, 1);
367 
368 	return 0;
369 }
370 
371 #define RETRY_MAX 100
372 
373 void
psshfs_notify(struct puffs_usermount * pu,int fd,int what)374 psshfs_notify(struct puffs_usermount *pu, int fd, int what)
375 {
376 	struct psshfs_ctx *pctx = puffs_getspecific(pu);
377 	int nretry, which, newfd, dummy;
378 
379 	if (fd == pctx->sshfd) {
380 		which = PSSHFD_META;
381 	} else {
382 		assert(fd == pctx->sshfd_data);
383 		which = PSSHFD_DATA;
384 	}
385 
386 	if (puffs_getstate(pu) != PUFFS_STATE_RUNNING)
387 		return;
388 
389 	if (what != (PUFFS_FBIO_READ | PUFFS_FBIO_WRITE)) {
390 		puffs_framev_removefd(pu, fd, ECONNRESET);
391 		return;
392 	}
393 	close(fd);
394 
395 	/* deal with zmobies, beware of half-eaten brain */
396 	while (waitpid(-1, &dummy, WNOHANG) > 0)
397 		continue;
398 
399 	for (nretry = 0;;nretry++) {
400 		if ((newfd = pssh_connect(pu, which)) == -1)
401 			goto retry2;
402 
403 		if (puffs_framev_addfd(pu, newfd,
404 		    PUFFS_FBIO_READ | PUFFS_FBIO_WRITE) == -1)
405 			goto retry1;
406 
407 		break;
408  retry1:
409 		fprintf(stderr, "reconnect failed... ");
410 		close(newfd);
411  retry2:
412 		if (nretry < RETRY_MAX) {
413 			fprintf(stderr, "retry (%d left)\n", RETRY_MAX-nretry);
414 			sleep(nretry);
415 		} else {
416 			fprintf(stderr, "retry count exceeded, going south\n");
417 			exit(1); /* XXXXXXX */
418 		}
419 	}
420 }
421 
422 static int
pssh_connect(struct puffs_usermount * pu,int which)423 pssh_connect(struct puffs_usermount *pu, int which)
424 {
425 	struct psshfs_ctx *pctx = puffs_getspecific(pu);
426 	char * const *sshargs = pctx->sshargs;
427 	int fds[2];
428 	pid_t pid;
429 	int dnfd, x;
430 	int *sshfd;
431 	pid_t *sshpid;
432 
433 	if (which == PSSHFD_META) {
434 		sshfd = &pctx->sshfd;
435 		sshpid = &pctx->sshpid;
436 	} else {
437 		assert(which == PSSHFD_DATA);
438 		sshfd = &pctx->sshfd_data;
439 		sshpid = &pctx->sshpid_data;
440 	}
441 
442 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1)
443 		return -1;
444 
445 	pid = fork();
446 	switch (pid) {
447 	case -1:
448 		return -1;
449 		/*NOTREACHED*/
450 	case 0: /* child */
451 		if (dup2(fds[0], STDIN_FILENO) == -1)
452 			err(1, "child dup2");
453 		if (dup2(fds[0], STDOUT_FILENO) == -1)
454 			err(1, "child dup2");
455 		close(fds[0]);
456 		close(fds[1]);
457 
458 		dnfd = open(_PATH_DEVNULL, O_RDWR);
459 		if (dnfd != -1)
460 			dup2(dnfd, STDERR_FILENO);
461 
462 		execvp(sshargs[0], sshargs);
463 		/*NOTREACHED*/
464 		break;
465 	default:
466 		*sshpid = pid;
467 		*sshfd = fds[1];
468 		close(fds[0]);
469 		break;
470 	}
471 
472 	if (psshfs_handshake(pu, *sshfd) != 0)
473 		errx(1, "handshake failed, server does not support sftp?");
474 	x = 1;
475 	if (ioctl(*sshfd, FIONBIO, &x) == -1)
476 		err(1, "nonblocking descriptor %d", which);
477 
478 	return *sshfd;
479 }
480 
481 static void *
invalone(struct puffs_usermount * pu,struct puffs_node * pn,void * arg)482 invalone(struct puffs_usermount *pu, struct puffs_node *pn, void *arg)
483 {
484 	struct psshfs_node *psn = pn->pn_data;
485 
486 	psn->attrread = 0;
487 	psn->dentread = 0;
488 	psn->slread = 0;
489 
490 	return NULL;
491 }
492 
493 static void
psshfs_loopfn(struct puffs_usermount * pu)494 psshfs_loopfn(struct puffs_usermount *pu)
495 {
496 
497 	if (sighup) {
498 		puffs_pn_nodewalk(pu, invalone, NULL);
499 		sighup = 0;
500 	}
501 }
502