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