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