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