1/*	$OpenBSD: rmt.c,v 1.23 2019/06/28 13:32:50 deraadt Exp $	*/
2
3/*
4 * Copyright (c) 1983 Regents of the University of California.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32/*
33 * rmt
34 */
35#include <sys/types.h>
36#include <sys/socket.h>
37#include <sys/stat.h>
38#include <sys/ioctl.h>
39#include <sys/mtio.h>
40
41#include <unistd.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <err.h>
45#include <errno.h>
46#include <fcntl.h>
47#include <string.h>
48#include <limits.h>
49
50int	tape = -1;
51
52char	*record;
53int	maxrecsize = -1;
54
55#define	STRSIZE	64
56char	device[PATH_MAX];
57char	lastdevice[PATH_MAX] = "";
58char	count[STRSIZE], mode[STRSIZE], pos[STRSIZE], op[STRSIZE];
59
60char	resp[BUFSIZ];
61
62FILE	*debug;
63#define	DEBUG(f)	if (debug) fprintf(debug, f)
64#define	DEBUG1(f,a)	if (debug) fprintf(debug, f, a)
65#define	DEBUG2(f,a1,a2)	if (debug) fprintf(debug, f, a1, a2)
66
67char		*checkbuf(char *, int);
68void		getstring(char *, int);
69void		error(int);
70__dead void	usage(void);
71
72int
73main(int argc, char *argv[])
74{
75	off_t orval;
76	int rval;
77	char c;
78	int n, i, cc;
79	int ch, rflag = 0, wflag = 0;
80	int f, acc;
81	mode_t m;
82	char *dir = NULL;
83	char *devp;
84	size_t dirlen;
85
86	if (pledge("stdio rpath wpath cpath inet", NULL) == -1)
87		err(1, "pledge");
88
89	while ((ch = getopt(argc, argv, "d:rw")) != -1) {
90		switch (ch) {
91		case 'd':
92			dir = optarg;
93			if (*dir != '/')
94				errx(1, "directory must be absolute");
95			break;
96		case 'r':
97			rflag = 1;
98			break;
99		case 'w':
100			wflag = 1;
101			break;
102		default:
103			usage();
104			/* NOTREACHED */
105		}
106	}
107	argc -= optind;
108	argv += optind;
109
110	if (rflag && wflag)
111		usage();
112
113	if (argc > 0) {
114		debug = fopen(*argv, "w");
115		if (debug == 0)
116			err(1, "cannot open debug file");
117		setvbuf(debug, NULL, _IONBF, 0);
118	}
119
120	if (dir) {
121		if (chdir(dir) != 0)
122			err(1, "chdir");
123		dirlen = strlen(dir);
124	}
125
126top:
127	errno = 0;
128	rval = 0;
129	if (read(STDIN_FILENO, &c, 1) != 1)
130		exit(0);
131	switch (c) {
132
133	case 'O':
134		if (tape >= 0)
135			(void) close(tape);
136		getstring(device, sizeof(device));
137		getstring(mode, sizeof(mode));
138		DEBUG2("rmtd: O %s %s\n", device, mode);
139
140		devp = device;
141		f = atoi(mode);
142		m = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
143		acc = f & O_ACCMODE;
144		if (dir) {
145			/* Strip away valid directory prefix. */
146			if (strncmp(dir, devp, dirlen) == 0 &&
147			    (devp[dirlen - 1] == '/' ||
148			     devp[dirlen] == '/')) {
149			     devp += dirlen;
150			     while (*devp == '/')
151				devp++;
152			}
153			/* Don't allow directory traversal. */
154			if (strchr(devp, '/')) {
155				errno = EACCES;
156				goto ioerror;
157			}
158			f |= O_NOFOLLOW;
159		}
160		if (rflag) {
161			/*
162			 * Only allow readonly open and ignore file
163			 * creation requests.
164			 */
165			if (acc != O_RDONLY) {
166				errno = EPERM;
167				goto ioerror;
168			}
169			f &= ~O_CREAT;
170		} else if (wflag) {
171			/*
172			 * Require, and force creation of, a nonexistent file,
173			 * unless we are reopening the last opened file again,
174			 * in which case it is opened read-only.
175			 */
176			if (strcmp(devp, lastdevice) != 0) {
177				/*
178				 * Disallow read-only open since that would
179				 * only result in an empty file.
180				 */
181				if (acc == O_RDONLY) {
182					errno = EPERM;
183					goto ioerror;
184				}
185				f |= O_CREAT | O_EXCL;
186			} else {
187				acc = O_RDONLY;
188			}
189			/* Create readonly file */
190			m = S_IRUSR|S_IRGRP|S_IROTH;
191		}
192		/* Apply new access mode. */
193		f = (f & ~O_ACCMODE) | acc;
194
195		tape = open(devp, f, m);
196		if (tape == -1)
197			goto ioerror;
198		(void)strlcpy(lastdevice, devp, sizeof(lastdevice));
199		goto respond;
200
201	case 'C':
202		DEBUG("rmtd: C\n");
203		getstring(device, sizeof(device));	/* discard */
204		if (close(tape) == -1)
205			goto ioerror;
206		tape = -1;
207		goto respond;
208
209	case 'L':
210		getstring(count, sizeof(count));
211		getstring(pos, sizeof(pos));
212		DEBUG2("rmtd: L %s %s\n", count, pos);
213		orval = lseek(tape, strtoll(count, NULL, 0), atoi(pos));
214		if (orval == -1)
215			goto ioerror;
216		goto respond;
217
218	case 'W':
219		getstring(count, sizeof(count));
220		n = atoi(count);
221		DEBUG1("rmtd: W %s\n", count);
222		record = checkbuf(record, n);
223		for (i = 0; i < n; i += cc) {
224			cc = read(STDIN_FILENO, &record[i], n - i);
225			if (cc <= 0) {
226				DEBUG("rmtd: premature eof\n");
227				exit(2);
228			}
229		}
230		rval = write(tape, record, n);
231		if (rval == -1)
232			goto ioerror;
233		goto respond;
234
235	case 'R':
236		getstring(count, sizeof(count));
237		DEBUG1("rmtd: R %s\n", count);
238		n = atoi(count);
239		record = checkbuf(record, n);
240		rval = read(tape, record, n);
241		if (rval == -1)
242			goto ioerror;
243		(void) snprintf(resp, sizeof resp, "A%d\n", rval);
244		(void) write(STDOUT_FILENO, resp, strlen(resp));
245		(void) write(STDOUT_FILENO, record, rval);
246		goto top;
247
248	case 'I':
249		getstring(op, sizeof(op));
250		getstring(count, sizeof(count));
251		DEBUG2("rmtd: I %s %s\n", op, count);
252		{ struct mtop mtop;
253		  mtop.mt_op = atoi(op);
254		  mtop.mt_count = atoi(count);
255		  if (ioctl(tape, MTIOCTOP, (char *)&mtop) == -1)
256			goto ioerror;
257		  rval = mtop.mt_count;
258		}
259		goto respond;
260
261	case 'S':		/* status */
262		DEBUG("rmtd: S\n");
263		{ struct mtget mtget;
264		  if (ioctl(tape, MTIOCGET, (char *)&mtget) == -1)
265			goto ioerror;
266		  rval = sizeof (mtget);
267		  (void) snprintf(resp, sizeof resp, "A%d\n", rval);
268		  (void) write(STDOUT_FILENO, resp, strlen(resp));
269		  (void) write(STDOUT_FILENO, (char *)&mtget, sizeof (mtget));
270		  goto top;
271		}
272
273	default:
274		DEBUG1("rmtd: garbage command %c\n", c);
275		exit(3);
276	}
277respond:
278	DEBUG1("rmtd: A %d\n", rval);
279	(void) snprintf(resp, sizeof resp, "A%d\n", rval);
280	(void) write(STDOUT_FILENO, resp, strlen(resp));
281	goto top;
282ioerror:
283	error(errno);
284	goto top;
285}
286
287void
288getstring(char *bp, int size)
289{
290	char *cp = bp;
291	char *ep = bp + size - 1;
292
293	do {
294		if (read(STDIN_FILENO, cp, 1) != 1)
295			exit(0);
296	} while (*cp != '\n' && ++cp < ep);
297	*cp = '\0';
298}
299
300char *
301checkbuf(char *record, int size)
302{
303	if (size <= maxrecsize)
304		return (record);
305	if (record != 0)
306		free(record);
307	record = malloc(size);
308	if (record == 0) {
309		DEBUG("rmtd: cannot allocate buffer space\n");
310		exit(4);
311	}
312	maxrecsize = size;
313	while (size > 1024 &&
314	    setsockopt(0, SOL_SOCKET, SO_RCVBUF, &size, sizeof (size)) == -1)
315		size -= 1024;
316	return (record);
317}
318
319void
320error(int num)
321{
322
323	DEBUG2("rmtd: E %d (%s)\n", num, strerror(num));
324	(void) snprintf(resp, sizeof (resp), "E%d\n%s\n", num, strerror(num));
325	(void) write(STDOUT_FILENO, resp, strlen(resp));
326}
327
328__dead void
329usage(void)
330{
331	extern char *__progname;
332
333	(void)fprintf(stderr, "usage: %s [-r | -w] [-d directory]\n",
334	    __progname);
335	exit(1);
336}
337