Add the DragonFly cvs id and perform general cleanups on cvs/rcs/sccs ids. Most
[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. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by the University of
16  *      California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * @(#) Copyright (c) 1983, 1993 The Regents of the University of California.  All rights reserved.
34  * @(#)uudecode.c       8.2 (Berkeley) 4/2/94
35  * $FreeBSD: src/usr.bin/uudecode/uudecode.c,v 1.13.2.6 2003/04/07 20:10:33 fanf Exp $
36  * $DragonFly: src/usr.bin/uudecode/uudecode.c,v 1.2 2003/06/17 04:29:33 dillon Exp $
37  */
38
39 /*
40  * uudecode [file ...]
41  *
42  * create the specified file, decoding as you go.
43  * used with uuencode.
44  */
45 #include <sys/param.h>
46 #include <sys/socket.h>
47 #include <sys/stat.h>
48
49 #include <netinet/in.h>
50
51 #include <err.h>
52 #include <errno.h>
53 #include <fcntl.h>
54 #include <libgen.h>
55 #include <pwd.h>
56 #include <resolv.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <unistd.h>
61
62 static const char *infile, *outfile;
63 static FILE *infp, *outfp;
64 static int base64, cflag, iflag, oflag, pflag, rflag, sflag;
65
66 static void     usage(void);
67 static int      decode(void);
68 static int      decode2(void);
69 static int      uu_decode(void);
70 static int      base64_decode(void);
71
72 int
73 main(int argc, char *argv[])
74 {
75         int rval, ch;
76
77         if (strcmp(basename(argv[0]), "b64decode") == 0)
78                 base64 = 1;
79
80         while ((ch = getopt(argc, argv, "cimo:prs")) != -1) {
81                 switch(ch) {
82                 case 'c':
83                         if (oflag || rflag)
84                                 usage();
85                         cflag = 1; /* multiple uudecode'd files */
86                         break;
87                 case 'i':
88                         iflag = 1; /* ask before override files */
89                         break;
90                 case 'm':
91                         base64 = 1;
92                         break;
93                 case 'o':
94                         if (cflag || pflag || rflag || sflag)
95                                 usage();
96                         oflag = 1; /* output to the specified file */
97                         sflag = 1; /* do not strip pathnames for output */
98                         outfile = optarg; /* set the output filename */
99                         break;
100                 case 'p':
101                         if (oflag)
102                                 usage();
103                         pflag = 1; /* print output to stdout */
104                         break;
105                 case 'r':
106                         if (cflag || oflag)
107                                 usage();
108                         rflag = 1; /* decode raw data */
109                         break;
110                 case 's':
111                         if (oflag)
112                                 usage();
113                         sflag = 1; /* do not strip pathnames for output */
114                         break;
115                 default:
116                         usage();
117                 }
118         }
119         argc -= optind;
120         argv += optind;
121
122         if (*argv) {
123                 rval = 0;
124                 do {
125                         infp = fopen(infile = *argv, "r");
126                         if (infp == NULL) {
127                                 warn("%s", *argv);
128                                 rval = 1;
129                                 continue;
130                         }
131                         rval |= decode();
132                         fclose(infp);
133                 } while (*++argv);
134         } else {
135                 infile = "stdin";
136                 infp = stdin;
137                 rval = decode();
138         }
139         exit(rval);
140 }
141
142 static int
143 decode(void)
144 {
145         int r, v;
146
147         if (rflag) {
148                 /* relaxed alternative to decode2() */
149                 outfile = "/dev/stdout";
150                 outfp = stdout;
151                 if (base64)
152                         return (base64_decode());
153                 else
154                         return (uu_decode());
155         }
156         v = decode2();
157         if (v == EOF) {
158                 warnx("%s: missing or bad \"begin\" line", infile);
159                 return (1);
160         }
161         for (r = v; cflag; r |= v) {
162                 v = decode2();
163                 if (v == EOF)
164                         break;
165         }
166         return (r);
167 }
168
169 static int
170 decode2(void)
171 {
172         int flags, fd, mode;
173         size_t n, m;
174         char *p, *q;
175         void *handle;
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         handle = setmode(p);
208         if (handle == NULL) {
209                 warnx("%s: unable to parse file mode", infile);
210                 return (1);
211         }
212         mode = getmode(handle, 0) & 0666;
213         free(handle);
214
215         if (sflag) {
216                 /* don't strip, so try ~user/file expansion */
217                 p = NULL;
218                 pw = NULL;
219                 if (*q == '~')
220                         p = strchr(q, '/');
221                 if (p != NULL) {
222                         *p = '\0';
223                         pw = getpwnam(q + 1);
224                         *p = '/';
225                 }
226                 if (pw != NULL) {
227                         n = strlen(pw->pw_dir);
228                         if (buf + n > p) {
229                                 /* make room */
230                                 m = strlen(p);
231                                 if (sizeof(buf) < n + m) {
232                                         warnx("%s: bad output filename",
233                                             infile);
234                                         return (1);
235                                 }
236                                 p = memmove(buf + n, p, m);
237                         }
238                         q = memcpy(p - n, pw->pw_dir, n);
239                 }
240         } else {
241                 /* strip down to leaf name */
242                 p = strrchr(q, '/');
243                 if (p != NULL)
244                         q = p + 1;
245         }
246         if (!oflag)
247                 outfile = q;
248
249         /* POSIX says "/dev/stdout" is a 'magic cookie' not a special file. */
250         if (pflag || strcmp(outfile, "/dev/stdout") == 0)
251                 outfp = stdout;
252         else {
253                 flags = O_WRONLY|O_CREAT|O_EXCL;
254                 if (lstat(outfile, &st) == 0) {
255                         if (iflag) {
256                                 warnc(EEXIST, "%s: %s", infile, outfile);
257                                 return (0);
258                         }
259                         switch (st.st_mode & S_IFMT) {
260                         case S_IFREG:
261                         case S_IFLNK:
262                                 /* avoid symlink attacks */
263                                 if (unlink(outfile) == 0 || errno == ENOENT)
264                                         break;
265                                 warn("%s: unlink %s", infile, outfile);
266                                 return (1);
267                         case S_IFDIR:
268                                 warnc(EISDIR, "%s: %s", infile, outfile);
269                                 return (1);
270                         default:
271                                 if (oflag) {
272                                         /* trust command-line names */
273                                         flags &= ~O_EXCL;
274                                         break;
275                                 }
276                                 warnc(EEXIST, "%s: %s", infile, outfile);
277                                 return (1);
278                         }
279                 } else if (errno != ENOENT) {
280                         warn("%s: %s", infile, outfile);
281                         return (1);
282                 }
283                 if ((fd = open(outfile, flags, mode)) < 0 ||
284                     (outfp = fdopen(fd, "w")) == NULL) {
285                         warn("%s: %s", infile, outfile);
286                         return (1);
287                 }
288         }
289
290         if (base64)
291                 return (base64_decode());
292         else
293                 return (uu_decode());
294 }
295
296 static int
297 getline(char *buf, size_t size)
298 {
299         if (fgets(buf, size, infp) != NULL)
300                 return (2);
301         if (rflag)
302                 return (0);
303         warnx("%s: %s: short file", infile, outfile);
304         return (1);
305 }
306
307 static int
308 checkend(const char *ptr, const char *end, const char *msg)
309 {
310         size_t n;
311
312         n = strlen(end);
313         if (strncmp(ptr, end, n) != 0 ||
314             strspn(ptr + n, " \t\r\n") != strlen(ptr + n)) {
315                 warnx("%s: %s: %s", infile, outfile, msg);
316                 return (1);
317         }
318         if (fclose(outfp) != 0) {
319                 warn("%s: %s", infile, outfile);
320                 return (1);
321         }
322         return (0);
323 }
324
325 static int
326 uu_decode(void)
327 {
328         int i, ch;
329         char *p;
330         char buf[MAXPATHLEN+1];
331
332         /* for each input line */
333         for (;;) {
334                 switch (getline(buf, sizeof(buf))) {
335                 case 0: return (0);
336                 case 1: return (1);
337                 }
338
339 #define DEC(c)  (((c) - ' ') & 077)             /* single character decode */
340 #define IS_DEC(c) ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) )
341
342 #define OUT_OF_RANGE do {                                               \
343         warnx("%s: %s: character out of range: [%d-%d]",                \
344             infile, outfile, 1 + ' ', 077 + ' ' + 1);                   \
345         return (1);                                                     \
346 } while (0)
347
348                 /*
349                  * `i' is used to avoid writing out all the characters
350                  * at the end of the file.
351                  */
352                 p = buf;
353                 if ((i = DEC(*p)) <= 0)
354                         break;
355                 for (++p; i > 0; p += 4, i -= 3)
356                         if (i >= 3) {
357                                 if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) &&
358                                      IS_DEC(*(p + 2)) && IS_DEC(*(p + 3))))
359                                         OUT_OF_RANGE;
360
361                                 ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
362                                 putc(ch, outfp);
363                                 ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
364                                 putc(ch, outfp);
365                                 ch = DEC(p[2]) << 6 | DEC(p[3]);
366                                 putc(ch, outfp);
367                         }
368                         else {
369                                 if (i >= 1) {
370                                         if (!(IS_DEC(*p) && IS_DEC(*(p + 1))))
371                                                 OUT_OF_RANGE;
372                                         ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
373                                         putc(ch, outfp);
374                                 }
375                                 if (i >= 2) {
376                                         if (!(IS_DEC(*(p + 1)) &&
377                                                 IS_DEC(*(p + 2))))
378                                                 OUT_OF_RANGE;
379
380                                         ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
381                                         putc(ch, outfp);
382                                 }
383                                 if (i >= 3) {
384                                         if (!(IS_DEC(*(p + 2)) &&
385                                                 IS_DEC(*(p + 3))))
386                                                 OUT_OF_RANGE;
387                                         ch = DEC(p[2]) << 6 | DEC(p[3]);
388                                         putc(ch, outfp);
389                                 }
390                         }
391         }
392         switch (getline(buf, sizeof(buf))) {
393         case 0:  return (0);
394         case 1:  return (1);
395         default: return (checkend(buf, "end", "no \"end\" line"));
396         }
397 }
398
399 static int
400 base64_decode(void)
401 {
402         int n;
403         char inbuf[MAXPATHLEN+1];
404         unsigned char outbuf[MAXPATHLEN * 4];
405
406         for (;;) {
407                 switch (getline(inbuf, sizeof(inbuf))) {
408                 case 0: return (0);
409                 case 1: return (1);
410                 }
411                 n = b64_pton(inbuf, outbuf, sizeof(outbuf));
412                 if (n < 0)
413                         break;
414                 fwrite(outbuf, 1, n, outfp);
415         }
416         return (checkend(inbuf, "====",
417                     "error decoding base64 input stream"));
418 }
419
420 static void
421 usage(void)
422 {
423         (void)fprintf(stderr,
424 "usage: uudecode [-cimprs] [file ...]\n"
425 "       uudecode [-i] -o output_file [file]\n"
426 "       b64decode [-cimprs] [file ...]\n"
427 "       b64decode [-i] -o output_file [file]\n");
428         exit(1);
429 }