I'm going wide with this diff I've been pushing for quite some time now. Is *anyone* but me using rdump(8) + rmt(8)?
*If you are currently using rdump/rrestore + rmt, I urge you to test this diff to make sure it causes no regression. It shouldn't, but you've been warned. So, anyway, this diff allows running a restricted rmt(8), in my case for remote dumps over ssh, a.k.a rdump(8). For restricting rmt(8) when dumping/restoring to/from a remote machine: -d <directory> confines rmt to operate within a single directory. -r enforces read-only mode. -w enforces write-only mode. With this, rmt could be used with the following (simplified) .ssh/authorized_keys entries command="/etc/rmt -wd /dumps/host/foo" ssh-ed25519 ...dumpkey... command="/etc/rmt -rd /dumps/host/foo" ssh-ed25519 ...restorekey... This has the major advantage that a remote user cannot ever destroy or manipulate former backups. A bit more detail is in the man page. OK? /Alexander Index: rmt.8 =================================================================== RCS file: /cvs/src/usr.sbin/rmt/rmt.8,v retrieving revision 1.12 diff -u -p -r1.12 rmt.8 --- rmt.8 23 Jul 2011 15:40:13 -0000 1.12 +++ rmt.8 9 Sep 2015 22:57:41 -0000 @@ -36,18 +36,38 @@ .Nm rmt .Nd remote magtape protocol module .Sh SYNOPSIS -.Nm rmt +.Nm +.Op Fl r | w +.Op Fl d Ar directory .Sh DESCRIPTION .Nm is a program used by the remote dump and restore programs -in manipulating a magnetic tape drive through an interprocess -communication connection. +through an interprocess communication connection. +Traditionally it is used for manipulating a magnetic tape drive but it may +be used for regular file access as well. .Nm is normally started up with an .Xr rcmd 3 or .Xr rcmdsh 3 call. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl d Ar directory +Confine file access to +.Ar directory . +Forward slashes in filenames are disallowed and symlinks are not followed. +.It Fl r +Read-only mode, suitable for use with +.Xr rrestore 8 . +.It Fl w +File write mode, suitable for use with +.Xr rdump 8 +for dumping to regular files. +Creates missing files and refuses to open existing ones. +The file permission bits are set to readonly. +.El .Pp The .Nm Index: rmt.c =================================================================== RCS file: /cvs/src/usr.sbin/rmt/rmt.c,v retrieving revision 1.15 diff -u -p -r1.15 rmt.c --- rmt.c 16 Jan 2015 06:40:20 -0000 1.15 +++ rmt.c 9 Sep 2015 22:57:41 -0000 @@ -41,6 +41,7 @@ #include <unistd.h> #include <stdio.h> #include <stdlib.h> +#include <err.h> #include <errno.h> #include <string.h> #include <limits.h> @@ -52,6 +53,7 @@ int maxrecsize = -1; #define STRSIZE 64 char device[PATH_MAX]; +char lastdevice[PATH_MAX] = ""; char count[STRSIZE], mode[STRSIZE], pos[STRSIZE], op[STRSIZE]; char resp[BUFSIZ]; @@ -61,9 +63,10 @@ FILE *debug; #define DEBUG1(f,a) if (debug) fprintf(debug, f, a) #define DEBUG2(f,a1,a2) if (debug) fprintf(debug, f, a1, a2) -char *checkbuf(char *, int); -void getstring(char *, int); -void error(int); +char *checkbuf(char *, int); +void getstring(char *, int); +void error(int); +__dead void usage(void); int main(int argc, char *argv[]) @@ -72,14 +75,50 @@ main(int argc, char *argv[]) int rval; char c; int n, i, cc; + int ch, rflag = 0, wflag = 0; + int f, acc; + mode_t m; + char *dir = NULL; + char *devp; + size_t dirlen; + + while ((ch = getopt(argc, argv, "d:rw")) != -1) { + switch (ch) { + case 'd': + dir = optarg; + if (*dir != '/') + errx(1, "directory must be absolute"); + break; + case 'r': + rflag = 1; + break; + case 'w': + wflag = 1; + break; + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + + if (rflag && wflag) + usage(); - argc--, argv++; if (argc > 0) { debug = fopen(*argv, "w"); if (debug == 0) - exit(1); + err(1, "cannot open debug file"); (void) setbuf(debug, (char *)0); } + + if (dir) { + if (chdir(dir) != 0) + err(1, "chdir"); + dirlen = strlen(dir); + } + top: errno = 0; rval = 0; @@ -93,10 +132,66 @@ top: getstring(device, sizeof(device)); getstring(mode, sizeof(mode)); DEBUG2("rmtd: O %s %s\n", device, mode); - tape = open(device, atoi(mode), - S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); + + devp = device; + f = atoi(mode); + m = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH; + acc = f & O_ACCMODE; + if (dir) { + /* Strip away valid directory prefix. */ + if (strncmp(dir, devp, dirlen) == 0 && + (devp[dirlen - 1] == '/' || + devp[dirlen] == '/')) { + devp += dirlen; + while (*devp == '/') + devp++; + } + /* Don't allow directory traversal. */ + if (strchr(devp, '/')) { + errno = EACCES; + goto ioerror; + } + f |= O_NOFOLLOW; + } + if (rflag) { + /* + * Only allow readonly open and ignore file + * creation requests. + */ + if (acc != O_RDONLY) { + errno = EPERM; + goto ioerror; + } + f &= ~O_CREAT; + } else if (wflag) { + /* + * Require, and force creation of, a nonexistant file, + * unless we are reopening the last opened file again, + * in which case it is opened read-only. + */ + if (strcmp(devp, lastdevice) != 0) { + /* + * Disallow read-only open since that would + * only result in an empty file. + */ + if (acc == O_RDONLY) { + errno = EPERM; + goto ioerror; + } + f |= O_CREAT | O_EXCL; + } else { + acc = O_RDONLY; + } + /* Create readonly file */ + m = S_IRUSR|S_IRGRP|S_IROTH; + } + /* Apply new access mode. */ + f = (f & ~O_ACCMODE) | acc; + + tape = open(device, f, m); if (tape == -1) goto ioerror; + (void)strlcpy(lastdevice, devp, sizeof(lastdevice)); goto respond; case 'C': @@ -224,4 +319,14 @@ error(int num) DEBUG2("rmtd: E %d (%s)\n", num, strerror(num)); (void) snprintf(resp, sizeof (resp), "E%d\n%s\n", num, strerror(num)); (void) write(STDOUT_FILENO, resp, strlen(resp)); +} + +__dead void +usage(void) +{ + extern char *__progname; + + (void)fprintf(stderr, "usage: %s [-r | -w] [-d directory]\n", + __progname); + exit(1); }