xref: /freebsd/usr.bin/bintrans/uudecode.c (revision d94d07d58141dcff48f01c6b3e5a31de9d7a7938)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1983, 1993
5  *	The Regents of the University of California.  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 #if 0
33 #ifndef lint
34 static const char copyright[] =
35 "@(#) Copyright (c) 1983, 1993\n\
36 	The Regents of the University of California.  All rights reserved.\n";
37 #endif /* not lint */
38 
39 #ifndef lint
40 static char sccsid[] = "@(#)uudecode.c	8.2 (Berkeley) 4/2/94";
41 #endif /* not lint */
42 #endif
43 #include <sys/cdefs.h>
44 /*
45  * uudecode [file ...]
46  *
47  * create the specified file, decoding as you go.
48  * used with uuencode.
49  */
50 #include <sys/param.h>
51 #include <sys/socket.h>
52 #include <sys/stat.h>
53 
54 #include <netinet/in.h>
55 
56 #include <ctype.h>
57 #include <err.h>
58 #include <errno.h>
59 #include <fcntl.h>
60 #include <libgen.h>
61 #include <pwd.h>
62 #include <resolv.h>
63 #include <stdbool.h>
64 #include <stdio.h>
65 #include <stdlib.h>
66 #include <string.h>
67 #include <unistd.h>
68 
69 extern int main_decode(int, char *[]);
70 extern int main_base64_decode(const char *);
71 
72 static const char *infile, *outfile;
73 static FILE *infp, *outfp;
74 static bool base64, cflag, iflag, oflag, pflag, rflag, sflag;
75 
76 static void	usage(void);
77 static int	decode(void);
78 static int	decode2(void);
79 static int	uu_decode(void);
80 static int	base64_decode(void);
81 
82 int
83 main_base64_decode(const char *in)
84 {
85 	base64 = 1;
86 	rflag = 1;
87 	if (in != NULL) {
88 		infile = in;
89 		infp = fopen(infile, "r");
90 		if (infp == NULL)
91 			err(1, "%s", in);
92 	} else {
93 		infile = "stdin";
94 		infp = stdin;
95 	}
96 	exit(decode());
97 }
98 
99 int
100 main_decode(int argc, char *argv[])
101 {
102 	int rval, ch;
103 
104 	if (strcmp(basename(argv[0]), "b64decode") == 0)
105 		base64 = true;
106 
107 	while ((ch = getopt(argc, argv, "cimo:prs")) != -1) {
108 		switch (ch) {
109 		case 'c':
110 			if (oflag || rflag)
111 				usage();
112 			cflag = true; /* multiple uudecode'd files */
113 			break;
114 		case 'i':
115 			iflag = true; /* ask before override files */
116 			break;
117 		case 'm':
118 			base64 = true;
119 			break;
120 		case 'o':
121 			if (cflag || pflag || rflag || sflag)
122 				usage();
123 			oflag = true; /* output to the specified file */
124 			sflag = true; /* do not strip pathnames for output */
125 			outfile = optarg; /* set the output filename */
126 			break;
127 		case 'p':
128 			if (oflag)
129 				usage();
130 			pflag = true; /* print output to stdout */
131 			break;
132 		case 'r':
133 			if (cflag || oflag)
134 				usage();
135 			rflag = true; /* decode raw data */
136 			break;
137 		case 's':
138 			if (oflag)
139 				usage();
140 			sflag = true; /* do not strip pathnames for output */
141 			break;
142 		default:
143 			usage();
144 		}
145 	}
146 	argc -= optind;
147 	argv += optind;
148 
149 	if (*argv != NULL) {
150 		rval = 0;
151 		do {
152 			infp = fopen(infile = *argv, "r");
153 			if (infp == NULL) {
154 				warn("%s", *argv);
155 				rval = 1;
156 				continue;
157 			}
158 			rval |= decode();
159 			fclose(infp);
160 		} while (*++argv);
161 	} else {
162 		infile = "stdin";
163 		infp = stdin;
164 		rval = decode();
165 	}
166 	exit(rval);
167 }
168 
169 static int
170 decode(void)
171 {
172 	int r, v;
173 
174 	if (rflag) {
175 		/* relaxed alternative to decode2() */
176 		outfile = "/dev/stdout";
177 		outfp = stdout;
178 		if (base64)
179 			return (base64_decode());
180 		else
181 			return (uu_decode());
182 	}
183 	v = decode2();
184 	if (v == EOF) {
185 		warnx("%s: missing or bad \"begin\" line", infile);
186 		return (1);
187 	}
188 	for (r = v; cflag; r |= v) {
189 		v = decode2();
190 		if (v == EOF)
191 			break;
192 	}
193 	return (r);
194 }
195 
196 static int
197 decode2(void)
198 {
199 	int flags, fd, mode;
200 	size_t n, m;
201 	char *p, *q;
202 	void *handle;
203 	struct passwd *pw;
204 	struct stat st;
205 	char buf[MAXPATHLEN + 1];
206 
207 	base64 = false;
208 	/* search for header line */
209 	for (;;) {
210 		if (fgets(buf, sizeof(buf), infp) == NULL)
211 			return (EOF);
212 		p = buf;
213 		if (strncmp(p, "begin-base64 ", 13) == 0) {
214 			base64 = true;
215 			p += 13;
216 		} else if (strncmp(p, "begin ", 6) == 0)
217 			p += 6;
218 		else
219 			continue;
220 		/* p points to mode */
221 		q = strchr(p, ' ');
222 		if (q == NULL)
223 			continue;
224 		*q++ = '\0';
225 		/* q points to filename */
226 		n = strlen(q);
227 		while (n > 0 && (q[n-1] == '\n' || q[n-1] == '\r'))
228 			q[--n] = '\0';
229 		/* found valid header? */
230 		if (n > 0)
231 			break;
232 	}
233 
234 	handle = setmode(p);
235 	if (handle == NULL) {
236 		warnx("%s: unable to parse file mode", infile);
237 		return (1);
238 	}
239 	mode = getmode(handle, 0) & 0666;
240 	free(handle);
241 
242 	if (sflag) {
243 		/* don't strip, so try ~user/file expansion */
244 		p = NULL;
245 		pw = NULL;
246 		if (*q == '~')
247 			p = strchr(q, '/');
248 		if (p != NULL) {
249 			*p = '\0';
250 			pw = getpwnam(q + 1);
251 			*p = '/';
252 		}
253 		if (pw != NULL) {
254 			n = strlen(pw->pw_dir);
255 			if (buf + n > p) {
256 				/* make room */
257 				m = strlen(p);
258 				if (sizeof(buf) < n + m) {
259 					warnx("%s: bad output filename",
260 					    infile);
261 					return (1);
262 				}
263 				p = memmove(buf + n, p, m);
264 			}
265 			q = memcpy(p - n, pw->pw_dir, n);
266 		}
267 	} else {
268 		/* strip down to leaf name */
269 		p = strrchr(q, '/');
270 		if (p != NULL)
271 			q = p + 1;
272 	}
273 	if (!oflag)
274 		outfile = q;
275 
276 	/* POSIX says "/dev/stdout" is a 'magic cookie' not a special file. */
277 	if (pflag || strcmp(outfile, "/dev/stdout") == 0)
278 		outfp = stdout;
279 	else {
280 		flags = O_WRONLY | O_CREAT | O_EXCL;
281 		if (lstat(outfile, &st) == 0) {
282 			if (iflag) {
283 				warnc(EEXIST, "%s: %s", infile, outfile);
284 				return (0);
285 			}
286 			switch (st.st_mode & S_IFMT) {
287 			case S_IFREG:
288 			case S_IFLNK:
289 				/* avoid symlink attacks */
290 				if (unlink(outfile) == 0 || errno == ENOENT)
291 					break;
292 				warn("%s: unlink %s", infile, outfile);
293 				return (1);
294 			case S_IFDIR:
295 				warnc(EISDIR, "%s: %s", infile, outfile);
296 				return (1);
297 			default:
298 				if (oflag) {
299 					/* trust command-line names */
300 					flags &= ~O_EXCL;
301 					break;
302 				}
303 				warnc(EEXIST, "%s: %s", infile, outfile);
304 				return (1);
305 			}
306 		} else if (errno != ENOENT) {
307 			warn("%s: %s", infile, outfile);
308 			return (1);
309 		}
310 		if ((fd = open(outfile, flags, mode)) < 0 ||
311 		    (outfp = fdopen(fd, "w")) == NULL) {
312 			warn("%s: %s", infile, outfile);
313 			return (1);
314 		}
315 	}
316 
317 	if (base64)
318 		return (base64_decode());
319 	else
320 		return (uu_decode());
321 }
322 
323 static int
324 get_line(char *buf, size_t size)
325 {
326 
327 	if (fgets(buf, size, infp) != NULL)
328 		return (2);
329 	if (rflag)
330 		return (0);
331 	warnx("%s: %s: short file", infile, outfile);
332 	return (1);
333 }
334 
335 static int
336 checkend(const char *ptr, const char *end, const char *msg)
337 {
338 	size_t n;
339 
340 	n = strlen(end);
341 	if (strncmp(ptr, end, n) != 0 ||
342 	    strspn(ptr + n, " \t\r\n") != strlen(ptr + n)) {
343 		warnx("%s: %s: %s", infile, outfile, msg);
344 		return (1);
345 	}
346 	if (fclose(outfp) != 0) {
347 		warn("%s: %s", infile, outfile);
348 		return (1);
349 	}
350 	return (0);
351 }
352 
353 static int
354 uu_decode(void)
355 {
356 	int i, ch;
357 	char *p;
358 	char buf[MAXPATHLEN+1];
359 
360 	/* for each input line */
361 	for (;;) {
362 		switch (get_line(buf, sizeof(buf))) {
363 		case 0:
364 			return (0);
365 		case 1:
366 			return (1);
367 		}
368 
369 #define	DEC(c)		(((c) - ' ') & 077)	/* single character decode */
370 #define IS_DEC(c)	 ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) )
371 
372 #define OUT_OF_RANGE do {						\
373 	warnx("%s: %s: character out of range: [%d-%d]",		\
374 	    infile, outfile, ' ', 077 + ' ' + 1);			\
375 	return (1);							\
376 } while (0)
377 
378 		/*
379 		 * `i' is used to avoid writing out all the characters
380 		 * at the end of the file.
381 		 */
382 		p = buf;
383 		if ((i = DEC(*p)) <= 0)
384 			break;
385 		for (++p; i > 0; p += 4, i -= 3)
386 			if (i >= 3) {
387 				if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) &&
388 				    IS_DEC(*(p + 2)) && IS_DEC(*(p + 3))))
389 					OUT_OF_RANGE;
390 
391 				ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
392 				putc(ch, outfp);
393 				ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
394 				putc(ch, outfp);
395 				ch = DEC(p[2]) << 6 | DEC(p[3]);
396 				putc(ch, outfp);
397 			} else {
398 				if (i >= 1) {
399 					if (!(IS_DEC(*p) && IS_DEC(*(p + 1))))
400 						OUT_OF_RANGE;
401 					ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
402 					putc(ch, outfp);
403 				}
404 				if (i >= 2) {
405 					if (!(IS_DEC(*(p + 1)) &&
406 					    IS_DEC(*(p + 2))))
407 						OUT_OF_RANGE;
408 
409 					ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
410 					putc(ch, outfp);
411 				}
412 				if (i >= 3) {
413 					if (!(IS_DEC(*(p + 2)) &&
414 					    IS_DEC(*(p + 3))))
415 						OUT_OF_RANGE;
416 					ch = DEC(p[2]) << 6 | DEC(p[3]);
417 					putc(ch, outfp);
418 				}
419 			}
420 	}
421 	switch (get_line(buf, sizeof(buf))) {
422 	case 0:
423 		return (0);
424 	case 1:
425 		return (1);
426 	default:
427 		return (checkend(buf, "end", "no \"end\" line"));
428 	}
429 }
430 
431 static int
432 base64_decode(void)
433 {
434 	int n, count, count4;
435 	char inbuf[MAXPATHLEN + 1], *p;
436 	unsigned char outbuf[MAXPATHLEN * 4];
437 	char leftover[MAXPATHLEN + 1];
438 
439 	leftover[0] = '\0';
440 	for (;;) {
441 		strcpy(inbuf, leftover);
442 		switch (get_line(inbuf + strlen(inbuf),
443 		    sizeof(inbuf) - strlen(inbuf))) {
444 		case 0:
445 			return (0);
446 		case 1:
447 			return (1);
448 		}
449 
450 		count = 0;
451 		count4 = -1;
452 		p = inbuf;
453 		while (*p != '\0') {
454 			/*
455 			 * Base64 encoded strings have the following
456 			 * characters in them: A-Z, a-z, 0-9 and +, / and =
457 			 */
458 			if (isalnum(*p) || *p == '+' || *p == '/' || *p == '=')
459 				count++;
460 			if (count % 4 == 0)
461 				count4 = p - inbuf;
462 			p++;
463 		}
464 
465 		strcpy(leftover, inbuf + count4 + 1);
466 		inbuf[count4 + 1] = 0;
467 
468 		n = b64_pton(inbuf, outbuf, sizeof(outbuf));
469 
470 		if (n < 0)
471 			break;
472 		fwrite(outbuf, 1, n, outfp);
473 	}
474 	return (checkend(inbuf, "====", "error decoding base64 input stream"));
475 }
476 
477 static void
478 usage(void)
479 {
480 
481 	(void)fprintf(stderr,
482 	    "usage: uudecode [-cimprs] [file ...]\n"
483 	    "       uudecode [-i] -o output_file [file]\n"
484 	    "       b64decode [-cimprs] [file ...]\n"
485 	    "       b64decode [-i] -o output_file [file]\n");
486 	exit(1);
487 }
488