11590Srgrimes/*-
21590Srgrimes * Copyright (c) 1983, 1993
31590Srgrimes *	The Regents of the University of California.  All rights reserved.
41590Srgrimes *
51590Srgrimes * Redistribution and use in source and binary forms, with or without
61590Srgrimes * modification, are permitted provided that the following conditions
71590Srgrimes * are met:
81590Srgrimes * 1. Redistributions of source code must retain the above copyright
91590Srgrimes *    notice, this list of conditions and the following disclaimer.
101590Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111590Srgrimes *    notice, this list of conditions and the following disclaimer in the
121590Srgrimes *    documentation and/or other materials provided with the distribution.
131590Srgrimes * 4. Neither the name of the University nor the names of its contributors
141590Srgrimes *    may be used to endorse or promote products derived from this software
151590Srgrimes *    without specific prior written permission.
161590Srgrimes *
171590Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
181590Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
191590Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
201590Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
211590Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
221590Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
231590Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
241590Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
251590Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
261590Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
271590Srgrimes * SUCH DAMAGE.
281590Srgrimes */
291590Srgrimes
301590Srgrimes#if 0
311590Srgrimes#ifndef lint
321590Srgrimesstatic const char copyright[] =
331590Srgrimes"@(#) Copyright (c) 1983, 1993\n\
341590Srgrimes	The Regents of the University of California.  All rights reserved.\n";
3527270Scharnier#endif /* not lint */
361590Srgrimes
371590Srgrimes#ifndef lint
381590Srgrimesstatic char sccsid[] = "@(#)uudecode.c	8.2 (Berkeley) 4/2/94";
391590Srgrimes#endif /* not lint */
401590Srgrimes#endif
4127270Scharnier#include <sys/cdefs.h>
421590Srgrimes__FBSDID("$FreeBSD$");
4327270Scharnier
4427270Scharnier/*
4550477Speter * uudecode [file ...]
461590Srgrimes *
471590Srgrimes * create the specified file, decoding as you go.
481590Srgrimes * used with uuencode.
491590Srgrimes */
5027270Scharnier#include <sys/param.h>
511590Srgrimes#include <sys/socket.h>
521590Srgrimes#include <sys/stat.h>
5318725Sjkh
5427270Scharnier#include <netinet/in.h>
5513678Swosch
5627270Scharnier#include <ctype.h>
571590Srgrimes#include <err.h>
5827270Scharnier#include <errno.h>
5927270Scharnier#include <fcntl.h>
6027270Scharnier#include <libgen.h>
6127270Scharnier#include <pwd.h>
621590Srgrimes#include <resolv.h>
631590Srgrimes#include <stdio.h>
641590Srgrimes#include <stdlib.h>
651590Srgrimes#include <string.h>
661590Srgrimes#include <unistd.h>
6749123Sgreen
681590Srgrimesstatic const char *infile, *outfile;
691590Srgrimesstatic FILE *infp, *outfp;
701590Srgrimesstatic int base64, cflag, iflag, oflag, pflag, rflag, sflag;
711590Srgrimes
721590Srgrimesstatic void	usage(void);
731590Srgrimesstatic int	decode(void);
741590Srgrimesstatic int	decode2(void);
751590Srgrimesstatic int	uu_decode(void);
7649123Sgreenstatic int	base64_decode(void);
7749123Sgreen
7849123Sgreenint
7949123Sgreenmain(int argc, char *argv[])
8049123Sgreen{
8149123Sgreen	int rval, ch;
821590Srgrimes
831590Srgrimes	if (strcmp(basename(argv[0]), "b64decode") == 0)
841590Srgrimes		base64 = 1;
851590Srgrimes
861590Srgrimes	while ((ch = getopt(argc, argv, "cimo:prs")) != -1) {
871590Srgrimes		switch (ch) {
881590Srgrimes		case 'c':
891590Srgrimes			if (oflag || rflag)
901590Srgrimes				usage();
911590Srgrimes			cflag = 1; /* multiple uudecode'd files */
921590Srgrimes			break;
9327270Scharnier		case 'i':
941590Srgrimes			iflag = 1; /* ask before override files */
951590Srgrimes			break;
961590Srgrimes		case 'm':
971590Srgrimes			base64 = 1;
9818725Sjkh			break;
9966590Sru		case 'o':
10018725Sjkh			if (cflag || pflag || rflag || sflag)
10118725Sjkh				usage();
10218725Sjkh			oflag = 1; /* output to the specified file */
10327270Scharnier			sflag = 1; /* do not strip pathnames for output */
10427270Scharnier			outfile = optarg; /* set the output filename */
10518725Sjkh			break;
10666590Sru		case 'p':
10718725Sjkh			if (oflag)
10818725Sjkh				usage();
1091590Srgrimes			pflag = 1; /* print output to stdout */
1101590Srgrimes			break;
1111590Srgrimes		case 'r':
11213678Swosch			if (cflag || oflag)
11313678Swosch				usage();
11413678Swosch			rflag = 1; /* decode raw data */
11513678Swosch			break;
11613678Swosch		case 's':
11727270Scharnier			if (oflag)
1181590Srgrimes				usage();
1191590Srgrimes			sflag = 1; /* do not strip pathnames for output */
1201590Srgrimes			break;
1211590Srgrimes		default:
1221590Srgrimes			usage();
1231590Srgrimes		}
1241590Srgrimes	}
12549123Sgreen	argc -= optind;
12649123Sgreen	argv += optind;
12749123Sgreen
12849123Sgreen	if (*argv != NULL) {
12949123Sgreen		rval = 0;
13049123Sgreen		do {
1311590Srgrimes			infp = fopen(infile = *argv, "r");
1321590Srgrimes			if (infp == NULL) {
13349123Sgreen				warn("%s", *argv);
13449123Sgreen				rval = 1;
13549123Sgreen				continue;
1361590Srgrimes			}
1371590Srgrimes			rval |= decode();
1381590Srgrimes			fclose(infp);
13927270Scharnier		} while (*++argv);
14027270Scharnier	} else {
14127270Scharnier		infile = "stdin";
14249123Sgreen		infp = stdin;
14327270Scharnier		rval = decode();
14427270Scharnier	}
14527270Scharnier	exit(rval);
14627270Scharnier}
1471590Srgrimes
1481590Srgrimesstatic int
1491590Srgrimesdecode(void)
1501590Srgrimes{
1511590Srgrimes	int r, v;
1521590Srgrimes
1531590Srgrimes	if (rflag) {
1541590Srgrimes		/* relaxed alternative to decode2() */
1551590Srgrimes		outfile = "/dev/stdout";
1561590Srgrimes		outfp = stdout;
1571590Srgrimes		if (base64)
1581590Srgrimes			return (base64_decode());
1591590Srgrimes		else
1601590Srgrimes			return (uu_decode());
1611590Srgrimes	}
1621590Srgrimes	v = decode2();
1631590Srgrimes	if (v == EOF) {
1641590Srgrimes		warnx("%s: missing or bad \"begin\" line", infile);
1651590Srgrimes		return (1);
1661590Srgrimes	}
1671590Srgrimes	for (r = v; cflag; r |= v) {
1681590Srgrimes		v = decode2();
1691590Srgrimes		if (v == EOF)
1701590Srgrimes			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
303getline(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 (getline(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 (getline(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 (getline(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