pax_global_header00006660000000000000000000000064152132236520014513gustar00rootroot0000000000000052 comment=2e6aebdadc47960dc3ec15e8b0527de89f477acf sqweek-9mount-2e6aebd/000077500000000000000000000000001521322365200150155ustar00rootroot00000000000000sqweek-9mount-2e6aebd/.gitignore000066400000000000000000000000251521322365200170020ustar00rootroot000000000000009bind 9mount 9umount sqweek-9mount-2e6aebd/9bind.c000066400000000000000000000014361521322365200161720ustar00rootroot00000000000000/* © 2008 sqweek * See COPYING for details. */ #include #include #include #include #include #include int main(int argc, char **argv) { char *old = NULL, *new = NULL; struct stat stbuf; while (*++argv) { if (!old) { old = *argv; } else if (!new) { new = *argv; } else { errx(1, "%s: too many arguments", *argv); } } if (!old || !new) { errx(1, "usage: 9bind old new"); } /* Make sure mount exists, is writable, and not sticky */ if (stat(new, &stbuf) || access(new, W_OK)) { err(1, "%s", new); } if (stbuf.st_mode & S_ISVTX) { errx(1, "%s: refusing to bind over sticky directory", new); } if (mount(old, new, NULL, MS_BIND, NULL)) { err(1, "mount"); } return 0; } sqweek-9mount-2e6aebd/9mount.1000066400000000000000000000060011521322365200163270ustar00rootroot00000000000000.TH "9mount" "1" "23 July 2008" "9mount" "User commands" .SH NAME 9mount, 9bind, 9umount \- mount/unmount 9p filesystems .SH SYNOPSIS .B 9mount [ insuvx ] [ -a SPEC ] [ -c CACHE ] [ -d DEBUG ] [ -m MSIZE ] DIAL MOUNTPT .PP .B 9bind OLD NEW .PP .B 9umount MOUNTPT .SH DESCRIPTION .B 9mount mounts a 9p filesystem served at DIAL on MOUNTPT. MOUNTPT must be writable by you and not sticky. DIAL is a dial string assuming one of the forms: .PP unix!SOCKET .br tcp!HOST[!PORT] .br virtio!CHANNEL .br - .PP where SOCKET is the name of a file representing a socket, HOST is a hostname, PORT is a port number or service name, and CHANNEL is a virtio channel name (currently ignored). - indicates that 9p messages should be read/written on stdin/stdout. .B 9mount has several options: .TP -0 allow setuid files to be served (root only) .TP -i mount the file system with your uid/gid .TP -n dry-run, print mount command to stderr but don't actually mount anything .TP -s single attach mode - all users accessing the mount point see the same filesystem (by default they'll each see a unique attach) .TP -u use the 9P2000.u extensions .TP -v allow device files to be served (root only) .TP -x exclusive access - other users cannot access the mount point .TP -a SPEC SPEC determines which file tree to mount when attaching to file servers that export multiple trees .TP -c CACHE turns on caching using CACHE mode. Supported modes are .I loose (suitable for exclusive read-only mounts), .IR fscache , and .IR mmap . .TP -d DEBUG comma seperated list of channels for which to enable debug output. Possible channels include: err, devel, 9p, vfs, conv, mux, trans, alloc, fcall. .TP -m MSIZE specifies the maximum length of a single 9p message in bytes. Must be less than or equal to 8192 for non-root users. .PP .B 9bind performs a bind mount, making the tree visible at directory OLD also visible at mount point NEW. .PP .B 9umount unmounts a 9p filesystem previously mounted by you. .SH ENVIRONMENT .TP $USER the uname to provide to the server. .SH EXAMPLES .TP 9mount -i 'unix!/tmp/ns.'$USER'.:0/factotum' $HOME/n/factotum mount p9p's factotum interface .TP 9mount 'tcp!sources.cs.bell-labs.com' $HOME/n/sources import plan 9's "sources" .TP 9mount -u -a/home/sqweek/mail 'tcp!wren!5640' $HOME/mail import my maildir from my server(wren), being served by ufs .TP 9mount -i 'tcp!wren' $HOME/n/wren; 9bind $HOME/n/wren/home/sqweek/mail $HOME/mail again importing my maildir, this time serving via u9fs .SH BUGS .B 9mount truncates user names and SPECs to 249 characters. .B 9umount doesn't know this, so you won't be able to unmount anything outside your home directory. But you probably never bother logging out if your user name is that long. .PP .B 9mount doesn't update /etc/mtab. .PP .B 9bind only does a "shallow", non-recursive bind - any mounted filesystems under the OLD tree will not appear mounted in the NEW tree. .PP If you .B 9bind a non-9p filesystem outside your home directory, .B 9umount won't let you unmount it. .SH AUTHOR sqweek@gmail.com .SH SEE ALSO .BR mount (1) sqweek-9mount-2e6aebd/9mount.c000066400000000000000000000166511521322365200164250ustar00rootroot00000000000000/* © 2008 sqweek * See COPYING for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define nelem(x) (sizeof(x)/sizeof(*(x))) enum { Maxmsize = 8192, }; struct {char *mnemonic; int mask;} debug_flags[] = { {"err", 0x001}, {"devel", 0x002}, {"9p", 0x004}, {"vfs", 0x008}, {"conv", 0x010}, {"mux", 0x020}, {"trans", 0x040}, {"alloc", 0x080}, {"fcall", 0x100} }; char* append(char **dest, char *src, int *destlen) { while (strlen(*dest) + 1 + strlen(src) > *destlen) *destlen *= 2; if (!(*dest=realloc(*dest, *destlen))) errx(1, "out of memory"); if (**dest) strcat(*dest, ","); strcat(*dest, src); return *dest; } char* getarg(char opt, char *cp, char*** argv) { if (*(cp+1)) { return cp+1; } else if (*(*argv+1)) { return *++(*argv); } else { errx(1, "-%c: expected argument", opt); } return NULL; } void parsedial(char *dial, char **network, char **netaddr, int *port) { char *cp; if (!(*network=strtok(dial, "!"))) { errx(1, "empty dial string"); } if (strcmp(*network, "unix") != 0 && strcmp(*network, "tcp") != 0 && strcmp(*network, "virtio") != 0) { errx(1, "%s: unknown network (expecting unix, tcp or virtio)", *network); } if (!(*netaddr=strtok(NULL, "!"))) { errx(1, "missing dial netaddress"); } if (strcmp(*network, "tcp") == 0) { char *service; if ((service=strtok(NULL, "!"))) { if (strspn(service, "0123456789") == strlen(service)) { *port = atoi(service); } else { struct servent *sv; if ((sv=getservbyname(service, *network))) { /* sv->s_port is a 16-bit big endian masquerading as an int */ *port = ntohs((uint16_t)sv->s_port); endservent(); } else { errx(1, "%s: unknown service", service); } } } } if ((cp=strtok(NULL, "!"))) { errx(1, "%s: junk trailing dial string", cp); } if (strcmp(*network, "unix") == 0) { if (access(*netaddr, R_OK | W_OK)) { err(1, "%s", *netaddr); } } } int main(int argc, char **argv) { char buf[256], *opts, *dial = NULL, *mountpt = NULL; int optlen = 64, port = 0, i; struct stat stbuf; struct passwd *pw; int xflag = 0, axess = 0, dotu = 0, uidgid = 0, dev = 0, suid = 0, debug = 0, dryrun = 0; char *debugstr = NULL, *msize = NULL, *cache = NULL, *aname = NULL; char *cp, *proto, *addr; unsigned long mountflags = 0; if (!(opts=calloc(optlen, 1))) { err(1, "calloc"); } while (*++argv) { if (**argv == '-' && (*argv)[1] != '\0') { for (cp=*argv+1; *cp; ++cp) { switch (*cp) { case '0': suid = 1; break; case 'i': uidgid = 1; break; case 'n': dryrun = 1; break; case 's': axess = -1; break; case 'u': dotu = 1; break; case 'v': dev = 1; break; case 'x': xflag = 1; axess = getuid(); break; case 'a': aname = getarg('a', cp, &argv); *cp-- = '\0'; /* breaks out of for loop */ break; case 'c': cache = getarg('c', cp, &argv); *cp-- = '\0'; break; case 'd': debugstr = getarg('d', cp, &argv); *cp-- = '\0'; break; case 'm': msize = getarg('m', cp, &argv); *cp-- = '\0'; break; default: errx(1, "unrecognised argument '%c'", *cp); } } } else if (!dial) { dial = *argv; } else if (!mountpt) { mountpt = *argv; } else { errx(1, "%s: too many arguments", *argv); } } if (!dial || !mountpt) { errx(1, "usage: 9mount [ -0insuvx ] [ -a spec ] [ -c cache ] [ -d debug ] [ -m msize ] dial mountpt"); } if(!(pw=getpwuid(getuid()))) { err(1, "who are you?? getpwuid failed"); } /* Make sure mount exists, is writable, and not sticky */ if (stat(mountpt, &stbuf) || access(mountpt, W_OK)) { err(1, "%s", mountpt); } if (stbuf.st_mode & S_ISVTX) { errx(1, "%s: refusing to mount over sticky directory", mountpt); } if (strcmp(dial, "-") == 0) { proto = "fd"; addr = "nodev"; append(&opts, "rfdno=0,wfdno=1", &optlen); } else { parsedial(dial, &proto, &addr, &port); } /* set up mount options */ append(&opts, proto, &optlen); /* < 2.6.24 */ snprintf(buf, sizeof(buf), "trans=%s", proto); append(&opts, buf, &optlen); /* >= 2.6.24 */ if (aname) { if (strchr(aname, ',')) { errx(1, "%s: spec can't contain commas", aname); } snprintf(buf, sizeof(buf), "aname=%s", aname); append(&opts, buf, &optlen); } if (cache) { if (strcmp(cache, "loose") != 0 && strcmp(cache, "fscache") != 0 && strcmp(cache, "mmap") != 0) { errx(1, "%s: unknown cache mode (expecting loose, fscache, or mmap)", cache); } snprintf(buf, sizeof(buf), "cache=%s", cache); append(&opts, buf, &optlen); } if (debugstr) { for (cp=strtok(debugstr, ","); cp; cp=strtok(NULL, ",")) { for (i=0; i= nelem(debug_flags)) { errx(1, "%s: unrecognised debug channel", cp); } } snprintf(buf, sizeof(buf), "debug=0x%04x", debug); append(&opts, buf, &optlen); } if (msize) { unsigned long nmsize; char *end = NULL; nmsize = strtoul(msize, &end, 10); if (*end || nmsize == 0 || nmsize > INT_MAX) { errx(1, "%s: msize must be a positive integer", msize); } if (pw->pw_uid != 0 && nmsize > Maxmsize) { nmsize = Maxmsize; } snprintf(buf, sizeof(buf), "msize=%lu", nmsize); append(&opts, buf, &optlen); } snprintf(buf, sizeof(buf), "name=%s", pw->pw_name); append(&opts, buf, &optlen); if (getenv("USER")) { snprintf(buf, sizeof(buf), "uname=%s", getenv("USER")); } else { snprintf(buf, sizeof(buf), "uname=%s", pw->pw_name); } if (strchr(buf, ',')) { errx(1, "%s: username can't contain commas", buf+6); } append(&opts, buf, &optlen); if (axess == -1) { append(&opts, "access=any", &optlen); } else if (xflag) { snprintf(buf, sizeof(buf), "access=%d", axess); append(&opts, buf, &optlen); } if (!dotu) { append(&opts, "noextend", &optlen); } if (!dev || pw->pw_uid != 0) { mountflags |= MS_NODEV; append(&opts, "nodev", &optlen); // this is a no-op but provides dryrun consistency } if (!suid || pw->pw_uid != 0) { mountflags |= MS_NOSUID; append(&opts, "nosuid", &optlen); // this is a no-op but provides dryrun consistency } if (uidgid) { snprintf(buf, sizeof(buf), "uid=%d,gid=%d", getuid(), getgid()); append(&opts, buf, &optlen); /* < 2.6.24 */ snprintf(buf, sizeof(buf), "dfltuid=%d,dfltgid=%d", getuid(), getgid()); append(&opts, buf, &optlen); /* >= 2.6.24 */ } if (port) { snprintf(buf, sizeof(buf), "port=%d", port); append(&opts, buf, &optlen); } if (strcmp(proto, "tcp") == 0) { struct addrinfo *ai; struct addrinfo aihints; int r; memset(&aihints, 0, sizeof(aihints)); aihints.ai_family = AF_INET; aihints.ai_socktype = SOCK_STREAM; if ((r=getaddrinfo(addr, NULL, &aihints, &ai))) { errx(1, "getaddrinfo: %s", gai_strerror(r)); } if ((r=getnameinfo(ai->ai_addr, ai->ai_addrlen, buf, sizeof(buf), NULL, 0, NI_NUMERICHOST))) { errx(1, "getnameinfo: %s", gai_strerror(r)); } } else { /* unix socket, virtio device or fd transport */ snprintf(buf, sizeof(buf), "%s", addr); } if(dryrun) { fprintf(stderr, "mount -t 9p -o %s %s %s\n", opts, buf, mountpt); } else if (mount(buf, mountpt, "9p", mountflags, (void*)opts)) { err(1, "mount"); } return 0; } sqweek-9mount-2e6aebd/9umount.c000066400000000000000000000054111521322365200166020ustar00rootroot00000000000000/* © 2008 sqweek * See COPYING for details. */ #include #include #include #include #include #include #include #include #include char* canonpath(char *rel) { char *abs, *cp, *prev; if (rel[0] == '/') { if (!(abs=malloc(strlen(rel) + 2))) { return NULL; } abs[0] = '\0'; strcat(abs, rel); strcat(abs, "/"); } else { char *cwd = NULL; if (!(cwd=getcwd(NULL, 0))) { return NULL; } if (!(abs=malloc(strlen(cwd) + 1 + strlen(rel) + 2))) { free(cwd); return NULL; } abs[0] = '\0'; strcat(abs, cwd); strcat(abs, "/"); strcat(abs, rel); strcat(abs, "/"); free(cwd); } /* abs is now an absolute path which begins and ends with a slash */ prev = abs; while ((cp=strchr(prev+1, '/'))) { if (cp == prev+1) { /* remove consecutive slashes */ memmove(prev, cp, strlen(cp)+1); } else if (strncmp(prev, "/./", cp-prev+1) == 0) { memmove(prev, cp, strlen(cp)+1); } else if (strncmp(prev, "/../", cp-prev+1) == 0) { char *parent; if (prev == abs) { parent = abs; /* just eat .. in root dir */ } else { for (parent=prev-1; *parent != '/'; --parent); } memmove(parent, cp, strlen(cp)+1); prev = parent; } else { prev = cp; } } abs[strlen(abs)-1] = '\0'; /* remove trailing slash */ return abs; } int indir(struct mntent *mnt, char *dir) { return (strncmp(mnt->mnt_dir, dir, strlen(dir)) == 0); } int mountedby(struct mntent *mnt, char *username) { char *s, *cp; if (!(s=strdup(mnt->mnt_opts))) { errx(1, "out of memory"); } for (cp=strtok(s, ","); cp; cp=strtok(NULL, ",")) { if (strncmp(cp, "name=", 5) == 0) { int eq = (strcmp(cp+5, username) == 0); free(s); return eq; } } free(s); return 0; } int main(int argc, char **argv) { int ret = 0; char *path; FILE *fp; struct mntent *mnt; struct passwd *pw; if (!(pw=getpwuid(getuid()))) { err(1, "who are you?? getpwuid failed"); } while (*++argv) { if (!(path=canonpath(*argv))) { warn("%s: canonpath", *argv); continue; } if (!(fp=fopen("/proc/mounts", "r"))) { err(1, "couldn't open /proc/mounts"); } while ((mnt=getmntent(fp))) { if (strcmp(path, mnt->mnt_dir) == 0) { int inhomedir; inhomedir = indir(mnt, pw->pw_dir); if (!inhomedir && strcmp(mnt->mnt_type, "9p") != 0) { warnx("%s: refusing to unmount non-9p fs", path); ret = 1; } else if (!inhomedir && !mountedby(mnt, pw->pw_name)) { warnx("%s: not mounted by you", path); ret = 1; } else if (umount(mnt->mnt_dir)) { warn("umount %s", mnt->mnt_dir); ret = 1; } goto done; } } warnx("%s not found in /proc/mounts", path); ret = 1; done: free(path); fclose(fp); } return ret; } sqweek-9mount-2e6aebd/COPYING000066400000000000000000000013501521322365200160470ustar00rootroot00000000000000Copyright (c) 2008, sqweek Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. sqweek-9mount-2e6aebd/ChangeLog000066400000000000000000000027651521322365200166010ustar00rootroot000000000000002026-06-13 sqweek * 1.5 release * 9mount: security fix: make nosuid/nodev options actually effective * 9mount: nosuid is now a default option unless -0 (root-only) is given * 9mount: fix -x being a no-op for root 2017-04-12 Joakim Sindholt * 9mount: add support for cache=fscache and cache=mmap * 9mount: restrict msize to 8192 bytes for non-root * 9mount: force "nosuid" in options for non-root * 9mount: verify the mounting user has permission to open socket 2016-10-18 Michael Gehring * 9mount: fix typo in trans=fd mount options 2014-04-10 Michael Gehring * 9mount: avoid ipv6 addresses since v9fs only supports ipv4 2008-11-17 sqweek * 9mount: unrecognised arguments now generate an error instead of being silently ignored 2008-07-22 sqweek * 1.3 release * 9mount: support for custom uname, cache, msize, virtio, fd transport, access modes, -d more useful * 9umount: allow unmounting of even non-9p filesystems in your home dir * introduced some test cases * added ISC license 2008-06-20 sqweek * 1.2 release * 9mount: support for linux kernels >= 2.6.24 * 9umount: allow unmounting of anything in your home dir 2007-10-16 sqweek * 1.1 release * 9bind: new SUID utility for bind mounting * Makefile: actually install the man page * 9mount: check that the initial calloc actually succeeds 2007-09-08 sqweek * 1.0 release sqweek-9mount-2e6aebd/Makefile000066400000000000000000000010431521322365200164530ustar00rootroot00000000000000prefix=/usr/local bindir=$(prefix)/bin mandir=$(prefix)/share/man CFLAGS=-Wall all: 9mount 9umount 9bind 9mount: 9mount.c 9umount: 9umount.c 9bind: 9bind.c %: %.c $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ -o $@ clean: rm -f 9mount 9umount 9bind install: all mkdir -p $(bindir) cp -f 9mount $(bindir) cp -f 9umount $(bindir) cp -f 9bind $(bindir) chown root:users $(bindir)/9mount $(bindir)/9umount $(bindir)/9bind chmod 4755 $(bindir)/9mount $(bindir)/9umount $(bindir)/9bind mkdir -p $(mandir)/man1 cp -f 9mount.1 $(mandir)/man1 sqweek-9mount-2e6aebd/test.sh000066400000000000000000000041121521322365200163260ustar00rootroot00000000000000#!/bin/sh expect() { addr=$1 opts=$(echo $2 |tr , '\n' |sort |tr '\n' , |sed 's/,$//') mtpt=$(eval 'echo $'$#) expected="mount -t 9p -o $opts $addr $mtpt" shift; shift actual=$(9mount -n "$@" 2>&1) aopts=$(echo $actual |sed 's/.*-o \([^ ]*\) .*/\1/' |tr , '\n' |sort |tr '\n' , |sed 's/,$//') actual=$(echo $actual |sed 's/-o [^ ]*/-o '"$aopts"'/') if [ "$expected" != "$actual" ]; then echo ' '9mount "$@" echo $expected' #expected' echo $actual' #actual' exit 1 fi } mtpt=/tmp/9mount mkdir -p $mtpt trap 'rmdir $mtpt' EXIT dfltopts="name=$USER,uname=$USER,noextend,nodev,nosuid" expect 127.0.0.1 $dfltopts,tcp,trans=tcp,port=888 tcp!localhost!888 $mtpt mkdir -p /tmp/ns.$USER.:0 nc -lkU /tmp/ns.$USER.:0/foo & expect /tmp/ns.$USER.:0/foo $dfltopts,unix,trans=unix unix!/tmp/ns.$USER.:0/foo $mtpt kill $! expect /dev/bar $dfltopts,virtio,trans=virtio virtio!/dev/bar $mtpt expect nodev $dfltopts,fd,trans=fd,rfdno=0,wfdno=1 - $mtpt dfltopts="tcp,trans=tcp,$dfltopts" testflag() { opts=$1 shift expect 127.0.0.1 $dfltopts,$opts "$@" tcp!localhost $mtpt } # some behaviour differs when running as root if [ $(id -u) -eq 0 ]; then dashv_expect=$(echo $dfltopts | sed 's/,nodev//') msize_expect="msize=16384" else dashv_expect=$dfltopts msize_expect="msize=8192" fi testflag "uid=$(id -u),gid=$(id -g),dfltuid=$(id -u),dfltgid=$(id -g)" -i testflag "access=any" -s expect 127.0.0.1 $(echo $dfltopts |sed 's/,noextend//') -u tcp!localhost $mtpt expect 127.0.0.1 $dashv_expect -v tcp!localhost $mtpt testflag "access=$(id -u)" -x testflag "aname=abcdef" -a abcdef testflag "cache=loose" -c loose testflag "debug=0x0130" -d fcall,conv,mux testflag $msize_expect -m 16384 shouldfail() { output=$(9mount -n "$@" 2>&1) && { echo ' '9mount "$@" echo $output' #should have failed!' } } shouldfail -a tcp!localhost $mtpt shouldfail -a main/active,bwahaha tcp!localhost $mtpt shouldfail -d lol tcp!localhost $mtpt shouldfail -m z tcp!localhost $mtpt shouldfail udp!localhost $mtpt shouldfail unix!/tmp/9mount!qux $mtpt shouldfail virtio!/dev/chan!bar $mtpt shouldfail tcp!localhost!564!foo $mtpt