xref: /freebsd-14.2/usr.bin/bintrans/uudecode.c (revision fa778f0c)
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
main_base64_decode(const char * in)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
main_decode(int argc,char * argv[])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
decode(void)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
decode2(void)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
get_line(char * buf,size_t size)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
checkend(const char * ptr,const char * end,const char * msg)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 	return (0);
347 }
348 
349 static int
checkout(int rval)350 checkout(int rval)
351 {
352 	if (fflush(outfp) != 0) {
353 		warn("%s: %s", infile, outfile);
354 		rval = 1;
355 	}
356 	if (outfp != stdout) {
357 		(void)fclose(outfp);
358 		outfp = stdout;
359 	}
360 	outfile = "/dev/stdout";
361 	return (rval);
362 }
363 
364 static int
uu_decode(void)365 uu_decode(void)
366 {
367 	int i, ch;
368 	char *p;
369 	char buf[MAXPATHLEN+1];
370 
371 	/* for each input line */
372 	for (;;) {
373 		switch (get_line(buf, sizeof(buf))) {
374 		case 0:
375 			return (checkout(0));
376 		case 1:
377 			return (checkout(1));
378 		}
379 
380 #define	DEC(c)		(((c) - ' ') & 077)	/* single character decode */
381 #define IS_DEC(c)	 ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) )
382 
383 #define OUT_OF_RANGE do {						\
384 	warnx("%s: %s: character out of range: [%d-%d]",		\
385 	    infile, outfile, ' ', 077 + ' ' + 1);			\
386 	return (1);							\
387 } while (0)
388 
389 		/*
390 		 * `i' is used to avoid writing out all the characters
391 		 * at the end of the file.
392 		 */
393 		p = buf;
394 		if ((i = DEC(*p)) <= 0)
395 			break;
396 		for (++p; i > 0; p += 4, i -= 3)
397 			if (i >= 3) {
398 				if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) &&
399 				    IS_DEC(*(p + 2)) && IS_DEC(*(p + 3))))
400 					OUT_OF_RANGE;
401 
402 				ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
403 				putc(ch, outfp);
404 				ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
405 				putc(ch, outfp);
406 				ch = DEC(p[2]) << 6 | DEC(p[3]);
407 				putc(ch, outfp);
408 			} else {
409 				if (i >= 1) {
410 					if (!(IS_DEC(*p) && IS_DEC(*(p + 1))))
411 						OUT_OF_RANGE;
412 					ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
413 					putc(ch, outfp);
414 				}
415 				if (i >= 2) {
416 					if (!(IS_DEC(*(p + 1)) &&
417 					    IS_DEC(*(p + 2))))
418 						OUT_OF_RANGE;
419 
420 					ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
421 					putc(ch, outfp);
422 				}
423 				if (i >= 3) {
424 					if (!(IS_DEC(*(p + 2)) &&
425 					    IS_DEC(*(p + 3))))
426 						OUT_OF_RANGE;
427 					ch = DEC(p[2]) << 6 | DEC(p[3]);
428 					putc(ch, outfp);
429 				}
430 			}
431 	}
432 	switch (get_line(buf, sizeof(buf))) {
433 	case 0:
434 		return (checkout(0));
435 	case 1:
436 		return (checkout(1));
437 	default:
438 		return (checkout(checkend(buf, "end", "no \"end\" line")));
439 	}
440 }
441 
442 static int
base64_decode(void)443 base64_decode(void)
444 {
445 	int n, count, count4;
446 	char inbuf[MAXPATHLEN + 1], *p;
447 	unsigned char outbuf[MAXPATHLEN * 4];
448 	char leftover[MAXPATHLEN + 1];
449 
450 	leftover[0] = '\0';
451 	for (;;) {
452 		strcpy(inbuf, leftover);
453 		switch (get_line(inbuf + strlen(inbuf),
454 		    sizeof(inbuf) - strlen(inbuf))) {
455 		case 0:
456 			return (checkout(0));
457 		case 1:
458 			return (checkout(1));
459 		}
460 
461 		count = 0;
462 		count4 = -1;
463 		p = inbuf;
464 		while (*p != '\0') {
465 			/*
466 			 * Base64 encoded strings have the following
467 			 * characters in them: A-Z, a-z, 0-9 and +, / and =
468 			 */
469 			if (isalnum(*p) || *p == '+' || *p == '/' || *p == '=')
470 				count++;
471 			if (count % 4 == 0)
472 				count4 = p - inbuf;
473 			p++;
474 		}
475 
476 		strcpy(leftover, inbuf + count4 + 1);
477 		inbuf[count4 + 1] = 0;
478 
479 		n = b64_pton(inbuf, outbuf, sizeof(outbuf));
480 
481 		if (n < 0)
482 			break;
483 		fwrite(outbuf, 1, n, outfp);
484 	}
485 	return (checkout(checkend(inbuf, "====", "error decoding base64 input stream")));
486 }
487 
488 static void
usage(void)489 usage(void)
490 {
491 
492 	(void)fprintf(stderr,
493 	    "usage: uudecode [-cimprs] [file ...]\n"
494 	    "       uudecode [-i] -o output_file [file]\n"
495 	    "       b64decode [-cimprs] [file ...]\n"
496 	    "       b64decode [-i] -o output_file [file]\n");
497 	exit(1);
498 }
499