xref: /openbsd/usr.sbin/rmt/rmt.c (revision d89ec533)
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 
50 int	tape = -1;
51 
52 char	*record;
53 int	maxrecsize = -1;
54 
55 #define	STRSIZE	64
56 char	device[PATH_MAX];
57 char	lastdevice[PATH_MAX] = "";
58 char	count[STRSIZE], mode[STRSIZE], pos[STRSIZE], op[STRSIZE];
59 
60 char	resp[BUFSIZ];
61 
62 FILE	*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 
67 char		*checkbuf(char *, int);
68 void		getstring(char *, int);
69 void		error(int);
70 __dead void	usage(void);
71 
72 int
73 main(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 
126 top:
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 	}
277 respond:
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;
282 ioerror:
283 	error(errno);
284 	goto top;
285 }
286 
287 void
288 getstring(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 
300 char *
301 checkbuf(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 
319 void
320 error(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
329 usage(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