blob: ee51269e51bbb46c33b59e730df0126b24f177e8 [file] [log] [blame]
Miklos Szeredi3cb3f142001-11-08 11:34:54 +00001/*
2 FUSE: Filesystem in Userspace
3 Copyright (C) 2001 Miklos Szeredi (mszeredi@inf.bme.hu)
4
5 This program can be distributed under the terms of the GNU GPL.
6 See the file COPYING.
7*/
Miklos Szeredi0a7077f2001-11-11 18:20:17 +00008/* This program does the mounting and unmounting of FUSE filesystems */
9
10/*
11 * NOTE: This program should be part of (or be called from) /bin/mount
12 *
13 * Unless that is done, operations on /etc/mtab are not under lock, and so
Miklos Szeredife428122001-11-20 19:12:28 +000014 * data in this file may be lost. (I will _not_ reimplement that locking,
15 * and anyway that should be done in libc, if possible. But probably it
16 * isn't).
Miklos Szeredi0a7077f2001-11-11 18:20:17 +000017 */
Miklos Szeredi3cb3f142001-11-08 11:34:54 +000018
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22#include <unistd.h>
23#include <errno.h>
24#include <fcntl.h>
Miklos Szeredi552c2812001-11-08 14:56:53 +000025#include <pwd.h>
Miklos Szeredi0a7077f2001-11-11 18:20:17 +000026#include <mntent.h>
27#include <limits.h>
Miklos Szeredi3cb3f142001-11-08 11:34:54 +000028#include <sys/wait.h>
29#include <sys/stat.h>
30#include <sys/mount.h>
Miklos Szeredi8cffdb92001-11-09 14:49:18 +000031#include <sys/fsuid.h>
Miklos Szeredi3cb3f142001-11-08 11:34:54 +000032#include <linux/fuse.h>
Miklos Szeredi8cffdb92001-11-09 14:49:18 +000033
34#define CHECK_PERMISSION 1
35
36#ifndef MS_PERMISSION
37#define MS_PERMISSION 128
38#endif
Miklos Szeredi3cb3f142001-11-08 11:34:54 +000039
40#define FUSE_DEV "/proc/fs/fuse/dev"
41
Miklos Szeredicc8c9752001-11-21 10:03:39 +000042#define FUSE_MOUNTED_ENV "_FUSE_MOUNTED"
43#define FUSE_UMOUNT_CMD_ENV "_FUSE_UNMOUNT_CMD"
44#define FUSE_KERNEL_VERSION_ENV "_FUSE_KERNEL_VERSION"
45
Miklos Szeredi3cb3f142001-11-08 11:34:54 +000046const char *progname;
Miklos Szeredi552c2812001-11-08 14:56:53 +000047
48static const char *get_user_name()
49{
50 struct passwd *pw = getpwuid(getuid());
51 if(pw != NULL && pw->pw_name != NULL)
52 return pw->pw_name;
53 else {
54 fprintf(stderr, "%s: could not determine username\n", progname);
55 return NULL;
56 }
57}
58
Miklos Szeredi0a7077f2001-11-11 18:20:17 +000059static int add_mount(const char *dev, const char *mnt, const char *type)
Miklos Szeredi552c2812001-11-08 14:56:53 +000060{
61 int res;
Miklos Szeredi0a7077f2001-11-11 18:20:17 +000062 const char *mtab = _PATH_MOUNTED;
63 struct mntent ent;
Miklos Szeredi552c2812001-11-08 14:56:53 +000064 FILE *fp;
Miklos Szeredi0a7077f2001-11-11 18:20:17 +000065 char *opts;
Miklos Szeredi552c2812001-11-08 14:56:53 +000066
Miklos Szeredi0a7077f2001-11-11 18:20:17 +000067 fp = setmntent(mtab, "a");
Miklos Szeredi552c2812001-11-08 14:56:53 +000068 if(fp == NULL) {
Miklos Szeredi0a7077f2001-11-11 18:20:17 +000069 fprintf(stderr, "%s failed to open %s: %s\n", progname, mtab,
70 strerror(errno));
71 return -1;
72 }
73
74 if(getuid() != 0) {
75 const char *user = get_user_name();
76 if(user == NULL)
77 return -1;
78
79 opts = malloc(strlen(user) + 128);
80 if(opts != NULL)
81 sprintf(opts, "rw,nosuid,nodev,user=%s", user);
82 }
83 else
84 opts = strdup("rw,nosuid,nodev");
85
86 if(opts == NULL)
87 return -1;
88
89 ent.mnt_fsname = (char *) dev;
90 ent.mnt_dir = (char *) mnt;
91 ent.mnt_type = (char *) type;
92 ent.mnt_opts = opts;
93 ent.mnt_freq = 0;
94 ent.mnt_passno = 0;
95 res = addmntent(fp, &ent);
96 if(res != 0) {
97 fprintf(stderr, "%s: failed to add entry to %s: %s\n", progname,
98 mtab, strerror(errno));
Miklos Szeredi552c2812001-11-08 14:56:53 +000099 return -1;
100 }
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000101
102 endmntent(fp);
Miklos Szeredi552c2812001-11-08 14:56:53 +0000103 return 0;
104}
105
106static int remove_mount(const char *mnt)
107{
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000108 int res;
109 const char *mtab = _PATH_MOUNTED;
110 const char *mtab_new = _PATH_MOUNTED "~";
111 struct mntent *entp;
Miklos Szeredi552c2812001-11-08 14:56:53 +0000112 FILE *fp;
113 FILE *newfp;
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000114 const char *user = NULL;
Miklos Szeredi552c2812001-11-08 14:56:53 +0000115 int found;
Miklos Szeredi552c2812001-11-08 14:56:53 +0000116
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000117 fp = setmntent(mtab, "r");
Miklos Szeredi552c2812001-11-08 14:56:53 +0000118 if(fp == NULL) {
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000119 fprintf(stderr, "%s failed to open %s: %s\n", progname, mtab,
120 strerror(errno));
121 return -1;
Miklos Szeredi552c2812001-11-08 14:56:53 +0000122 }
123
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000124 newfp = setmntent(mtab_new, "w");
125 if(newfp == NULL) {
126 fprintf(stderr, "%s failed to open %s: %s\n", progname, mtab_new,
127 strerror(errno));
128 return -1;
129 }
130
131 if(getuid() != 0) {
132 user = get_user_name();
133 if(user == NULL)
134 return -1;
135 }
Miklos Szeredi552c2812001-11-08 14:56:53 +0000136
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000137 found = 0;
138 while((entp = getmntent(fp)) != NULL) {
139 int remove = 0;
140 if(!found && strcmp(entp->mnt_dir, mnt) == 0 &&
141 strcmp(entp->mnt_type, "fuse") == 0) {
142 if(user == NULL)
143 remove = 1;
144 else {
145 char *p = strstr(entp->mnt_opts, "user=");
146 if(p != NULL && strcmp(p + 5, user) == 0)
147 remove = 1;
148 }
Miklos Szeredi552c2812001-11-08 14:56:53 +0000149 }
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000150 if(remove) {
151 res = umount(mnt);
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000152 if(res == -1) {
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000153 fprintf(stderr, "%s: failed to unmount %s: %s\n", progname,
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000154 mnt, strerror(errno));
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000155 found = -1;
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000156 break;
157 }
Miklos Szeredi552c2812001-11-08 14:56:53 +0000158 found = 1;
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000159 }
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000160 else {
161 res = addmntent(newfp, entp);
162 if(res != 0) {
163 fprintf(stderr, "%s: failed to add entry to %s: %s", progname,
164 mtab_new, strerror(errno));
165
166 }
167 }
Miklos Szeredi552c2812001-11-08 14:56:53 +0000168 }
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000169
170 endmntent(fp);
171 endmntent(newfp);
Miklos Szeredi552c2812001-11-08 14:56:53 +0000172
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000173 if(found == 1) {
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000174 res = rename(mtab_new, mtab);
Miklos Szeredi552c2812001-11-08 14:56:53 +0000175 if(res == -1) {
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000176 fprintf(stderr, "%s: failed to rename %s to %s: %s\n", progname,
177 mtab_new, mtab, strerror(errno));
Miklos Szeredi552c2812001-11-08 14:56:53 +0000178 return -1;
179 }
180 }
181 else {
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000182 if(!found)
183 fprintf(stderr, "%s: entry for %s not found in %s\n", progname,
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000184 mnt, mtab);
185 unlink(mtab_new);
Miklos Szeredi552c2812001-11-08 14:56:53 +0000186 return -1;
187 }
188
Miklos Szeredi552c2812001-11-08 14:56:53 +0000189 return 0;
190}
191
Miklos Szeredife428122001-11-20 19:12:28 +0000192/* Until there is a nice interface for capabilities in _libc_, this will
193remain here. I don't think it is fair to expect users to compile libcap
194for this program. And anyway what's all this fuss about versioning the
195kernel interface? It is quite good as is. */
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000196#define _LINUX_CAPABILITY_VERSION 0x19980330
197
198typedef struct __user_cap_header_struct {
199 unsigned int version;
200 int pid;
201} *cap_user_header_t;
202
203typedef struct __user_cap_data_struct {
204 unsigned int effective;
205 unsigned int permitted;
206 unsigned int inheritable;
207} *cap_user_data_t;
208
209int capget(cap_user_header_t header, cap_user_data_t data);
210int capset(cap_user_header_t header, cap_user_data_t data);
211
212#define CAP_SYS_ADMIN 21
213
214static uid_t oldfsuid;
215static gid_t oldfsgid;
216static struct __user_cap_data_struct oldcaps;
217
218static int drop_privs()
219{
220 int res;
221 struct __user_cap_header_struct head;
222 struct __user_cap_data_struct newcaps;
223
224 head.version = _LINUX_CAPABILITY_VERSION;
225 head.pid = 0;
226 res = capget(&head, &oldcaps);
227 if(res == -1) {
228 fprintf(stderr, "%s: failed to get capabilities: %s\n", progname,
229 strerror(errno));
230 return -1;
231 }
232
233 oldfsuid = setfsuid(getuid());
234 oldfsgid = setfsgid(getgid());
235 newcaps = oldcaps;
236 /* Keep CAP_SYS_ADMIN for mount */
237 newcaps.effective &= (1 << CAP_SYS_ADMIN);
238
239 head.version = _LINUX_CAPABILITY_VERSION;
240 head.pid = 0;
241 res = capset(&head, &newcaps);
242 if(res == -1) {
243 fprintf(stderr, "%s: failed to set capabilities: %s\n", progname,
244 strerror(errno));
245 return -1;
246 }
247 return 0;
248}
249
250static void restore_privs()
251{
252 struct __user_cap_header_struct head;
253 int res;
254
255 head.version = _LINUX_CAPABILITY_VERSION;
256 head.pid = 0;
257 res = capset(&head, &oldcaps);
258 if(res == -1)
259 fprintf(stderr, "%s: failed to restore capabilities: %s\n", progname,
260 strerror(errno));
261
262 setfsuid(oldfsuid);
263 setfsgid(oldfsgid);
264}
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000265
266static int do_mount(const char *dev, const char *mnt, const char *type,
267 mode_t rootmode, int fd)
268{
269 int res;
270 struct fuse_mount_data data;
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000271 int flags = MS_NOSUID | MS_NODEV;
272
273 if(getuid() != 0) {
274 res = drop_privs();
275 if(res == -1)
276 return -1;
277
278 flags |= MS_PERMISSION;
279 }
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000280
281 data.version = FUSE_KERNEL_VERSION;
282 data.fd = fd;
283 data.rootmode = rootmode;
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000284 data.uid = getuid();
285 data.flags = 0;
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000286
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000287 res = mount(dev, mnt, type, flags, &data);
288 if(res == -1)
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000289 fprintf(stderr, "%s: mount failed: %s\n", progname, strerror(errno));
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000290
291 if(getuid() != 0)
292 restore_privs();
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000293
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000294 return res;
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000295}
296
297static int check_perm(const char *mnt, struct stat *stbuf)
298{
299 int res;
300
301 res = lstat(mnt, stbuf);
302 if(res == -1) {
303 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
304 progname, mnt, strerror(errno));
305 return -1;
306 }
307
308 if(!S_ISDIR(stbuf->st_mode) && !S_ISREG(stbuf->st_mode)) {
309 fprintf(stderr, "%s: mountpoint %s is a special file\n",
310 progname, mnt);
311 return -1;
312 }
313
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000314/* Should be done by the kernel */
315#ifdef CHECK_PERMISSION
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000316 if(getuid() != 0) {
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000317 if((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000318 fprintf(stderr, "%s: mountpoint %s not owned by user\n",
319 progname, mnt);
320 return -1;
321 }
322
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000323 res = access(mnt, W_OK);
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000324 if(res == -1) {
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000325 fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000326 progname, mnt);
327 return -1;
328 }
329 }
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000330#endif
331
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000332 return 0;
333}
334
335static int mount_fuse(const char *mnt)
336{
337 int res;
338 int fd;
339 const char *dev = FUSE_DEV;
340 const char *type = "fuse";
341 struct stat stbuf;
342
343 res = check_perm(mnt, &stbuf);
344 if(res == -1)
345 return -1;
346
347 fd = open(dev, O_RDWR);
348 if(fd == -1) {
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000349 int status;
350 pid_t pid = fork();
351 if(pid == 0) {
352 setuid(0);
353 execl("/sbin/modprobe", "modprobe", "fuse", NULL);
354 exit(1);
355 }
356 if(pid != -1)
357 waitpid(pid, &status, 0);
358
359 fd = open(dev, O_RDWR);
360 }
361 if(fd == -1) {
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000362 fprintf(stderr, "%s: unable to open fuse device %s: %s\n", progname,
363 dev, strerror(errno));
364 return -1;
365 }
366
367 res = do_mount(dev, mnt, type, stbuf.st_mode & S_IFMT, fd);
368 if(res == -1)
369 return -1;
370
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000371 res = add_mount(dev, mnt, type);
Miklos Szeredi552c2812001-11-08 14:56:53 +0000372 if(res == -1) {
373 umount(mnt);
374 return -1;
375 }
376
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000377 return fd;
378}
379
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000380static char *resolve_path(const char *orig, int unmount)
Miklos Szeredi552c2812001-11-08 14:56:53 +0000381{
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000382 char buf[PATH_MAX];
Miklos Szeredi552c2812001-11-08 14:56:53 +0000383
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000384 /* Resolving at unmount can only be done very carefully, not touching
385 the mountpoint... So for the moment it's not done. */
386 if(unmount)
387 return strdup(orig);
Miklos Szeredi552c2812001-11-08 14:56:53 +0000388
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000389 if(realpath(orig, buf) == NULL) {
390 fprintf(stderr, "%s: Bad mount point %s: %s\n", progname, orig,
391 strerror(errno));
392 return NULL;
393 }
394
395 return strdup(buf);
Miklos Szeredi552c2812001-11-08 14:56:53 +0000396}
397
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000398static void usage()
399{
400 fprintf(stderr,
Miklos Szeredi552c2812001-11-08 14:56:53 +0000401 "%s: [options] mountpoint [program [args ...]]\n"
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000402 "Options:\n"
Miklos Szeredi552c2812001-11-08 14:56:53 +0000403 " -h print help\n"
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000404 " -u unmount\n",
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000405 progname);
406 exit(1);
407}
408
409int main(int argc, char *argv[])
410{
411 int a;
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000412 int fd;
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000413 int res;
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000414 char *origmnt;
415 char *mnt;
416 int unmount = 0;
Miklos Szeredi552c2812001-11-08 14:56:53 +0000417 char **userprog;
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000418 int numargs;
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000419 char mypath[PATH_MAX];
420 char *unmount_cmd;
Miklos Szeredife428122001-11-20 19:12:28 +0000421 char verstr[128];
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000422
423 progname = argv[0];
424
425 for(a = 1; a < argc; a++) {
426 if(argv[a][0] != '-')
427 break;
428
429 switch(argv[a][1]) {
430 case 'h':
431 usage();
432 break;
Miklos Szeredi552c2812001-11-08 14:56:53 +0000433
434 case 'u':
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000435 unmount = 1;
Miklos Szeredi552c2812001-11-08 14:56:53 +0000436 break;
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000437
438 default:
439 fprintf(stderr, "%s: Unknown option %s\n", progname, argv[a]);
440 exit(1);
441 }
442 }
443
444 if(a == argc) {
445 fprintf(stderr, "%s: Missing mountpoint argument\n", progname);
446 exit(1);
447 }
448
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000449 origmnt = argv[a++];
450
451 if(getpid() != 0)
452 drop_privs();
453
454 mnt = resolve_path(origmnt, unmount);
455 if(mnt == NULL)
456 exit(1);
457
458 if(getpid() != 0)
459 restore_privs();
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000460
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000461 if(unmount) {
462 res = remove_mount(mnt);
Miklos Szeredi552c2812001-11-08 14:56:53 +0000463 if(res == -1)
464 exit(1);
465
466 return 0;
467 }
468
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000469 if(a == argc) {
470 fprintf(stderr, "%s: Missing program argument\n", progname);
471 exit(1);
472 }
473
474 userprog = argv + a;
Miklos Szeredi8cffdb92001-11-09 14:49:18 +0000475 numargs = argc - a;
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000476
477 fd = mount_fuse(mnt);
478 if(fd == -1)
479 exit(1);
480
481 /* Dup the file descriptor to stdin */
482 if(fd != 0) {
483 dup2(fd, 0);
484 close(fd);
485 }
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000486
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000487 /* Strangely this doesn't work after dropping permissions... */
488 res = readlink("/proc/self/exe", mypath, sizeof(mypath) - 1);
489 if(res == -1) {
490 fprintf(stderr, "%s: failed to determine self path: %s\n",
491 progname, strerror(errno));
492 strcpy(mypath, "fusermount");
493 fprintf(stderr, "using %s as the default\n", mypath);
494 }
495 else
496 mypath[res] = '\0';
497
Miklos Szeredi552c2812001-11-08 14:56:53 +0000498 /* Drop setuid/setgid permissions */
499 setuid(getuid());
500 setgid(getgid());
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000501
502 unmount_cmd = (char *) malloc(strlen(mypath) + strlen(mnt) + 64);
503 sprintf(unmount_cmd, "%s -u %s", mypath, mnt);
Miklos Szeredicc8c9752001-11-21 10:03:39 +0000504 setenv(FUSE_UMOUNT_CMD_ENV, unmount_cmd, 1);
Miklos Szeredife428122001-11-20 19:12:28 +0000505 sprintf(verstr, "%i", FUSE_KERNEL_VERSION);
Miklos Szeredicc8c9752001-11-21 10:03:39 +0000506 setenv(FUSE_KERNEL_VERSION_ENV, verstr, 1);
507 setenv(FUSE_MOUNTED_ENV, "", 1);
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000508
Miklos Szeredife428122001-11-20 19:12:28 +0000509 execvp(userprog[0], userprog);
Miklos Szeredi552c2812001-11-08 14:56:53 +0000510 fprintf(stderr, "%s: failed to exec %s: %s\n", progname, userprog[0],
511 strerror(errno));
512
Miklos Szeredi0a7077f2001-11-11 18:20:17 +0000513 close(0);
514 system(unmount_cmd);
515 return 1;
Miklos Szeredi3cb3f142001-11-08 11:34:54 +0000516}