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