1 /* $OpenBSD: uudecode.c,v 1.29 2022/08/30 16:06:09 yasuoka 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/stat.h> 39 40 #include <netinet/in.h> 41 42 #include <err.h> 43 #include <errno.h> 44 #include <fcntl.h> 45 #include <limits.h> 46 #include <pwd.h> 47 #include <resolv.h> 48 #include <stdio.h> 49 #include <stdlib.h> 50 #include <string.h> 51 #include <unistd.h> 52 53 static const char *infile, *outfile; 54 static FILE *infp, *outfp; 55 static int base64, cflag, iflag, oflag, pflag, rflag, sflag; 56 57 static void __dead usage(void); 58 static int decode(void); 59 static int decode2(void); 60 static int uu_decode(void); 61 static int base64_decode(void); 62 63 enum program_mode { 64 MODE_DECODE, 65 MODE_B64DECODE 66 } pmode; 67 68 int 69 main(int argc, char *argv[]) 70 { 71 int rval, ch; 72 extern char *__progname; 73 static const char *optstr[2] = { 74 "cimo:prs", 75 "cio:prs" 76 }; 77 78 pmode = MODE_DECODE; 79 if (strcmp(__progname, "b64decode") == 0) { 80 base64 = 1; 81 pmode = MODE_B64DECODE; 82 } 83 84 while ((ch = getopt(argc, argv, optstr[pmode])) != -1) { 85 switch(ch) { 86 case 'c': 87 if (oflag || rflag) 88 usage(); 89 cflag = 1; /* multiple uudecode'd files */ 90 break; 91 case 'i': 92 iflag = 1; /* ask before override files */ 93 break; 94 case 'm': 95 base64 = 1; 96 break; 97 case 'o': 98 if (cflag || pflag || rflag || sflag) 99 usage(); 100 oflag = 1; /* output to the specified file */ 101 sflag = 1; /* do not strip pathnames for output */ 102 outfile = optarg; /* set the output filename */ 103 break; 104 case 'p': 105 if (oflag) 106 usage(); 107 pflag = 1; /* print output to stdout */ 108 break; 109 case 'r': 110 if (cflag || oflag) 111 usage(); 112 rflag = 1; /* decode raw data */ 113 break; 114 case 's': 115 if (oflag) 116 usage(); 117 sflag = 1; /* do not strip pathnames for output */ 118 break; 119 default: 120 usage(); 121 } 122 } 123 argc -= optind; 124 argv += optind; 125 126 if (sflag) { 127 if (pledge("stdio rpath wpath cpath getpw", NULL) == -1) 128 err(1, "pledge"); 129 } else if (pflag == 0) { 130 if (pledge("stdio rpath wpath cpath", NULL) == -1) 131 err(1, "pledge"); 132 } else { 133 if (pledge("stdio rpath", NULL) == -1) 134 err(1, "pledge"); 135 } 136 137 if (*argv) { 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 return (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[BUFSIZ]; 194 195 base64 = 0; 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 = 1; 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|O_NOFOLLOW; 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|O_NOFOLLOW); 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)) == -1 || 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 if (fgets(buf, size, infp) != NULL) 315 return (2); 316 if (rflag) 317 return (0); 318 warnx("%s: %s: short file", infile, outfile); 319 return (1); 320 } 321 322 static int 323 checkend(const char *ptr, const char *end, const char *msg) 324 { 325 size_t n; 326 327 n = strlen(end); 328 if (strncmp(ptr, end, n) != 0 || 329 strspn(ptr + n, " \t\r\n") != strlen(ptr + n)) { 330 warnx("%s: %s: %s", infile, outfile, msg); 331 return (1); 332 } 333 if (fclose(outfp) != 0) { 334 warn("%s: %s", infile, outfile); 335 return (1); 336 } 337 return (0); 338 } 339 340 static int 341 uu_decode(void) 342 { 343 int i, ch; 344 char *p; 345 char buf[BUFSIZ]; 346 347 /* for each input line */ 348 for (;;) { 349 switch (get_line(buf, sizeof(buf))) { 350 case 0: 351 return (0); 352 case 1: 353 return (1); 354 } 355 356 #define DEC(c) (((c) - ' ') & 077) /* single character decode */ 357 #define IS_DEC(c) ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) ) 358 359 #define OUT_OF_RANGE(c) do { \ 360 warnx("%s: %s: character value (%d) out of range [%d-%d]", \ 361 infile, outfile, (unsigned char)(c), 1 + ' ', 077 + ' ' + 1); \ 362 return (1); \ 363 } while (0) 364 365 /* 366 * `i' is used to avoid writing out all the characters 367 * at the end of the file. 368 */ 369 p = buf; 370 if ((i = DEC(*p)) <= 0) 371 break; 372 for (++p; i > 0; p += 4, i -= 3) 373 if (i >= 3) { 374 if (!IS_DEC(*p)) 375 OUT_OF_RANGE(*p); 376 if (!IS_DEC(*(p + 1))) 377 OUT_OF_RANGE(*(p + 1)); 378 if (!IS_DEC(*(p + 2))) 379 OUT_OF_RANGE(*(p + 2)); 380 if (!IS_DEC(*(p + 3))) 381 OUT_OF_RANGE(*(p + 3)); 382 ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; 383 putc(ch, outfp); 384 ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; 385 putc(ch, outfp); 386 ch = DEC(p[2]) << 6 | DEC(p[3]); 387 putc(ch, outfp); 388 } 389 else { 390 if (i >= 1) { 391 if (!IS_DEC(*p)) 392 OUT_OF_RANGE(*p); 393 if (!IS_DEC(*(p + 1))) 394 OUT_OF_RANGE(*(p + 1)); 395 ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; 396 putc(ch, outfp); 397 } 398 if (i >= 2) { 399 if (!IS_DEC(*(p + 1))) 400 OUT_OF_RANGE(*(p + 1)); 401 if (!IS_DEC(*(p + 2))) 402 OUT_OF_RANGE(*(p + 2)); 403 ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; 404 putc(ch, outfp); 405 } 406 if (i >= 3) { 407 if (!IS_DEC(*(p + 2))) 408 OUT_OF_RANGE(*(p + 2)); 409 if (!IS_DEC(*(p + 3))) 410 OUT_OF_RANGE(*(p + 3)); 411 ch = DEC(p[2]) << 6 | DEC(p[3]); 412 putc(ch, outfp); 413 } 414 } 415 } 416 switch (get_line(buf, sizeof(buf))) { 417 case 0: 418 return (0); 419 case 1: 420 return (1); 421 default: 422 return (checkend(buf, "end", "no \"end\" line")); 423 } 424 } 425 426 #define ROUNDDOWN(x,y) (((x)/(y)) * (y)) 427 428 static int 429 base64_decode(void) 430 { 431 int n; 432 char inbuf[ROUNDDOWN(BUFSIZ, 4) + 1]; 433 unsigned char outbuf[BUFSIZ * 4]; 434 435 for (;;) { 436 switch (get_line(inbuf, sizeof(inbuf))) { 437 case 0: 438 return (0); 439 case 1: 440 return (1); 441 } 442 n = b64_pton(inbuf, outbuf, sizeof(outbuf)); 443 if (n < 0) 444 break; 445 fwrite(outbuf, 1, n, outfp); 446 } 447 return (checkend(inbuf, "====", 448 "error decoding base64 input stream")); 449 } 450 451 static void __dead 452 usage(void) 453 { 454 switch (pmode) { 455 case MODE_DECODE: 456 (void)fprintf(stderr, 457 "usage: uudecode [-cimprs] [file ...]\n" 458 " uudecode [-i] -o output_file [file]\n"); 459 break; 460 case MODE_B64DECODE: 461 (void)fprintf(stderr, 462 "usage: b64decode [-ciprs] [file ...]\n" 463 " b64decode [-i] -o output_file [file]\n"); 464 break; 465 } 466 exit(1); 467 } 468