1/*
2    FUSE: Filesystem in Userspace
3    Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
4
5    This program can be distributed under the terms of the GNU GPL.
6    See the file COPYING.
7*/
8/* This program does the mounting and unmounting of FUSE filesystems */
9
10#include <config.h>
11
12#include "mount_util.h"
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <ctype.h>
17#include <unistd.h>
18#include <getopt.h>
19#include <errno.h>
20#include <fcntl.h>
21#include <pwd.h>
22#include <mntent.h>
23#include <sys/wait.h>
24#include <sys/stat.h>
25#include <sys/mount.h>
26#include <sys/fsuid.h>
27#include <sys/socket.h>
28#include <sys/utsname.h>
29#include <grp.h>
30
31#define FUSE_DEV_NEW "/dev/fuse"
32
33#ifndef MS_DIRSYNC
34#define MS_DIRSYNC 128
35#endif
36
37static const char *progname = "ntfs-3g-mount";
38
39static int mount_max = 1000;
40
41int drop_privs(void);
42int restore_privs(void);
43
44static const char *get_user_name(void)
45{
46    struct passwd *pw = getpwuid(getuid());
47    if (pw != NULL && pw->pw_name != NULL)
48        return pw->pw_name;
49    else {
50        fprintf(stderr, "%s: could not determine username\n", progname);
51        return NULL;
52    }
53}
54
55int drop_privs(void)
56{
57	if (!getegid()) {
58
59		gid_t new_gid = getgid();
60
61		if (setresgid(-1, new_gid, getegid()) < 0) {
62			perror("priv drop: setresgid failed");
63			return -1;
64		}
65		if (getegid() != new_gid){
66			perror("dropping group privilege failed");
67			return -1;
68		}
69	}
70
71	if (!geteuid()) {
72
73		uid_t new_uid = getuid();
74
75		if (setresuid(-1, new_uid, geteuid()) < 0) {
76			perror("priv drop: setresuid failed");
77			return -1;
78		}
79		if (geteuid() != new_uid){
80			perror("dropping user privilege failed");
81			return -1;
82		}
83	}
84
85	return 0;
86}
87
88int restore_privs(void)
89{
90	if (geteuid()) {
91
92		uid_t ruid, euid, suid;
93
94		if (getresuid(&ruid, &euid, &suid) < 0) {
95			perror("priv restore: getresuid failed");
96			return -1;
97		}
98		if (setresuid(-1, suid, -1) < 0) {
99			perror("priv restore: setresuid failed");
100			return -1;
101		}
102		if (geteuid() != suid) {
103			perror("restoring privilege failed");
104			return -1;
105		}
106	}
107
108	if (getegid()) {
109
110		gid_t rgid, egid, sgid;
111
112		if (getresgid(&rgid, &egid, &sgid) < 0) {
113			perror("priv restore: getresgid failed");
114			return -1;
115		}
116		if (setresgid(-1, sgid, -1) < 0) {
117			perror("priv restore: setresgid failed");
118			return -1;
119		}
120		if (getegid() != sgid){
121			perror("restoring group privilege failed");
122			return -1;
123		}
124	}
125
126	return 0;
127}
128
129#ifndef IGNORE_MTAB
130static int add_mount(const char *source, const char *mnt, const char *type,
131                     const char *opts)
132{
133    return fuse_mnt_add_mount(progname, source, mnt, type, opts);
134}
135
136static int count_fuse_fs(void)
137{
138    struct mntent *entp;
139    int count = 0;
140    const char *mtab = _PATH_MOUNTED;
141    FILE *fp = setmntent(mtab, "r");
142    if (fp == NULL) {
143        fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
144                strerror(errno));
145        return -1;
146    }
147    while ((entp = getmntent(fp)) != NULL) {
148        if (strcmp(entp->mnt_type, "fuse") == 0 ||
149            strncmp(entp->mnt_type, "fuse.", 5) == 0)
150            count ++;
151    }
152    endmntent(fp);
153    return count;
154}
155
156
157#else /* IGNORE_MTAB */
158static int count_fuse_fs()
159{
160    return 0;
161}
162
163static int add_mount(const char *source, const char *mnt, const char *type,
164                     const char *opts)
165{
166    (void) source;
167    (void) mnt;
168    (void) type;
169    (void) opts;
170    return 0;
171}
172#endif /* IGNORE_MTAB */
173
174static int begins_with(const char *s, const char *beg)
175{
176    if (strncmp(s, beg, strlen(beg)) == 0)
177        return 1;
178    else
179        return 0;
180}
181
182struct mount_flags {
183    const char *opt;
184    unsigned long flag;
185    int on;
186    int safe;
187};
188
189static struct mount_flags mount_flags[] = {
190    {"rw",      MS_RDONLY,      0, 1},
191    {"ro",      MS_RDONLY,      1, 1},
192    {"suid",    MS_NOSUID,      0, 0},
193    {"nosuid",  MS_NOSUID,      1, 1},
194    {"dev",     MS_NODEV,       0, 0},
195    {"nodev",   MS_NODEV,       1, 1},
196    {"exec",    MS_NOEXEC,      0, 1},
197    {"noexec",  MS_NOEXEC,      1, 1},
198    {"async",   MS_SYNCHRONOUS, 0, 1},
199    {"sync",    MS_SYNCHRONOUS, 1, 1},
200    {"atime",   MS_NOATIME,     0, 1},
201    {"noatime", MS_NOATIME,     1, 1},
202    {"dirsync", MS_DIRSYNC,     1, 1},
203    {NULL,      0,              0, 0}
204};
205
206static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
207{
208    int i;
209
210    for (i = 0; mount_flags[i].opt != NULL; i++) {
211        const char *opt = mount_flags[i].opt;
212        if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
213            *on = mount_flags[i].on;
214            *flag = mount_flags[i].flag;
215            if (!mount_flags[i].safe && getuid() != 0) {
216                *flag = 0;
217                fprintf(stderr, "%s: unsafe option '%s' ignored\n",
218                        progname, opt);
219            }
220            return 1;
221        }
222    }
223    return 0;
224}
225
226static int add_option(char **optsp, const char *opt, unsigned expand)
227{
228    char *newopts;
229    if (*optsp == NULL)
230        newopts = strdup(opt);
231    else {
232        unsigned oldsize = strlen(*optsp);
233        unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
234        newopts = (char *) realloc(*optsp, newsize);
235        if (newopts)
236            sprintf(newopts + oldsize, ",%s", opt);
237    }
238    if (newopts == NULL) {
239        fprintf(stderr, "%s: failed to allocate memory\n", progname);
240        return -1;
241    }
242    *optsp = newopts;
243    return 0;
244}
245
246static int get_mnt_opts(int flags, char *opts, char **mnt_optsp)
247{
248    int i;
249    int l;
250
251    if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1)
252        return -1;
253
254    for (i = 0; mount_flags[i].opt != NULL; i++) {
255        if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
256            add_option(mnt_optsp, mount_flags[i].opt, 0) == -1)
257            return -1;
258    }
259
260    if (add_option(mnt_optsp, opts, 0) == -1)
261        return -1;
262    /* remove comma from end of opts*/
263    l = strlen(*mnt_optsp);
264    if ((*mnt_optsp)[l-1] == ',')
265        (*mnt_optsp)[l-1] = '\0';
266    if (getuid() != 0) {
267        const char *user = get_user_name();
268        if (user == NULL)
269            return -1;
270
271        if (add_option(mnt_optsp, "user=", strlen(user)) == -1)
272            return -1;
273        strcat(*mnt_optsp, user);
274    }
275    return 0;
276}
277
278static int opt_eq(const char *s, unsigned len, const char *opt)
279{
280    if(strlen(opt) == len && strncmp(s, opt, len) == 0)
281        return 1;
282    else
283        return 0;
284}
285
286static int get_string_opt(const char *s, unsigned len, const char *opt,
287                          char **val)
288{
289    unsigned opt_len = strlen(opt);
290
291    if (*val)
292        free(*val);
293    *val = (char *) malloc(len - opt_len + 1);
294    if (!*val) {
295        fprintf(stderr, "%s: failed to allocate memory\n", progname);
296        return 0;
297    }
298
299    memcpy(*val, s + opt_len, len - opt_len);
300    (*val)[len - opt_len] = '\0';
301    return 1;
302}
303
304static int do_mount(const char *mnt, char **typep, mode_t rootmode,
305                    int fd, const char *opts, const char *dev, char **sourcep,
306                    char **mnt_optsp)
307{
308    int res;
309    int flags = MS_NOSUID | MS_NODEV;
310    char *optbuf;
311    char *mnt_opts = NULL;
312    const char *s;
313    char *d;
314    char *fsname = NULL;
315    char *source = NULL;
316    char *type = NULL;
317    int blkdev = 0;
318
319    optbuf = (char *) malloc(strlen(opts) + 128);
320    if (!optbuf) {
321        fprintf(stderr, "%s: failed to allocate memory\n", progname);
322        return -1;
323    }
324
325    for (s = opts, d = optbuf; *s;) {
326        unsigned len;
327        const char *fsname_str = "fsname=";
328        for (len = 0; s[len] && s[len] != ','; len++);
329        if (begins_with(s, fsname_str)) {
330            if (!get_string_opt(s, len, fsname_str, &fsname))
331                goto err;
332        } else if (opt_eq(s, len, "blkdev")) {
333            blkdev = 1;
334        } else if (!begins_with(s, "fd=") &&
335                   !begins_with(s, "rootmode=") &&
336                   !begins_with(s, "user_id=") &&
337                   !begins_with(s, "group_id=")) {
338            int on;
339            int flag;
340            int skip_option = 0;
341            if (opt_eq(s, len, "large_read")) {
342                struct utsname utsname;
343                unsigned kmaj, kmin;
344                res = uname(&utsname);
345                if (res == 0 &&
346                    sscanf(utsname.release, "%u.%u", &kmaj, &kmin) == 2 &&
347                    (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
348                    fprintf(stderr, "%s: note: 'large_read' mount option is "
349			    "deprecated for %i.%i kernels\n", progname, kmaj, kmin);
350                    skip_option = 1;
351                }
352            }
353            if (!skip_option) {
354                if (find_mount_flag(s, len, &on, &flag)) {
355                    if (on)
356                        flags |= flag;
357                    else
358                        flags  &= ~flag;
359                } else {
360                    memcpy(d, s, len);
361                    d += len;
362                    *d++ = ',';
363                }
364            }
365        }
366        s += len;
367        if (*s)
368            s++;
369    }
370    *d = '\0';
371    res = get_mnt_opts(flags, optbuf, &mnt_opts);
372    if (res == -1)
373        goto err;
374
375    sprintf(d, "fd=%i,rootmode=%o,user_id=%i,group_id=%i",
376            fd, rootmode, getuid(), getgid());
377
378    source = malloc((fsname ? strlen(fsname) : 0) + strlen(dev) + 32);
379
380    type = malloc(32);
381    if (!type || !source) {
382        fprintf(stderr, "%s: failed to allocate memory\n", progname);
383        goto err;
384    }
385
386    strcpy(type, blkdev ? "fuseblk" : "fuse");
387
388    if (fsname)
389        strcpy(source, fsname);
390    else
391        strcpy(source, dev);
392
393    if (restore_privs())
394	goto err;
395
396    res = mount(source, mnt, type, flags, optbuf);
397    if (res == -1 && errno == EINVAL) {
398        /* It could be an old version not supporting group_id */
399        sprintf(d, "fd=%i,rootmode=%o,user_id=%i", fd, rootmode, getuid());
400        res = mount(source, mnt, type, flags, optbuf);
401    }
402
403    if (drop_privs())
404	goto err;
405
406    if (res == -1) {
407        int errno_save = errno;
408        if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
409            fprintf(stderr, "%s: 'fuseblk' support missing\n", progname);
410	else {
411            fprintf(stderr, "%s: mount failed: %s\n", progname, strerror(errno_save));
412	    if (errno_save == EPERM)
413		    fprintf(stderr, "User doesn't have privilege to mount. "
414			    "For more information\nplease see: "
415			    "http://ntfs-3g.org/support.html#unprivileged\n");
416	}
417	goto err;
418    } else {
419        *sourcep = source;
420        *typep = type;
421        *mnt_optsp = mnt_opts;
422    }
423out:
424    free(fsname);
425    free(optbuf);
426    return res;
427err:
428    free(source);
429    free(type);
430    free(mnt_opts);
431    res = -1;
432    goto out;
433}
434
435static int check_perm(const char **mntp, struct stat *stbuf, int *currdir_fd,
436                      int *mountpoint_fd)
437{
438    int res;
439    const char *mnt = *mntp;
440    const char *origmnt = mnt;
441
442    res = stat(mnt, stbuf);
443    if (res == -1) {
444        fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
445                progname, mnt, strerror(errno));
446        return -1;
447    }
448
449    /* No permission checking is done for root */
450    if (getuid() == 0)
451        return 0;
452
453    if (S_ISDIR(stbuf->st_mode)) {
454        *currdir_fd = open(".", O_RDONLY);
455        if (*currdir_fd == -1) {
456            fprintf(stderr, "%s: failed to open current directory: %s\n",
457                    progname, strerror(errno));
458            return -1;
459        }
460        res = chdir(mnt);
461        if (res == -1) {
462            fprintf(stderr, "%s: failed to chdir to mountpoint: %s\n",
463                    progname, strerror(errno));
464            return -1;
465        }
466        mnt = *mntp = ".";
467        res = lstat(mnt, stbuf);
468        if (res == -1) {
469            fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
470                    progname, origmnt, strerror(errno));
471            return -1;
472        }
473
474        if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
475            fprintf(stderr, "%s: mountpoint %s not owned by user\n",
476                    progname, origmnt);
477            return -1;
478        }
479
480        res = access(mnt, W_OK);
481        if (res == -1) {
482            fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
483                    progname, origmnt);
484            return -1;
485        }
486    } else if (S_ISREG(stbuf->st_mode)) {
487        static char procfile[256];
488        *mountpoint_fd = open(mnt, O_WRONLY);
489        if (*mountpoint_fd == -1) {
490            fprintf(stderr, "%s: failed to open %s: %s\n", progname, mnt,
491                    strerror(errno));
492            return -1;
493        }
494        res = fstat(*mountpoint_fd, stbuf);
495        if (res == -1) {
496            fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
497                    progname, mnt, strerror(errno));
498            return -1;
499        }
500        if (!S_ISREG(stbuf->st_mode)) {
501            fprintf(stderr, "%s: mountpoint %s is no longer a regular file\n",
502                    progname, mnt);
503            return -1;
504        }
505
506        sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd);
507        *mntp = procfile;
508    } else {
509        fprintf(stderr,
510                "%s: mountpoint %s is not a directory or a regular file\n",
511                progname, mnt);
512        return -1;
513    }
514
515
516    return 0;
517}
518
519static int try_open(const char *dev, char **devp)
520{
521    int fd;
522
523    if (restore_privs())
524	return -1;
525    fd = open(dev, O_RDWR);
526    if (drop_privs())
527	return -1;
528    if (fd != -1) {
529        *devp = strdup(dev);
530        if (*devp == NULL) {
531            fprintf(stderr, "%s: failed to allocate memory\n", progname);
532            close(fd);
533            fd = -1;
534        }
535    } else if (errno == ENODEV ||
536               errno == ENOENT) /* check for ENOENT too, for the udev case */
537        return -2;
538    else {
539        fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev,
540                strerror(errno));
541    }
542    return fd;
543}
544
545static int open_fuse_device(char **devp)
546{
547    int fd;
548
549    fd = try_open(FUSE_DEV_NEW, devp);
550    if (fd >= -1)
551        return fd;
552
553    fprintf(stderr, "%s: fuse device is missing, try 'modprobe fuse' as root\n",
554            progname);
555
556    return -1;
557}
558
559
560static int mount_fuse(const char *mnt, const char *opts)
561{
562    int res;
563    int fd;
564    char *dev;
565    struct stat stbuf;
566    char *type = NULL;
567    char *source = NULL;
568    char *mnt_opts = NULL;
569    const char *real_mnt = mnt;
570    int currdir_fd = -1;
571    int mountpoint_fd = -1;
572
573    fd = open_fuse_device(&dev);
574    if (fd == -1)
575        return -1;
576
577    if (getuid() != 0 && mount_max != -1) {
578        if (count_fuse_fs() >= mount_max) {
579            fprintf(stderr, "%s: too many mounted FUSE filesystems (%d+)\n",
580		    progname, mount_max);
581	    goto err;
582        }
583    }
584
585    res = check_perm(&real_mnt, &stbuf, &currdir_fd, &mountpoint_fd);
586    if (res != -1)
587         res = do_mount(real_mnt, &type, stbuf.st_mode & S_IFMT, fd, opts, dev,
588			&source, &mnt_opts);
589
590    if (currdir_fd != -1) {
591        fchdir(currdir_fd);
592        close(currdir_fd);
593    }
594    if (mountpoint_fd != -1)
595        close(mountpoint_fd);
596
597    if (res == -1)
598	goto err;
599
600    if (restore_privs())
601	goto err;
602
603    if (geteuid() == 0) {
604
605	if (setgroups(0, NULL) == -1) {
606	    perror("priv drop: setgroups failed");
607	    goto err;
608	}
609
610        res = add_mount(source, mnt, type, mnt_opts);
611        if (res == -1) {
612            umount2(mnt, 2); /* lazy umount */
613	    drop_privs();
614	    goto err;
615        }
616    }
617
618    if (drop_privs())
619	goto err;
620out:
621    free(source);
622    free(type);
623    free(mnt_opts);
624    free(dev);
625
626    return fd;
627err:
628    close(fd);
629    fd = -1;
630    goto out;
631}
632
633int fusermount(int unmount, int quiet, int lazy, const char *opts,
634	       const char *origmnt)
635{
636    int res = -1;
637    char *mnt;
638    mode_t old_umask;
639
640    mnt = fuse_mnt_resolve_path(progname, origmnt);
641    if (mnt == NULL)
642	return -1;
643
644    old_umask = umask(033);
645
646    if (unmount) {
647
648	if (restore_privs())
649	    goto out;
650
651        if (geteuid() == 0)
652            res = fuse_mnt_umount(progname, mnt, lazy);
653        else {
654            res = umount2(mnt, lazy ? 2 : 0);
655            if (res == -1 && !quiet)
656                fprintf(stderr, "%s: failed to unmount %s: %s\n", progname,
657                        mnt, strerror(errno));
658        }
659
660	if (drop_privs())
661	    res = -1;
662
663    } else
664	    res = mount_fuse(mnt, opts);
665out:
666    umask(old_umask);
667    free(mnt);
668    return res;
669}
670