uudecode.c revision 1.22
1/*	$OpenBSD: uudecode.c,v 1.22 2015/10/09 01:37:09 deraadt 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/socket.h>
39#include <sys/stat.h>
40
41#include <netinet/in.h>
42
43#include <err.h>
44#include <errno.h>
45#include <fcntl.h>
46#include <locale.h>
47#include <pwd.h>
48#include <resolv.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52#include <unistd.h>
53#include <limits.h>
54
55static const char *infile, *outfile;
56static FILE *infp, *outfp;
57static int base64, cflag, iflag, oflag, pflag, rflag, sflag;
58
59static void	usage(void);
60static int	decode(void);
61static int	decode2(void);
62static int	uu_decode(void);
63static int	base64_decode(void);
64
65enum program_mode {
66	MODE_DECODE,
67	MODE_B64DECODE
68} pmode;
69
70int
71main(int argc, char *argv[])
72{
73	int rval, ch;
74	extern char *__progname;
75	static const char *optstr[2] = {
76		"cimo:prs",
77		"cio:prs"
78	};
79
80	pmode = MODE_DECODE;
81	if (strcmp(__progname, "b64decode") == 0) {
82		base64 = 1;
83		pmode = MODE_B64DECODE;
84	}
85
86	setlocale(LC_ALL, "");
87	while ((ch = getopt(argc, argv, optstr[pmode])) != -1) {
88		switch(ch) {
89		case 'c':
90			if (oflag || rflag)
91				usage();
92			cflag = 1; /* multiple uudecode'd files */
93			break;
94		case 'i':
95			iflag = 1; /* ask before override files */
96			break;
97		case 'm':
98			base64 = 1;
99			break;
100		case 'o':
101			if (cflag || pflag || rflag || sflag)
102				usage();
103			oflag = 1; /* output to the specified file */
104			sflag = 1; /* do not strip pathnames for output */
105			outfile = optarg; /* set the output filename */
106			break;
107		case 'p':
108			if (oflag)
109				usage();
110			pflag = 1; /* print output to stdout */
111			break;
112		case 'r':
113			if (cflag || oflag)
114				usage();
115			rflag = 1; /* decode raw data */
116			break;
117		case 's':
118			if (oflag)
119				usage();
120			sflag = 1; /* do not strip pathnames for output */
121			break;
122		default:
123			usage();
124		}
125	}
126	argc -= optind;
127	argv += optind;
128
129	if (oflag || 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	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[PATH_MAX];
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)) < 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	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[PATH_MAX];
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 do {						\
360	warnx("%s: %s: character out of range: [%d-%d]",		\
361	    infile, outfile, 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) && IS_DEC(*(p + 1)) &&
375				     IS_DEC(*(p + 2)) && IS_DEC(*(p + 3))))
376					OUT_OF_RANGE;
377
378				ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
379				putc(ch, outfp);
380				ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
381				putc(ch, outfp);
382				ch = DEC(p[2]) << 6 | DEC(p[3]);
383				putc(ch, outfp);
384			}
385			else {
386				if (i >= 1) {
387					if (!(IS_DEC(*p) && IS_DEC(*(p + 1))))
388						OUT_OF_RANGE;
389					ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
390					putc(ch, outfp);
391				}
392				if (i >= 2) {
393					if (!(IS_DEC(*(p + 1)) &&
394					    IS_DEC(*(p + 2))))
395						OUT_OF_RANGE;
396
397					ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
398					putc(ch, outfp);
399				}
400				if (i >= 3) {
401					if (!(IS_DEC(*(p + 2)) &&
402					    IS_DEC(*(p + 3))))
403						OUT_OF_RANGE;
404					ch = DEC(p[2]) << 6 | DEC(p[3]);
405					putc(ch, outfp);
406				}
407			}
408	}
409	switch (get_line(buf, sizeof(buf))) {
410	case 0:
411		return (0);
412	case 1:
413		return (1);
414	default:
415		return (checkend(buf, "end", "no \"end\" line"));
416	}
417}
418
419static int
420base64_decode(void)
421{
422	int n;
423	char inbuf[PATH_MAX];
424	unsigned char outbuf[PATH_MAX * 4];
425
426	for (;;) {
427		switch (get_line(inbuf, sizeof(inbuf))) {
428		case 0:
429			return (0);
430		case 1:
431			return (1);
432		}
433		n = b64_pton(inbuf, outbuf, sizeof(outbuf));
434		if (n < 0)
435			break;
436		fwrite(outbuf, 1, n, outfp);
437	}
438	return (checkend(inbuf, "====",
439		    "error decoding base64 input stream"));
440}
441
442static void
443usage(void)
444{
445	switch (pmode) {
446	case MODE_DECODE:
447		(void)fprintf(stderr,
448		    "usage: uudecode [-cimprs] [file ...]\n"
449		    "       uudecode [-i] -o output_file [file]\n");
450		break;
451	case MODE_B64DECODE:
452		(void)fprintf(stderr,
453		    "usage: b64decode [-ciprs] [file ...]\n"
454		    "       b64decode [-i] -o output_file [file]\n");
455		break;
456	}
457	exit(1);
458}
459