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