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