xref: /openbsd-src/usr.bin/uudecode/uudecode.c (revision daf88648c0e349d5c02e1504293082072c981640)
1 /*	$OpenBSD: uudecode.c,v 1.14 2004/04/09 22:54:02 millert 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 #ifndef lint
34 static const char copyright[] =
35 "@(#) Copyright (c) 1983, 1993\n\
36 	The Regents of the University of California.  All rights reserved.\n";
37 #endif /* not lint */
38 
39 #ifndef lint
40 #if 0
41 static const char sccsid[] = "@(#)uudecode.c	8.2 (Berkeley) 4/2/94";
42 #endif
43 static const char rcsid[] = "$OpenBSD: uudecode.c,v 1.14 2004/04/09 22:54:02 millert Exp $";
44 #endif /* not lint */
45 
46 /*
47  * Create the specified file, decoding as you go.
48  * Used with uuencode.
49  */
50 
51 #include <sys/param.h>
52 #include <sys/socket.h>
53 #include <sys/stat.h>
54 
55 #include <netinet/in.h>
56 
57 #include <err.h>
58 #include <errno.h>
59 #include <fcntl.h>
60 #include <locale.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 
68 static const char *infile, *outfile;
69 static FILE *infp, *outfp;
70 static int base64, cflag, iflag, oflag, pflag, rflag, sflag;
71 
72 static void	usage(void);
73 static int	decode(void);
74 static int	decode2(void);
75 static int	uu_decode(void);
76 static int	base64_decode(void);
77 
78 int
79 main(int argc, char *argv[])
80 {
81 	int rval, ch;
82 	extern char *__progname;
83 
84 	if (strcmp(__progname, "b64decode") == 0)
85 		base64 = 1;
86 
87 	setlocale(LC_ALL, "");
88 	while ((ch = getopt(argc, argv, "cimo:prs")) != -1) {
89 		switch(ch) {
90 		case 'c':
91 			if (oflag || rflag)
92 				usage();
93 			cflag = 1; /* multiple uudecode'd files */
94 			break;
95 		case 'i':
96 			iflag = 1; /* ask before override files */
97 			break;
98 		case 'm':
99 			base64 = 1;
100 			break;
101 		case 'o':
102 			if (cflag || pflag || rflag || sflag)
103 				usage();
104 			oflag = 1; /* output to the specified file */
105 			sflag = 1; /* do not strip pathnames for output */
106 			outfile = optarg; /* set the output filename */
107 			break;
108 		case 'p':
109 			if (oflag)
110 				usage();
111 			pflag = 1; /* print output to stdout */
112 			break;
113 		case 'r':
114 			if (cflag || oflag)
115 				usage();
116 			rflag = 1; /* decode raw data */
117 			break;
118 		case 's':
119 			if (oflag)
120 				usage();
121 			sflag = 1; /* do not strip pathnames for output */
122 			break;
123 		default:
124 			usage();
125 		}
126 	}
127 	argc -= optind;
128 	argv += optind;
129 
130 	if (*argv) {
131 		rval = 0;
132 		do {
133 			infp = fopen(infile = *argv, "r");
134 			if (infp == NULL) {
135 				warn("%s", *argv);
136 				rval = 1;
137 				continue;
138 			}
139 			rval |= decode();
140 			fclose(infp);
141 		} while (*++argv);
142 	} else {
143 		infile = "stdin";
144 		infp = stdin;
145 		rval = decode();
146 	}
147 	exit(rval);
148 }
149 
150 static int
151 decode(void)
152 {
153 	int r, v;
154 
155 	if (rflag) {
156 		/* relaxed alternative to decode2() */
157 		outfile = "/dev/stdout";
158 		outfp = stdout;
159 		if (base64)
160 			return (base64_decode());
161 		else
162 			return (uu_decode());
163 	}
164 	v = decode2();
165 	if (v == EOF) {
166 		warnx("%s: missing or bad \"begin\" line", infile);
167 		return (1);
168 	}
169 	for (r = v; cflag; r |= v) {
170 		v = decode2();
171 		if (v == EOF)
172 			break;
173 	}
174 	return (r);
175 }
176 
177 static int
178 decode2(void)
179 {
180 	int flags, fd, mode;
181 	size_t n, m;
182 	char *p, *q;
183 	void *handle;
184 	struct passwd *pw;
185 	struct stat st;
186 	char buf[MAXPATHLEN];
187 
188 	base64 = 0;
189 	/* search for header line */
190 	for (;;) {
191 		if (fgets(buf, sizeof(buf), infp) == NULL)
192 			return (EOF);
193 		p = buf;
194 		if (strncmp(p, "begin-base64 ", 13) == 0) {
195 			base64 = 1;
196 			p += 13;
197 		} else if (strncmp(p, "begin ", 6) == 0)
198 			p += 6;
199 		else
200 			continue;
201 		/* p points to mode */
202 		q = strchr(p, ' ');
203 		if (q == NULL)
204 			continue;
205 		*q++ = '\0';
206 		/* q points to filename */
207 		n = strlen(q);
208 		while (n > 0 && (q[n-1] == '\n' || q[n-1] == '\r'))
209 			q[--n] = '\0';
210 		/* found valid header? */
211 		if (n > 0)
212 			break;
213 	}
214 
215 	handle = setmode(p);
216 	if (handle == NULL) {
217 		warnx("%s: unable to parse file mode", infile);
218 		return (1);
219 	}
220 	mode = getmode(handle, 0) & 0666;
221 	free(handle);
222 
223 	if (sflag) {
224 		/* don't strip, so try ~user/file expansion */
225 		p = NULL;
226 		pw = NULL;
227 		if (*q == '~')
228 			p = strchr(q, '/');
229 		if (p != NULL) {
230 			*p = '\0';
231 			pw = getpwnam(q + 1);
232 			*p = '/';
233 		}
234 		if (pw != NULL) {
235 			n = strlen(pw->pw_dir);
236 			if (buf + n > p) {
237 				/* make room */
238 				m = strlen(p);
239 				if (sizeof(buf) < n + m) {
240 					warnx("%s: bad output filename",
241 					    infile);
242 					return (1);
243 				}
244 				p = memmove(buf + n, p, m);
245 			}
246 			q = memcpy(p - n, pw->pw_dir, n);
247 		}
248 	} else {
249 		/* strip down to leaf name */
250 		p = strrchr(q, '/');
251 		if (p != NULL)
252 			q = p + 1;
253 	}
254 	if (!oflag)
255 		outfile = q;
256 
257 	/* POSIX says "/dev/stdout" is a 'magic cookie' not a special file. */
258 	if (pflag || strcmp(outfile, "/dev/stdout") == 0)
259 		outfp = stdout;
260 	else {
261 		flags = O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW;
262 		if (lstat(outfile, &st) == 0) {
263 			if (iflag) {
264 				errno = EEXIST;
265 				warn("%s: %s", infile, outfile);
266 				return (0);
267 			}
268 			switch (st.st_mode & S_IFMT) {
269 			case S_IFREG:
270 			case S_IFLNK:
271 				/* avoid symlink attacks */
272 				if (unlink(outfile) == 0 || errno == ENOENT)
273 					break;
274 				warn("%s: unlink %s", infile, outfile);
275 				return (1);
276 			case S_IFDIR:
277 				errno = EISDIR;
278 				warn("%s: %s", infile, outfile);
279 				return (1);
280 			default:
281 				if (oflag) {
282 					/* trust command-line names */
283 					flags &= ~(O_EXCL|O_NOFOLLOW);
284 					break;
285 				}
286 				errno = EEXIST;
287 				warn("%s: %s", infile, outfile);
288 				return (1);
289 			}
290 		} else if (errno != ENOENT) {
291 			warn("%s: %s", infile, outfile);
292 			return (1);
293 		}
294 		if ((fd = open(outfile, flags, mode)) < 0 ||
295 		    (outfp = fdopen(fd, "w")) == NULL) {
296 			warn("%s: %s", infile, outfile);
297 			return (1);
298 		}
299 	}
300 
301 	if (base64)
302 		return (base64_decode());
303 	else
304 		return (uu_decode());
305 }
306 
307 static int
308 getline(char *buf, size_t size)
309 {
310 	if (fgets(buf, size, infp) != NULL)
311 		return (2);
312 	if (rflag)
313 		return (0);
314 	warnx("%s: %s: short file", infile, outfile);
315 	return (1);
316 }
317 
318 static int
319 checkend(const char *ptr, const char *end, const char *msg)
320 {
321 	size_t n;
322 
323 	n = strlen(end);
324 	if (strncmp(ptr, end, n) != 0 ||
325 	    strspn(ptr + n, " \t\r\n") != strlen(ptr + n)) {
326 		warnx("%s: %s: %s", infile, outfile, msg);
327 		return (1);
328 	}
329 	if (fclose(outfp) != 0) {
330 		warn("%s: %s", infile, outfile);
331 		return (1);
332 	}
333 	return (0);
334 }
335 
336 static int
337 uu_decode(void)
338 {
339 	int i, ch;
340 	char *p;
341 	char buf[MAXPATHLEN];
342 
343 	/* for each input line */
344 	for (;;) {
345 		switch (getline(buf, sizeof(buf))) {
346 		case 0:
347 			return (0);
348 		case 1:
349 			return (1);
350 		}
351 
352 #define	DEC(c)	(((c) - ' ') & 077)		/* single character decode */
353 #define IS_DEC(c) ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) )
354 
355 #define OUT_OF_RANGE do {						\
356 	warnx("%s: %s: character out of range: [%d-%d]",		\
357 	    infile, outfile, 1 + ' ', 077 + ' ' + 1);			\
358 	return (1);							\
359 } while (0)
360 
361 		/*
362 		 * `i' is used to avoid writing out all the characters
363 		 * at the end of the file.
364 		 */
365 		p = buf;
366 		if ((i = DEC(*p)) <= 0)
367 			break;
368 		for (++p; i > 0; p += 4, i -= 3)
369 			if (i >= 3) {
370 				if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) &&
371 				     IS_DEC(*(p + 2)) && IS_DEC(*(p + 3))))
372 					OUT_OF_RANGE;
373 
374 				ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
375 				putc(ch, outfp);
376 				ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
377 				putc(ch, outfp);
378 				ch = DEC(p[2]) << 6 | DEC(p[3]);
379 				putc(ch, outfp);
380 			}
381 			else {
382 				if (i >= 1) {
383 					if (!(IS_DEC(*p) && IS_DEC(*(p + 1))))
384 						OUT_OF_RANGE;
385 					ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
386 					putc(ch, outfp);
387 				}
388 				if (i >= 2) {
389 					if (!(IS_DEC(*(p + 1)) &&
390 					    IS_DEC(*(p + 2))))
391 						OUT_OF_RANGE;
392 
393 					ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
394 					putc(ch, outfp);
395 				}
396 				if (i >= 3) {
397 					if (!(IS_DEC(*(p + 2)) &&
398 					    IS_DEC(*(p + 3))))
399 						OUT_OF_RANGE;
400 					ch = DEC(p[2]) << 6 | DEC(p[3]);
401 					putc(ch, outfp);
402 				}
403 			}
404 	}
405 	switch (getline(buf, sizeof(buf))) {
406 	case 0:
407 		return (0);
408 	case 1:
409 		return (1);
410 	default:
411 		return (checkend(buf, "end", "no \"end\" line"));
412 	}
413 }
414 
415 static int
416 base64_decode(void)
417 {
418 	int n;
419 	char inbuf[MAXPATHLEN];
420 	unsigned char outbuf[MAXPATHLEN * 4];
421 
422 	for (;;) {
423 		switch (getline(inbuf, sizeof(inbuf))) {
424 		case 0:
425 			return (0);
426 		case 1:
427 			return (1);
428 		}
429 		n = b64_pton(inbuf, outbuf, sizeof(outbuf));
430 		if (n < 0)
431 			break;
432 		fwrite(outbuf, 1, n, outfp);
433 	}
434 	return (checkend(inbuf, "====",
435 		    "error decoding base64 input stream"));
436 }
437 
438 static void
439 usage(void)
440 {
441 	(void)fprintf(stderr,
442 	    "usage: uudecode [-cimprs] [file ...]\n"
443 	    "       uudecode [-i] -o output_file [file]\n"
444 	    "       b64decode [-cimprs] [file ...]\n"
445 	    "       b64decode [-i] -o output_file [file]\n");
446 	exit(1);
447 }
448