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