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/*
33 * uudecode [file ...]
34 *
35 * create the specified file, decoding as you go.
36 * used with uuencode.
37 */
38#include <sys/param.h>
39#include <sys/socket.h>
40#include <sys/stat.h>
41
42#include <netinet/in.h>
43
44#include <ctype.h>
45#include <err.h>
46#include <errno.h>
47#include <fcntl.h>
48#include <libgen.h>
49#include <pwd.h>
50#include <resolv.h>
51#include <stdbool.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <unistd.h>
56
57extern int main_decode(int, char *[]);
58extern int main_base64_decode(const char *);
59
60static const char *infile, *outfile;
61static FILE *infp, *outfp;
62static bool base64, cflag, iflag, oflag, pflag, rflag, sflag;
63
64static void	usage(void);
65static int	decode(void);
66static int	decode2(void);
67static int	uu_decode(void);
68static int	base64_decode(void);
69
70int
71main_base64_decode(const char *in)
72{
73	base64 = 1;
74	rflag = 1;
75	if (in != NULL) {
76		infile = in;
77		infp = fopen(infile, "r");
78		if (infp == NULL)
79			err(1, "%s", in);
80	} else {
81		infile = "stdin";
82		infp = stdin;
83	}
84	exit(decode());
85}
86
87int
88main_decode(int argc, char *argv[])
89{
90	int rval, ch;
91
92	if (strcmp(basename(argv[0]), "b64decode") == 0)
93		base64 = true;
94
95	while ((ch = getopt(argc, argv, "cimo:prs")) != -1) {
96		switch (ch) {
97		case 'c':
98			if (oflag || rflag)
99				usage();
100			cflag = true; /* multiple uudecode'd files */
101			break;
102		case 'i':
103			iflag = true; /* ask before override files */
104			break;
105		case 'm':
106			base64 = true;
107			break;
108		case 'o':
109			if (cflag || pflag || rflag || sflag)
110				usage();
111			oflag = true; /* output to the specified file */
112			sflag = true; /* do not strip pathnames for output */
113			outfile = optarg; /* set the output filename */
114			break;
115		case 'p':
116			if (oflag)
117				usage();
118			pflag = true; /* print output to stdout */
119			break;
120		case 'r':
121			if (cflag || oflag)
122				usage();
123			rflag = true; /* decode raw data */
124			break;
125		case 's':
126			if (oflag)
127				usage();
128			sflag = true; /* do not strip pathnames for output */
129			break;
130		default:
131			usage();
132		}
133	}
134	argc -= optind;
135	argv += optind;
136
137	if (*argv != NULL) {
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	exit(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[MAXPATHLEN + 1];
194
195	base64 = false;
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 = true;
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;
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;
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)) < 0 ||
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
315	if (fgets(buf, size, infp) != NULL)
316		return (2);
317	if (rflag)
318		return (0);
319	warnx("%s: %s: short file", infile, outfile);
320	return (1);
321}
322
323static int
324checkend(const char *ptr, const char *end, const char *msg)
325{
326	size_t n;
327
328	n = strlen(end);
329	if (strncmp(ptr, end, n) != 0 ||
330	    strspn(ptr + n, " \t\r\n") != strlen(ptr + n)) {
331		warnx("%s: %s: %s", infile, outfile, msg);
332		return (1);
333	}
334	return (0);
335}
336
337static int
338checkout(int rval)
339{
340	if (fflush(outfp) != 0) {
341		warn("%s: %s", infile, outfile);
342		rval = 1;
343	}
344	if (outfp != stdout) {
345		(void)fclose(outfp);
346		outfp = stdout;
347	}
348	outfile = "/dev/stdout";
349	return (rval);
350}
351
352static int
353uu_decode(void)
354{
355	int i, ch;
356	char *p;
357	char buf[MAXPATHLEN+1];
358
359	/* for each input line */
360	for (;;) {
361		switch (get_line(buf, sizeof(buf))) {
362		case 0:
363			return (checkout(0));
364		case 1:
365			return (checkout(1));
366		}
367
368#define	DEC(c)		(((c) - ' ') & 077)	/* single character decode */
369#define IS_DEC(c)	 ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) )
370
371#define OUT_OF_RANGE do {						\
372	warnx("%s: %s: character out of range: [%d-%d]",		\
373	    infile, outfile, ' ', 077 + ' ' + 1);			\
374	return (1);							\
375} while (0)
376
377		/*
378		 * `i' is used to avoid writing out all the characters
379		 * at the end of the file.
380		 */
381		p = buf;
382		if ((i = DEC(*p)) <= 0)
383			break;
384		for (++p; i > 0; p += 4, i -= 3)
385			if (i >= 3) {
386				if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) &&
387				    IS_DEC(*(p + 2)) && IS_DEC(*(p + 3))))
388					OUT_OF_RANGE;
389
390				ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
391				putc(ch, outfp);
392				ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
393				putc(ch, outfp);
394				ch = DEC(p[2]) << 6 | DEC(p[3]);
395				putc(ch, outfp);
396			} else {
397				if (i >= 1) {
398					if (!(IS_DEC(*p) && IS_DEC(*(p + 1))))
399						OUT_OF_RANGE;
400					ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
401					putc(ch, outfp);
402				}
403				if (i >= 2) {
404					if (!(IS_DEC(*(p + 1)) &&
405					    IS_DEC(*(p + 2))))
406						OUT_OF_RANGE;
407
408					ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
409					putc(ch, outfp);
410				}
411				if (i >= 3) {
412					if (!(IS_DEC(*(p + 2)) &&
413					    IS_DEC(*(p + 3))))
414						OUT_OF_RANGE;
415					ch = DEC(p[2]) << 6 | DEC(p[3]);
416					putc(ch, outfp);
417				}
418			}
419	}
420	switch (get_line(buf, sizeof(buf))) {
421	case 0:
422		return (checkout(0));
423	case 1:
424		return (checkout(1));
425	default:
426		return (checkout(checkend(buf, "end", "no \"end\" line")));
427	}
428}
429
430static int
431base64_decode(void)
432{
433	int n, count, count4;
434	char inbuf[MAXPATHLEN + 1], *p;
435	unsigned char outbuf[MAXPATHLEN * 4];
436	char leftover[MAXPATHLEN + 1];
437
438	leftover[0] = '\0';
439	for (;;) {
440		strcpy(inbuf, leftover);
441		switch (get_line(inbuf + strlen(inbuf),
442		    sizeof(inbuf) - strlen(inbuf))) {
443		case 0:
444			return (checkout(0));
445		case 1:
446			return (checkout(1));
447		}
448
449		count = 0;
450		count4 = -1;
451		p = inbuf;
452		while (*p != '\0') {
453			/*
454			 * Base64 encoded strings have the following
455			 * characters in them: A-Z, a-z, 0-9 and +, / and =
456			 */
457			if (isalnum(*p) || *p == '+' || *p == '/' || *p == '=')
458				count++;
459			if (count % 4 == 0)
460				count4 = p - inbuf;
461			p++;
462		}
463
464		strcpy(leftover, inbuf + count4 + 1);
465		inbuf[count4 + 1] = 0;
466
467		n = b64_pton(inbuf, outbuf, sizeof(outbuf));
468
469		if (n < 0)
470			break;
471		fwrite(outbuf, 1, n, outfp);
472	}
473	return (checkout(checkend(inbuf, "====", "error decoding base64 input stream")));
474}
475
476static void
477usage(void)
478{
479
480	(void)fprintf(stderr,
481	    "usage: uudecode [-cimprs] [file ...]\n"
482	    "       uudecode [-i] -o output_file [file]\n"
483	    "       b64decode [-cimprs] [file ...]\n"
484	    "       b64decode [-i] -o output_file [file]\n");
485	exit(1);
486}
487