xref: /openbsd-src/usr.sbin/rmt/rmt.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: rmt.c,v 1.20 2016/08/14 18:34:48 guenther 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/file.h>
38 #include <sys/stat.h>
39 #include <sys/ioctl.h>
40 #include <sys/mtio.h>
41 #include <unistd.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <err.h>
45 #include <errno.h>
46 #include <string.h>
47 #include <limits.h>
48 
49 int	tape = -1;
50 
51 char	*record;
52 int	maxrecsize = -1;
53 
54 #define	STRSIZE	64
55 char	device[PATH_MAX];
56 char	lastdevice[PATH_MAX] = "";
57 char	count[STRSIZE], mode[STRSIZE], pos[STRSIZE], op[STRSIZE];
58 
59 char	resp[BUFSIZ];
60 
61 FILE	*debug;
62 #define	DEBUG(f)	if (debug) fprintf(debug, f)
63 #define	DEBUG1(f,a)	if (debug) fprintf(debug, f, a)
64 #define	DEBUG2(f,a1,a2)	if (debug) fprintf(debug, f, a1, a2)
65 
66 char		*checkbuf(char *, int);
67 void		getstring(char *, int);
68 void		error(int);
69 __dead void	usage(void);
70 
71 int
72 main(int argc, char *argv[])
73 {
74 	off_t orval;
75 	int rval;
76 	char c;
77 	int n, i, cc;
78 	int ch, rflag = 0, wflag = 0;
79 	int f, acc;
80 	mode_t m;
81 	char *dir = NULL;
82 	char *devp;
83 	size_t dirlen;
84 
85 	if (pledge("stdio rpath wpath cpath inet", NULL) == -1)
86 		err(1, "pledge");
87 
88 	while ((ch = getopt(argc, argv, "d:rw")) != -1) {
89 		switch (ch) {
90 		case 'd':
91 			dir = optarg;
92 			if (*dir != '/')
93 				errx(1, "directory must be absolute");
94 			break;
95 		case 'r':
96 			rflag = 1;
97 			break;
98 		case 'w':
99 			wflag = 1;
100 			break;
101 		default:
102 			usage();
103 			/* NOTREACHED */
104 		}
105 	}
106 	argc -= optind;
107 	argv += optind;
108 
109 	if (rflag && wflag)
110 		usage();
111 
112 	if (argc > 0) {
113 		debug = fopen(*argv, "w");
114 		if (debug == 0)
115 			err(1, "cannot open debug file");
116 		setvbuf(debug, NULL, _IONBF, 0);
117 	}
118 
119 	if (dir) {
120 		if (chdir(dir) != 0)
121 			err(1, "chdir");
122 		dirlen = strlen(dir);
123 	}
124 
125 top:
126 	errno = 0;
127 	rval = 0;
128 	if (read(STDIN_FILENO, &c, 1) != 1)
129 		exit(0);
130 	switch (c) {
131 
132 	case 'O':
133 		if (tape >= 0)
134 			(void) close(tape);
135 		getstring(device, sizeof(device));
136 		getstring(mode, sizeof(mode));
137 		DEBUG2("rmtd: O %s %s\n", device, mode);
138 
139 		devp = device;
140 		f = atoi(mode);
141 		m = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
142 		acc = f & O_ACCMODE;
143 		if (dir) {
144 			/* Strip away valid directory prefix. */
145 			if (strncmp(dir, devp, dirlen) == 0 &&
146 			    (devp[dirlen - 1] == '/' ||
147 			     devp[dirlen] == '/')) {
148 			     devp += dirlen;
149 			     while (*devp == '/')
150 				devp++;
151 			}
152 			/* Don't allow directory traversal. */
153 			if (strchr(devp, '/')) {
154 				errno = EACCES;
155 				goto ioerror;
156 			}
157 			f |= O_NOFOLLOW;
158 		}
159 		if (rflag) {
160 			/*
161 			 * Only allow readonly open and ignore file
162 			 * creation requests.
163 			 */
164 			if (acc != O_RDONLY) {
165 				errno = EPERM;
166 				goto ioerror;
167 			}
168 			f &= ~O_CREAT;
169 		} else if (wflag) {
170 			/*
171 			 * Require, and force creation of, a nonexistant file,
172 			 * unless we are reopening the last opened file again,
173 			 * in which case it is opened read-only.
174 			 */
175 			if (strcmp(devp, lastdevice) != 0) {
176 				/*
177 				 * Disallow read-only open since that would
178 				 * only result in an empty file.
179 				 */
180 				if (acc == O_RDONLY) {
181 					errno = EPERM;
182 					goto ioerror;
183 				}
184 				f |= O_CREAT | O_EXCL;
185 			} else {
186 				acc = O_RDONLY;
187 			}
188 			/* Create readonly file */
189 			m = S_IRUSR|S_IRGRP|S_IROTH;
190 		}
191 		/* Apply new access mode. */
192 		f = (f & ~O_ACCMODE) | acc;
193 
194 		tape = open(devp, f, m);
195 		if (tape == -1)
196 			goto ioerror;
197 		(void)strlcpy(lastdevice, devp, sizeof(lastdevice));
198 		goto respond;
199 
200 	case 'C':
201 		DEBUG("rmtd: C\n");
202 		getstring(device, sizeof(device));	/* discard */
203 		if (close(tape) == -1)
204 			goto ioerror;
205 		tape = -1;
206 		goto respond;
207 
208 	case 'L':
209 		getstring(count, sizeof(count));
210 		getstring(pos, sizeof(pos));
211 		DEBUG2("rmtd: L %s %s\n", count, pos);
212 		orval = lseek(tape, strtoll(count, NULL, 0), atoi(pos));
213 		if (orval == -1)
214 			goto ioerror;
215 		goto respond;
216 
217 	case 'W':
218 		getstring(count, sizeof(count));
219 		n = atoi(count);
220 		DEBUG1("rmtd: W %s\n", count);
221 		record = checkbuf(record, n);
222 		for (i = 0; i < n; i += cc) {
223 			cc = read(STDIN_FILENO, &record[i], n - i);
224 			if (cc <= 0) {
225 				DEBUG("rmtd: premature eof\n");
226 				exit(2);
227 			}
228 		}
229 		rval = write(tape, record, n);
230 		if (rval < 0)
231 			goto ioerror;
232 		goto respond;
233 
234 	case 'R':
235 		getstring(count, sizeof(count));
236 		DEBUG1("rmtd: R %s\n", count);
237 		n = atoi(count);
238 		record = checkbuf(record, n);
239 		rval = read(tape, record, n);
240 		if (rval < 0)
241 			goto ioerror;
242 		(void) snprintf(resp, sizeof resp, "A%d\n", rval);
243 		(void) write(STDOUT_FILENO, resp, strlen(resp));
244 		(void) write(STDOUT_FILENO, record, rval);
245 		goto top;
246 
247 	case 'I':
248 		getstring(op, sizeof(op));
249 		getstring(count, sizeof(count));
250 		DEBUG2("rmtd: I %s %s\n", op, count);
251 		{ struct mtop mtop;
252 		  mtop.mt_op = atoi(op);
253 		  mtop.mt_count = atoi(count);
254 		  if (ioctl(tape, MTIOCTOP, (char *)&mtop) == -1)
255 			goto ioerror;
256 		  rval = mtop.mt_count;
257 		}
258 		goto respond;
259 
260 	case 'S':		/* status */
261 		DEBUG("rmtd: S\n");
262 		{ struct mtget mtget;
263 		  if (ioctl(tape, MTIOCGET, (char *)&mtget) == -1)
264 			goto ioerror;
265 		  rval = sizeof (mtget);
266 		  (void) snprintf(resp, sizeof resp, "A%d\n", rval);
267 		  (void) write(STDOUT_FILENO, resp, strlen(resp));
268 		  (void) write(STDOUT_FILENO, (char *)&mtget, sizeof (mtget));
269 		  goto top;
270 		}
271 
272 	default:
273 		DEBUG1("rmtd: garbage command %c\n", c);
274 		exit(3);
275 	}
276 respond:
277 	DEBUG1("rmtd: A %d\n", rval);
278 	(void) snprintf(resp, sizeof resp, "A%d\n", rval);
279 	(void) write(STDOUT_FILENO, resp, strlen(resp));
280 	goto top;
281 ioerror:
282 	error(errno);
283 	goto top;
284 }
285 
286 void
287 getstring(char *bp, int size)
288 {
289 	char *cp = bp;
290 	char *ep = bp + size - 1;
291 
292 	do {
293 		if (read(STDIN_FILENO, cp, 1) != 1)
294 			exit(0);
295 	} while (*cp != '\n' && ++cp < ep);
296 	*cp = '\0';
297 }
298 
299 char *
300 checkbuf(char *record, int size)
301 {
302 	if (size <= maxrecsize)
303 		return (record);
304 	if (record != 0)
305 		free(record);
306 	record = malloc(size);
307 	if (record == 0) {
308 		DEBUG("rmtd: cannot allocate buffer space\n");
309 		exit(4);
310 	}
311 	maxrecsize = size;
312 	while (size > 1024 &&
313 	    setsockopt(0, SOL_SOCKET, SO_RCVBUF, &size, sizeof (size)) == -1)
314 		size -= 1024;
315 	return (record);
316 }
317 
318 void
319 error(int num)
320 {
321 
322 	DEBUG2("rmtd: E %d (%s)\n", num, strerror(num));
323 	(void) snprintf(resp, sizeof (resp), "E%d\n%s\n", num, strerror(num));
324 	(void) write(STDOUT_FILENO, resp, strlen(resp));
325 }
326 
327 __dead void
328 usage(void)
329 {
330 	extern char *__progname;
331 
332 	(void)fprintf(stderr, "usage: %s [-r | -w] [-d directory]\n",
333 	    __progname);
334 	exit(1);
335 }
336