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 if (fclose(outfp) != 0) { 335 warn("%s: %s", infile, outfile); 336 return (1); 337 } 338 return (0); 339 } 340 341 static int 342 uu_decode(void) 343 { 344 int i, ch; 345 char *p; 346 char buf[MAXPATHLEN+1]; 347 348 /* for each input line */ 349 for (;;) { 350 switch (get_line(buf, sizeof(buf))) { 351 case 0: 352 return (0); 353 case 1: 354 return (1); 355 } 356 357 #define DEC(c) (((c) - ' ') & 077) /* single character decode */ 358 #define IS_DEC(c) ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) ) 359 360 #define OUT_OF_RANGE do { \ 361 warnx("%s: %s: character out of range: [%d-%d]", \ 362 infile, outfile, ' ', 077 + ' ' + 1); \ 363 return (1); \ 364 } while (0) 365 366 /* 367 * `i' is used to avoid writing out all the characters 368 * at the end of the file. 369 */ 370 p = buf; 371 if ((i = DEC(*p)) <= 0) 372 break; 373 for (++p; i > 0; p += 4, i -= 3) 374 if (i >= 3) { 375 if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) && 376 IS_DEC(*(p + 2)) && IS_DEC(*(p + 3)))) 377 OUT_OF_RANGE; 378 379 ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; 380 putc(ch, outfp); 381 ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; 382 putc(ch, outfp); 383 ch = DEC(p[2]) << 6 | DEC(p[3]); 384 putc(ch, outfp); 385 } else { 386 if (i >= 1) { 387 if (!(IS_DEC(*p) && IS_DEC(*(p + 1)))) 388 OUT_OF_RANGE; 389 ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; 390 putc(ch, outfp); 391 } 392 if (i >= 2) { 393 if (!(IS_DEC(*(p + 1)) && 394 IS_DEC(*(p + 2)))) 395 OUT_OF_RANGE; 396 397 ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; 398 putc(ch, outfp); 399 } 400 if (i >= 3) { 401 if (!(IS_DEC(*(p + 2)) && 402 IS_DEC(*(p + 3)))) 403 OUT_OF_RANGE; 404 ch = DEC(p[2]) << 6 | DEC(p[3]); 405 putc(ch, outfp); 406 } 407 } 408 } 409 switch (get_line(buf, sizeof(buf))) { 410 case 0: 411 return (0); 412 case 1: 413 return (1); 414 default: 415 return (checkend(buf, "end", "no \"end\" line")); 416 } 417 } 418 419 static int 420 base64_decode(void) 421 { 422 int n, count, count4; 423 char inbuf[MAXPATHLEN + 1], *p; 424 unsigned char outbuf[MAXPATHLEN * 4]; 425 char leftover[MAXPATHLEN + 1]; 426 427 leftover[0] = '\0'; 428 for (;;) { 429 strcpy(inbuf, leftover); 430 switch (get_line(inbuf + strlen(inbuf), 431 sizeof(inbuf) - strlen(inbuf))) { 432 case 0: 433 return (0); 434 case 1: 435 return (1); 436 } 437 438 count = 0; 439 count4 = -1; 440 p = inbuf; 441 while (*p != '\0') { 442 /* 443 * Base64 encoded strings have the following 444 * characters in them: A-Z, a-z, 0-9 and +, / and = 445 */ 446 if (isalnum(*p) || *p == '+' || *p == '/' || *p == '=') 447 count++; 448 if (count % 4 == 0) 449 count4 = p - inbuf; 450 p++; 451 } 452 453 strcpy(leftover, inbuf + count4 + 1); 454 inbuf[count4 + 1] = 0; 455 456 n = b64_pton(inbuf, outbuf, sizeof(outbuf)); 457 458 if (n < 0) 459 break; 460 fwrite(outbuf, 1, n, outfp); 461 } 462 return (checkend(inbuf, "====", "error decoding base64 input stream")); 463 } 464 465 static void 466 usage(void) 467 { 468 469 (void)fprintf(stderr, 470 "usage: uudecode [-cimprs] [file ...]\n" 471 " uudecode [-i] -o output_file [file]\n" 472 " b64decode [-cimprs] [file ...]\n" 473 " b64decode [-i] -o output_file [file]\n"); 474 exit(1); 475 } 476