Initial import from FreeBSD RELENG_4:
[dragonfly.git] / usr.bin / uudecode / uudecode.c
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 #ifndef lint
35 static const char copyright[] =
36 "@(#) Copyright (c) 1983, 1993\n\
37         The Regents of the University of California.  All rights reserved.\n";
38 #endif /* not lint */
39
40 #if 0
41 #ifndef lint
42 static char sccsid[] = "@(#)uudecode.c  8.2 (Berkeley) 4/2/94";
43 #endif /* not lint */
44 #endif
45
46 #include <sys/cdefs.h>
47 __FBSDID("$FreeBSD: src/usr.bin/uudecode/uudecode.c,v 1.13.2.6 2003/04/07 20:10:33 fanf Exp $");
48
49 /*
50  * uudecode [file ...]
51  *
52  * create the specified file, decoding as you go.
53  * used with uuencode.
54  */
55 #include <sys/param.h>
56 #include <sys/socket.h>
57 #include <sys/stat.h>
58
59 #include <netinet/in.h>
60
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
72 static const char *infile, *outfile;
73 static FILE *infp, *outfp;
74 static int base64, cflag, iflag, oflag, pflag, rflag, sflag;
75
76 static void     usage(void);
77 static int      decode(void);
78 static int      decode2(void);
79 static int      uu_decode(void);
80 static int      base64_decode(void);
81
82 int
83 main(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) {
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
152 static int
153 decode(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
179 static int
180 decode2(void)
181 {
182         int flags, fd, mode;
183         size_t n, m;
184         char *p, *q;
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
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) & 0666;
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;
258
259         /* POSIX says "/dev/stdout" is a 'magic cookie' not a special file. */
260         if (pflag || strcmp(outfile, "/dev/stdout") == 0)
261                 outfp = stdout;
262         else {
263                 flags = O_WRONLY|O_CREAT|O_EXCL;
264                 if (lstat(outfile, &st) == 0) {
265                         if (iflag) {
266                                 warnc(EEXIST, "%s: %s", infile, outfile);
267                                 return (0);
268                         }
269                         switch (st.st_mode & S_IFMT) {
270                         case S_IFREG:
271                         case S_IFLNK:
272                                 /* avoid symlink attacks */
273                                 if (unlink(outfile) == 0 || errno == ENOENT)
274                                         break;
275                                 warn("%s: unlink %s", infile, outfile);
276                                 return (1);
277                         case S_IFDIR:
278                                 warnc(EISDIR, "%s: %s", infile, outfile);
279                                 return (1);
280                         default:
281                                 if (oflag) {
282                                         /* trust command-line names */
283                                         flags &= ~O_EXCL;
284                                         break;
285                                 }
286                                 warnc(EEXIST, "%s: %s", infile, outfile);
287                                 return (1);
288                         }
289                 } else if (errno != ENOENT) {
290                         warn("%s: %s", infile, outfile);
291                         return (1);
292                 }
293                 if ((fd = open(outfile, flags, mode)) < 0 ||
294                     (outfp = fdopen(fd, "w")) == NULL) {
295                         warn("%s: %s", infile, outfile);
296                         return (1);
297                 }
298         }
299
300         if (base64)
301                 return (base64_decode());
302         else
303                 return (uu_decode());
304 }
305
306 static int
307 getline(char *buf, size_t size)
308 {
309         if (fgets(buf, size, infp) != NULL)
310                 return (2);
311         if (rflag)
312                 return (0);
313         warnx("%s: %s: short file", infile, outfile);
314         return (1);
315 }
316
317 static int
318 checkend(const char *ptr, const char *end, const char *msg)
319 {
320         size_t n;
321
322         n = strlen(end);
323         if (strncmp(ptr, end, n) != 0 ||
324             strspn(ptr + n, " \t\r\n") != strlen(ptr + n)) {
325                 warnx("%s: %s: %s", infile, outfile, msg);
326                 return (1);
327         }
328         if (fclose(outfp) != 0) {
329                 warn("%s: %s", infile, outfile);
330                 return (1);
331         }
332         return (0);
333 }
334
335 static int
336 uu_decode(void)
337 {
338         int i, ch;
339         char *p;
340         char buf[MAXPATHLEN+1];
341
342         /* for each input line */
343         for (;;) {
344                 switch (getline(buf, sizeof(buf))) {
345                 case 0: return (0);
346                 case 1: return (1);
347                 }
348
349 #define DEC(c)  (((c) - ' ') & 077)             /* single character decode */
350 #define IS_DEC(c) ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) )
351
352 #define OUT_OF_RANGE do {                                               \
353         warnx("%s: %s: character out of range: [%d-%d]",                \
354             infile, outfile, 1 + ' ', 077 + ' ' + 1);                   \
355         return (1);                                                     \
356 } while (0)
357
358                 /*
359                  * `i' is used to avoid writing out all the characters
360                  * at the end of the file.
361                  */
362                 p = buf;
363                 if ((i = DEC(*p)) <= 0)
364                         break;
365                 for (++p; i > 0; p += 4, i -= 3)
366                         if (i >= 3) {
367                                 if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) &&
368                                      IS_DEC(*(p + 2)) && IS_DEC(*(p + 3))))
369                                         OUT_OF_RANGE;
370
371                                 ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
372                                 putc(ch, outfp);
373                                 ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
374                                 putc(ch, outfp);
375                                 ch = DEC(p[2]) << 6 | DEC(p[3]);
376                                 putc(ch, outfp);
377                         }
378                         else {
379                                 if (i >= 1) {
380                                         if (!(IS_DEC(*p) && IS_DEC(*(p + 1))))
381                                                 OUT_OF_RANGE;
382                                         ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
383                                         putc(ch, outfp);
384                                 }
385                                 if (i >= 2) {
386                                         if (!(IS_DEC(*(p + 1)) &&
387                                                 IS_DEC(*(p + 2))))
388                                                 OUT_OF_RANGE;
389
390                                         ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
391                                         putc(ch, outfp);
392                                 }
393                                 if (i >= 3) {
394                                         if (!(IS_DEC(*(p + 2)) &&
395                                                 IS_DEC(*(p + 3))))
396                                                 OUT_OF_RANGE;
397                                         ch = DEC(p[2]) << 6 | DEC(p[3]);
398                                         putc(ch, outfp);
399                                 }
400                         }
401         }
402         switch (getline(buf, sizeof(buf))) {
403         case 0:  return (0);
404         case 1:  return (1);
405         default: return (checkend(buf, "end", "no \"end\" line"));
406         }
407 }
408
409 static int
410 base64_decode(void)
411 {
412         int n;
413         char inbuf[MAXPATHLEN+1];
414         unsigned char outbuf[MAXPATHLEN * 4];
415
416         for (;;) {
417                 switch (getline(inbuf, sizeof(inbuf))) {
418                 case 0: return (0);
419                 case 1: return (1);
420                 }
421                 n = b64_pton(inbuf, outbuf, sizeof(outbuf));
422                 if (n < 0)
423                         break;
424                 fwrite(outbuf, 1, n, outfp);
425         }
426         return (checkend(inbuf, "====",
427                     "error decoding base64 input stream"));
428 }
429
430 static void
431 usage(void)
432 {
433         (void)fprintf(stderr,
434 "usage: uudecode [-cimprs] [file ...]\n"
435 "       uudecode [-i] -o output_file [file]\n"
436 "       b64decode [-cimprs] [file ...]\n"
437 "       b64decode [-i] -o output_file [file]\n");
438         exit(1);
439 }