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