]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/bintrans/uudecode.c
bintrans: move files to a new directory
[FreeBSD/FreeBSD.git] / usr.bin / bintrans / 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 <stdbool.h>
66 #include <stdio.h>
67 #include <stdlib.h>
68 #include <string.h>
69 #include <unistd.h>
70
71 extern int main_decode(int, char *[]);
72
73 static const char *infile, *outfile;
74 static FILE *infp, *outfp;
75 static bool base64, cflag, iflag, oflag, pflag, rflag, sflag;
76
77 static void     usage(void);
78 static int      decode(void);
79 static int      decode2(void);
80 static int      uu_decode(void);
81 static int      base64_decode(void);
82
83 int
84 main_decode(int argc, char *argv[])
85 {
86         int rval, ch;
87
88         if (strcmp(basename(argv[0]), "b64decode") == 0)
89                 base64 = true;
90
91         while ((ch = getopt(argc, argv, "cimo:prs")) != -1) {
92                 switch (ch) {
93                 case 'c':
94                         if (oflag || rflag)
95                                 usage();
96                         cflag = true; /* multiple uudecode'd files */
97                         break;
98                 case 'i':
99                         iflag = true; /* ask before override files */
100                         break;
101                 case 'm':
102                         base64 = true;
103                         break;
104                 case 'o':
105                         if (cflag || pflag || rflag || sflag)
106                                 usage();
107                         oflag = true; /* output to the specified file */
108                         sflag = true; /* do not strip pathnames for output */
109                         outfile = optarg; /* set the output filename */
110                         break;
111                 case 'p':
112                         if (oflag)
113                                 usage();
114                         pflag = true; /* print output to stdout */
115                         break;
116                 case 'r':
117                         if (cflag || oflag)
118                                 usage();
119                         rflag = true; /* decode raw data */
120                         break;
121                 case 's':
122                         if (oflag)
123                                 usage();
124                         sflag = true; /* do not strip pathnames for output */
125                         break;
126                 default:
127                         usage();
128                 }
129         }
130         argc -= optind;
131         argv += optind;
132
133         if (*argv != NULL) {
134                 rval = 0;
135                 do {
136                         infp = fopen(infile = *argv, "r");
137                         if (infp == NULL) {
138                                 warn("%s", *argv);
139                                 rval = 1;
140                                 continue;
141                         }
142                         rval |= decode();
143                         fclose(infp);
144                 } while (*++argv);
145         } else {
146                 infile = "stdin";
147                 infp = stdin;
148                 rval = decode();
149         }
150         exit(rval);
151 }
152
153 static int
154 decode(void)
155 {
156         int r, v;
157
158         if (rflag) {
159                 /* relaxed alternative to decode2() */
160                 outfile = "/dev/stdout";
161                 outfp = stdout;
162                 if (base64)
163                         return (base64_decode());
164                 else
165                         return (uu_decode());
166         }
167         v = decode2();
168         if (v == EOF) {
169                 warnx("%s: missing or bad \"begin\" line", infile);
170                 return (1);
171         }
172         for (r = v; cflag; r |= v) {
173                 v = decode2();
174                 if (v == EOF)
175                         break;
176         }
177         return (r);
178 }
179
180 static int
181 decode2(void)
182 {
183         int flags, fd, mode;
184         size_t n, m;
185         char *p, *q;
186         void *handle;
187         struct passwd *pw;
188         struct stat st;
189         char buf[MAXPATHLEN + 1];
190
191         base64 = false;
192         /* search for header line */
193         for (;;) {
194                 if (fgets(buf, sizeof(buf), infp) == NULL)
195                         return (EOF);
196                 p = buf;
197                 if (strncmp(p, "begin-base64 ", 13) == 0) {
198                         base64 = true;
199                         p += 13;
200                 } else if (strncmp(p, "begin ", 6) == 0)
201                         p += 6;
202                 else
203                         continue;
204                 /* p points to mode */
205                 q = strchr(p, ' ');
206                 if (q == NULL)
207                         continue;
208                 *q++ = '\0';
209                 /* q points to filename */
210                 n = strlen(q);
211                 while (n > 0 && (q[n-1] == '\n' || q[n-1] == '\r'))
212                         q[--n] = '\0';
213                 /* found valid header? */
214                 if (n > 0)
215                         break;
216         }
217
218         handle = setmode(p);
219         if (handle == NULL) {
220                 warnx("%s: unable to parse file mode", infile);
221                 return (1);
222         }
223         mode = getmode(handle, 0) & 0666;
224         free(handle);
225
226         if (sflag) {
227                 /* don't strip, so try ~user/file expansion */
228                 p = NULL;
229                 pw = NULL;
230                 if (*q == '~')
231                         p = strchr(q, '/');
232                 if (p != NULL) {
233                         *p = '\0';
234                         pw = getpwnam(q + 1);
235                         *p = '/';
236                 }
237                 if (pw != NULL) {
238                         n = strlen(pw->pw_dir);
239                         if (buf + n > p) {
240                                 /* make room */
241                                 m = strlen(p);
242                                 if (sizeof(buf) < n + m) {
243                                         warnx("%s: bad output filename",
244                                             infile);
245                                         return (1);
246                                 }
247                                 p = memmove(buf + n, p, m);
248                         }
249                         q = memcpy(p - n, pw->pw_dir, n);
250                 }
251         } else {
252                 /* strip down to leaf name */
253                 p = strrchr(q, '/');
254                 if (p != NULL)
255                         q = p + 1;
256         }
257         if (!oflag)
258                 outfile = q;
259
260         /* POSIX says "/dev/stdout" is a 'magic cookie' not a special file. */
261         if (pflag || strcmp(outfile, "/dev/stdout") == 0)
262                 outfp = stdout;
263         else {
264                 flags = O_WRONLY | O_CREAT | O_EXCL;
265                 if (lstat(outfile, &st) == 0) {
266                         if (iflag) {
267                                 warnc(EEXIST, "%s: %s", infile, outfile);
268                                 return (0);
269                         }
270                         switch (st.st_mode & S_IFMT) {
271                         case S_IFREG:
272                         case S_IFLNK:
273                                 /* avoid symlink attacks */
274                                 if (unlink(outfile) == 0 || errno == ENOENT)
275                                         break;
276                                 warn("%s: unlink %s", infile, outfile);
277                                 return (1);
278                         case S_IFDIR:
279                                 warnc(EISDIR, "%s: %s", infile, outfile);
280                                 return (1);
281                         default:
282                                 if (oflag) {
283                                         /* trust command-line names */
284                                         flags &= ~O_EXCL;
285                                         break;
286                                 }
287                                 warnc(EEXIST, "%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 get_line(char *buf, size_t size)
309 {
310
311         if (fgets(buf, size, infp) != NULL)
312                 return (2);
313         if (rflag)
314                 return (0);
315         warnx("%s: %s: short file", infile, outfile);
316         return (1);
317 }
318
319 static int
320 checkend(const char *ptr, const char *end, const char *msg)
321 {
322         size_t n;
323
324         n = strlen(end);
325         if (strncmp(ptr, end, n) != 0 ||
326             strspn(ptr + n, " \t\r\n") != strlen(ptr + n)) {
327                 warnx("%s: %s: %s", infile, outfile, msg);
328                 return (1);
329         }
330         if (fclose(outfp) != 0) {
331                 warn("%s: %s", infile, outfile);
332                 return (1);
333         }
334         return (0);
335 }
336
337 static int
338 uu_decode(void)
339 {
340         int i, ch;
341         char *p;
342         char buf[MAXPATHLEN+1];
343
344         /* for each input line */
345         for (;;) {
346                 switch (get_line(buf, sizeof(buf))) {
347                 case 0:
348                         return (0);
349                 case 1:
350                         return (1);
351                 }
352
353 #define DEC(c)          (((c) - ' ') & 077)     /* single character decode */
354 #define IS_DEC(c)        ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) )
355
356 #define OUT_OF_RANGE do {                                               \
357         warnx("%s: %s: character out of range: [%d-%d]",                \
358             infile, outfile, ' ', 077 + ' ' + 1);                       \
359         return (1);                                                     \
360 } while (0)
361
362                 /*
363                  * `i' is used to avoid writing out all the characters
364                  * at the end of the file.
365                  */
366                 p = buf;
367                 if ((i = DEC(*p)) <= 0)
368                         break;
369                 for (++p; i > 0; p += 4, i -= 3)
370                         if (i >= 3) {
371                                 if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) &&
372                                     IS_DEC(*(p + 2)) && IS_DEC(*(p + 3))))
373                                         OUT_OF_RANGE;
374
375                                 ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
376                                 putc(ch, outfp);
377                                 ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
378                                 putc(ch, outfp);
379                                 ch = DEC(p[2]) << 6 | DEC(p[3]);
380                                 putc(ch, outfp);
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 (get_line(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, count, count4;
419         char inbuf[MAXPATHLEN + 1], *p;
420         unsigned char outbuf[MAXPATHLEN * 4];
421         char leftover[MAXPATHLEN + 1];
422
423         leftover[0] = '\0';
424         for (;;) {
425                 strcpy(inbuf, leftover);
426                 switch (get_line(inbuf + strlen(inbuf),
427                     sizeof(inbuf) - strlen(inbuf))) {
428                 case 0:
429                         return (0);
430                 case 1:
431                         return (1);
432                 }
433
434                 count = 0;
435                 count4 = -1;
436                 p = inbuf;
437                 while (*p != '\0') {
438                         /*
439                          * Base64 encoded strings have the following
440                          * characters in them: A-Z, a-z, 0-9 and +, / and =
441                          */
442                         if (isalnum(*p) || *p == '+' || *p == '/' || *p == '=')
443                                 count++;
444                         if (count % 4 == 0)
445                                 count4 = p - inbuf;
446                         p++;
447                 }
448
449                 strcpy(leftover, inbuf + count4 + 1);
450                 inbuf[count4 + 1] = 0;
451
452                 n = b64_pton(inbuf, outbuf, sizeof(outbuf));
453
454                 if (n < 0)
455                         break;
456                 fwrite(outbuf, 1, n, outfp);
457         }
458         return (checkend(inbuf, "====", "error decoding base64 input stream"));
459 }
460
461 static void
462 usage(void)
463 {
464
465         (void)fprintf(stderr,
466             "usage: uudecode [-cimprs] [file ...]\n"
467             "       uudecode [-i] -o output_file [file]\n"
468             "       b64decode [-cimprs] [file ...]\n"
469             "       b64decode [-i] -o output_file [file]\n");
470         exit(1);
471 }