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