]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/uudecode/uudecode.c
THIS BRANCH IS OBSOLETE, PLEASE READ:
[FreeBSD/FreeBSD.git] / usr.bin / uudecode / uudecode.c
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1983, 1993
5  *      The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31
32 #if 0
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 static char sccsid[] = "@(#)uudecode.c  8.2 (Berkeley) 4/2/94";
41 #endif /* not lint */
42 #endif
43 #include <sys/cdefs.h>
44 __FBSDID("$FreeBSD$");
45
46 /*
47  * uudecode [file ...]
48  *
49  * create the specified file, decoding as you go.
50  * used with uuencode.
51  */
52 #include <sys/param.h>
53 #include <sys/socket.h>
54 #include <sys/stat.h>
55
56 #include <netinet/in.h>
57
58 #include <ctype.h>
59 #include <err.h>
60 #include <errno.h>
61 #include <fcntl.h>
62 #include <libgen.h>
63 #include <pwd.h>
64 #include <resolv.h>
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68 #include <unistd.h>
69
70 static const char *infile, *outfile;
71 static FILE *infp, *outfp;
72 static int base64, cflag, iflag, oflag, pflag, rflag, sflag;
73
74 static void     usage(void);
75 static int      decode(void);
76 static int      decode2(void);
77 static int      uu_decode(void);
78 static int      base64_decode(void);
79
80 int
81 main(int argc, char *argv[])
82 {
83         int rval, ch;
84
85         if (strcmp(basename(argv[0]), "b64decode") == 0)
86                 base64 = 1;
87
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 != NULL) {
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 + 1];
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;
262                 if (lstat(outfile, &st) == 0) {
263                         if (iflag) {
264                                 warnc(EEXIST, "%s: %s", infile, outfile);
265                                 return (0);
266                         }
267                         switch (st.st_mode & S_IFMT) {
268                         case S_IFREG:
269                         case S_IFLNK:
270                                 /* avoid symlink attacks */
271                                 if (unlink(outfile) == 0 || errno == ENOENT)
272                                         break;
273                                 warn("%s: unlink %s", infile, outfile);
274                                 return (1);
275                         case S_IFDIR:
276                                 warnc(EISDIR, "%s: %s", infile, outfile);
277                                 return (1);
278                         default:
279                                 if (oflag) {
280                                         /* trust command-line names */
281                                         flags &= ~O_EXCL;
282                                         break;
283                                 }
284                                 warnc(EEXIST, "%s: %s", infile, outfile);
285                                 return (1);
286                         }
287                 } else if (errno != ENOENT) {
288                         warn("%s: %s", infile, outfile);
289                         return (1);
290                 }
291                 if ((fd = open(outfile, flags, mode)) < 0 ||
292                     (outfp = fdopen(fd, "w")) == NULL) {
293                         warn("%s: %s", infile, outfile);
294                         return (1);
295                 }
296         }
297
298         if (base64)
299                 return (base64_decode());
300         else
301                 return (uu_decode());
302 }
303
304 static int
305 get_line(char *buf, size_t size)
306 {
307
308         if (fgets(buf, size, infp) != NULL)
309                 return (2);
310         if (rflag)
311                 return (0);
312         warnx("%s: %s: short file", infile, outfile);
313         return (1);
314 }
315
316 static int
317 checkend(const char *ptr, const char *end, const char *msg)
318 {
319         size_t n;
320
321         n = strlen(end);
322         if (strncmp(ptr, end, n) != 0 ||
323             strspn(ptr + n, " \t\r\n") != strlen(ptr + n)) {
324                 warnx("%s: %s: %s", infile, outfile, msg);
325                 return (1);
326         }
327         if (fclose(outfp) != 0) {
328                 warn("%s: %s", infile, outfile);
329                 return (1);
330         }
331         return (0);
332 }
333
334 static int
335 uu_decode(void)
336 {
337         int i, ch;
338         char *p;
339         char buf[MAXPATHLEN+1];
340
341         /* for each input line */
342         for (;;) {
343                 switch (get_line(buf, sizeof(buf))) {
344                 case 0:
345                         return (0);
346                 case 1:
347                         return (1);
348                 }
349
350 #define DEC(c)          (((c) - ' ') & 077)     /* single character decode */
351 #define IS_DEC(c)        ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) )
352
353 #define OUT_OF_RANGE do {                                               \
354         warnx("%s: %s: character out of range: [%d-%d]",                \
355             infile, outfile, 1 + ' ', 077 + ' ' + 1);                   \
356         return (1);                                                     \
357 } while (0)
358
359                 /*
360                  * `i' is used to avoid writing out all the characters
361                  * at the end of the file.
362                  */
363                 p = buf;
364                 if ((i = DEC(*p)) <= 0)
365                         break;
366                 for (++p; i > 0; p += 4, i -= 3)
367                         if (i >= 3) {
368                                 if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) &&
369                                     IS_DEC(*(p + 2)) && IS_DEC(*(p + 3))))
370                                         OUT_OF_RANGE;
371
372                                 ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
373                                 putc(ch, outfp);
374                                 ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
375                                 putc(ch, outfp);
376                                 ch = DEC(p[2]) << 6 | DEC(p[3]);
377                                 putc(ch, outfp);
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 (get_line(buf, sizeof(buf))) {
403         case 0:
404                 return (0);
405         case 1:
406                 return (1);
407         default:
408                 return (checkend(buf, "end", "no \"end\" line"));
409         }
410 }
411
412 static int
413 base64_decode(void)
414 {
415         int n, count, count4;
416         char inbuf[MAXPATHLEN + 1], *p;
417         unsigned char outbuf[MAXPATHLEN * 4];
418         char leftover[MAXPATHLEN + 1];
419
420         leftover[0] = '\0';
421         for (;;) {
422                 strcpy(inbuf, leftover);
423                 switch (get_line(inbuf + strlen(inbuf),
424                     sizeof(inbuf) - strlen(inbuf))) {
425                 case 0:
426                         return (0);
427                 case 1:
428                         return (1);
429                 }
430
431                 count = 0;
432                 count4 = -1;
433                 p = inbuf;
434                 while (*p != '\0') {
435                         /*
436                          * Base64 encoded strings have the following
437                          * characters in them: A-Z, a-z, 0-9 and +, / and =
438                          */
439                         if (isalnum(*p) || *p == '+' || *p == '/' || *p == '=')
440                                 count++;
441                         if (count % 4 == 0)
442                                 count4 = p - inbuf;
443                         p++;
444                 }
445
446                 strcpy(leftover, inbuf + count4 + 1);
447                 inbuf[count4 + 1] = 0;
448
449                 n = b64_pton(inbuf, outbuf, sizeof(outbuf));
450
451                 if (n < 0)
452                         break;
453                 fwrite(outbuf, 1, n, outfp);
454         }
455         return (checkend(inbuf, "====", "error decoding base64 input stream"));
456 }
457
458 static void
459 usage(void)
460 {
461
462         (void)fprintf(stderr,
463             "usage: uudecode [-cimprs] [file ...]\n"
464             "       uudecode [-i] -o output_file [file]\n"
465             "       b64decode [-cimprs] [file ...]\n"
466             "       b64decode [-i] -o output_file [file]\n");
467         exit(1);
468 }