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