1/*	$OpenBSD: uudecode.c,v 1.29 2022/08/30 16:06:09 yasuoka Exp $	*/
2/*	$FreeBSD: uudecode.c,v 1.49 2003/05/03 19:44:46 obrien Exp $	*/
3
4/*-
5 * Copyright (c) 1983, 1993
6 *	The Regents of the University of California.  All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33/*
34 * Create the specified file, decoding as you go.
35 * Used with uuencode.
36 */
37
38#include <sys/stat.h>
39
40#include <netinet/in.h>
41
42#include <err.h>
43#include <errno.h>
44#include <fcntl.h>
45#include <limits.h>
46#include <pwd.h>
47#include <resolv.h>
48#include <stdio.h>
49#include <stdlib.h>
50#include <string.h>
51#include <unistd.h>
52
53static const char *infile, *outfile;
54static FILE *infp, *outfp;
55static int base64, cflag, iflag, oflag, pflag, rflag, sflag;
56
57static void __dead	usage(void);
58static int	decode(void);
59static int	decode2(void);
60static int	uu_decode(void);
61static int	base64_decode(void);
62
63enum program_mode {
64	MODE_DECODE,
65	MODE_B64DECODE
66} pmode;
67
68int
69main(int argc, char *argv[])
70{
71	int rval, ch;
72	extern char *__progname;
73	static const char *optstr[2] = {
74		"cimo:prs",
75		"cio:prs"
76	};
77
78	pmode = MODE_DECODE;
79	if (strcmp(__progname, "b64decode") == 0) {
80		base64 = 1;
81		pmode = MODE_B64DECODE;
82	}
83
84	while ((ch = getopt(argc, argv, optstr[pmode])) != -1) {
85		switch(ch) {
86		case 'c':
87			if (oflag || rflag)
88				usage();
89			cflag = 1; /* multiple uudecode'd files */
90			break;
91		case 'i':
92			iflag = 1; /* ask before override files */
93			break;
94		case 'm':
95			base64 = 1;
96			break;
97		case 'o':
98			if (cflag || pflag || rflag || sflag)
99				usage();
100			oflag = 1; /* output to the specified file */
101			sflag = 1; /* do not strip pathnames for output */
102			outfile = optarg; /* set the output filename */
103			break;
104		case 'p':
105			if (oflag)
106				usage();
107			pflag = 1; /* print output to stdout */
108			break;
109		case 'r':
110			if (cflag || oflag)
111				usage();
112			rflag = 1; /* decode raw data */
113			break;
114		case 's':
115			if (oflag)
116				usage();
117			sflag = 1; /* do not strip pathnames for output */
118			break;
119		default:
120			usage();
121		}
122	}
123	argc -= optind;
124	argv += optind;
125
126	if (sflag) {
127		if (pledge("stdio rpath wpath cpath getpw", NULL) == -1)
128			err(1, "pledge");
129	} else if (pflag == 0) {
130		if (pledge("stdio rpath wpath cpath", NULL) == -1)
131			err(1, "pledge");
132	} else {
133		if (pledge("stdio rpath", NULL) == -1)
134			err(1, "pledge");
135	}
136
137	if (*argv) {
138		rval = 0;
139		do {
140			infp = fopen(infile = *argv, "r");
141			if (infp == NULL) {
142				warn("%s", *argv);
143				rval = 1;
144				continue;
145			}
146			rval |= decode();
147			fclose(infp);
148		} while (*++argv);
149	} else {
150		infile = "stdin";
151		infp = stdin;
152		rval = decode();
153	}
154	return (rval);
155}
156
157static int
158decode(void)
159{
160	int r, v;
161
162	if (rflag) {
163		/* relaxed alternative to decode2() */
164		outfile = "/dev/stdout";
165		outfp = stdout;
166		if (base64)
167			return (base64_decode());
168		else
169			return (uu_decode());
170	}
171	v = decode2();
172	if (v == EOF) {
173		warnx("%s: missing or bad \"begin\" line", infile);
174		return (1);
175	}
176	for (r = v; cflag; r |= v) {
177		v = decode2();
178		if (v == EOF)
179			break;
180	}
181	return (r);
182}
183
184static int
185decode2(void)
186{
187	int flags, fd, mode;
188	size_t n, m;
189	char *p, *q;
190	void *handle;
191	struct passwd *pw;
192	struct stat st;
193	char buf[BUFSIZ];
194
195	base64 = 0;
196	/* search for header line */
197	for (;;) {
198		if (fgets(buf, sizeof(buf), infp) == NULL)
199			return (EOF);
200		p = buf;
201		if (strncmp(p, "begin-base64 ", 13) == 0) {
202			base64 = 1;
203			p += 13;
204		} else if (strncmp(p, "begin ", 6) == 0)
205			p += 6;
206		else
207			continue;
208		/* p points to mode */
209		q = strchr(p, ' ');
210		if (q == NULL)
211			continue;
212		*q++ = '\0';
213		/* q points to filename */
214		n = strlen(q);
215		while (n > 0 && (q[n-1] == '\n' || q[n-1] == '\r'))
216			q[--n] = '\0';
217		/* found valid header? */
218		if (n > 0)
219			break;
220	}
221
222	handle = setmode(p);
223	if (handle == NULL) {
224		warnx("%s: unable to parse file mode", infile);
225		return (1);
226	}
227	mode = getmode(handle, 0) & 0666;
228	free(handle);
229
230	if (sflag) {
231		/* don't strip, so try ~user/file expansion */
232		p = NULL;
233		pw = NULL;
234		if (*q == '~')
235			p = strchr(q, '/');
236		if (p != NULL) {
237			*p = '\0';
238			pw = getpwnam(q + 1);
239			*p = '/';
240		}
241		if (pw != NULL) {
242			n = strlen(pw->pw_dir);
243			if (buf + n > p) {
244				/* make room */
245				m = strlen(p);
246				if (sizeof(buf) < n + m) {
247					warnx("%s: bad output filename",
248					    infile);
249					return (1);
250				}
251				p = memmove(buf + n, p, m);
252			}
253			q = memcpy(p - n, pw->pw_dir, n);
254		}
255	} else {
256		/* strip down to leaf name */
257		p = strrchr(q, '/');
258		if (p != NULL)
259			q = p + 1;
260	}
261	if (!oflag)
262		outfile = q;
263
264	/* POSIX says "/dev/stdout" is a 'magic cookie' not a special file. */
265	if (pflag || strcmp(outfile, "/dev/stdout") == 0)
266		outfp = stdout;
267	else {
268		flags = O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW;
269		if (lstat(outfile, &st) == 0) {
270			if (iflag) {
271				warnc(EEXIST, "%s: %s", infile, outfile);
272				return (0);
273			}
274			switch (st.st_mode & S_IFMT) {
275			case S_IFREG:
276			case S_IFLNK:
277				/* avoid symlink attacks */
278				if (unlink(outfile) == 0 || errno == ENOENT)
279					break;
280				warn("%s: unlink %s", infile, outfile);
281				return (1);
282			case S_IFDIR:
283				warnc(EISDIR, "%s: %s", infile, outfile);
284				return (1);
285			default:
286				if (oflag) {
287					/* trust command-line names */
288					flags &= ~(O_EXCL|O_NOFOLLOW);
289					break;
290				}
291				warnc(EEXIST, "%s: %s", infile, outfile);
292				return (1);
293			}
294		} else if (errno != ENOENT) {
295			warn("%s: %s", infile, outfile);
296			return (1);
297		}
298		if ((fd = open(outfile, flags, mode)) == -1 ||
299		    (outfp = fdopen(fd, "w")) == NULL) {
300			warn("%s: %s", infile, outfile);
301			return (1);
302		}
303	}
304
305	if (base64)
306		return (base64_decode());
307	else
308		return (uu_decode());
309}
310
311static int
312get_line(char *buf, size_t size)
313{
314	if (fgets(buf, size, infp) != NULL)
315		return (2);
316	if (rflag)
317		return (0);
318	warnx("%s: %s: short file", infile, outfile);
319	return (1);
320}
321
322static int
323checkend(const char *ptr, const char *end, const char *msg)
324{
325	size_t n;
326
327	n = strlen(end);
328	if (strncmp(ptr, end, n) != 0 ||
329	    strspn(ptr + n, " \t\r\n") != strlen(ptr + n)) {
330		warnx("%s: %s: %s", infile, outfile, msg);
331		return (1);
332	}
333	if (fclose(outfp) != 0) {
334		warn("%s: %s", infile, outfile);
335		return (1);
336	}
337	return (0);
338}
339
340static int
341uu_decode(void)
342{
343	int i, ch;
344	char *p;
345	char buf[BUFSIZ];
346
347	/* for each input line */
348	for (;;) {
349		switch (get_line(buf, sizeof(buf))) {
350		case 0:
351			return (0);
352		case 1:
353			return (1);
354		}
355
356#define	DEC(c)	(((c) - ' ') & 077)		/* single character decode */
357#define IS_DEC(c) ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) )
358
359#define OUT_OF_RANGE(c) do {						\
360	warnx("%s: %s: character value (%d) out of range [%d-%d]",	\
361	    infile, outfile, (unsigned char)(c), 1 + ' ', 077 + ' ' + 1); \
362	return (1);							\
363} while (0)
364
365		/*
366		 * `i' is used to avoid writing out all the characters
367		 * at the end of the file.
368		 */
369		p = buf;
370		if ((i = DEC(*p)) <= 0)
371			break;
372		for (++p; i > 0; p += 4, i -= 3)
373			if (i >= 3) {
374				if (!IS_DEC(*p))
375					OUT_OF_RANGE(*p);
376				if (!IS_DEC(*(p + 1)))
377					OUT_OF_RANGE(*(p + 1));
378				if (!IS_DEC(*(p + 2)))
379					OUT_OF_RANGE(*(p + 2));
380				if (!IS_DEC(*(p + 3)))
381					OUT_OF_RANGE(*(p + 3));
382				ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
383				putc(ch, outfp);
384				ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
385				putc(ch, outfp);
386				ch = DEC(p[2]) << 6 | DEC(p[3]);
387				putc(ch, outfp);
388			}
389			else {
390				if (i >= 1) {
391					if (!IS_DEC(*p))
392						OUT_OF_RANGE(*p);
393					if (!IS_DEC(*(p + 1)))
394						OUT_OF_RANGE(*(p + 1));
395					ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
396					putc(ch, outfp);
397				}
398				if (i >= 2) {
399					if (!IS_DEC(*(p + 1)))
400						OUT_OF_RANGE(*(p + 1));
401					if (!IS_DEC(*(p + 2)))
402						OUT_OF_RANGE(*(p + 2));
403					ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
404					putc(ch, outfp);
405				}
406				if (i >= 3) {
407					if (!IS_DEC(*(p + 2)))
408						OUT_OF_RANGE(*(p + 2));
409					if (!IS_DEC(*(p + 3)))
410						OUT_OF_RANGE(*(p + 3));
411					ch = DEC(p[2]) << 6 | DEC(p[3]);
412					putc(ch, outfp);
413				}
414			}
415	}
416	switch (get_line(buf, sizeof(buf))) {
417	case 0:
418		return (0);
419	case 1:
420		return (1);
421	default:
422		return (checkend(buf, "end", "no \"end\" line"));
423	}
424}
425
426#define	ROUNDDOWN(x,y)	(((x)/(y)) * (y))
427
428static int
429base64_decode(void)
430{
431	int n;
432	char inbuf[ROUNDDOWN(BUFSIZ, 4) + 1];
433	unsigned char outbuf[BUFSIZ * 4];
434
435	for (;;) {
436		switch (get_line(inbuf, sizeof(inbuf))) {
437		case 0:
438			return (0);
439		case 1:
440			return (1);
441		}
442		n = b64_pton(inbuf, outbuf, sizeof(outbuf));
443		if (n < 0)
444			break;
445		fwrite(outbuf, 1, n, outfp);
446	}
447	return (checkend(inbuf, "====",
448		    "error decoding base64 input stream"));
449}
450
451static void __dead
452usage(void)
453{
454	switch (pmode) {
455	case MODE_DECODE:
456		(void)fprintf(stderr,
457		    "usage: uudecode [-cimprs] [file ...]\n"
458		    "       uudecode [-i] -o output_file [file]\n");
459		break;
460	case MODE_B64DECODE:
461		(void)fprintf(stderr,
462		    "usage: b64decode [-ciprs] [file ...]\n"
463		    "       b64decode [-i] -o output_file [file]\n");
464		break;
465	}
466	exit(1);
467}
468