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