1 /* $OpenBSD: uudecode.c,v 1.23 2016/01/03 14:43:20 tb 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/socket.h> 39 #include <sys/stat.h> 40 41 #include <netinet/in.h> 42 43 #include <err.h> 44 #include <errno.h> 45 #include <fcntl.h> 46 #include <locale.h> 47 #include <pwd.h> 48 #include <resolv.h> 49 #include <stdio.h> 50 #include <stdlib.h> 51 #include <string.h> 52 #include <unistd.h> 53 #include <limits.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 (sflag) { 130 if (pledge("stdio rpath wpath cpath getpw", NULL) == -1) 131 err(1, "pledge"); 132 } else if (pflag == 0) { 133 if (pledge("stdio rpath wpath cpath", NULL) == -1) 134 err(1, "pledge"); 135 } else { 136 if (pledge("stdio rpath", NULL) == -1) 137 err(1, "pledge"); 138 } 139 140 if (*argv) { 141 rval = 0; 142 do { 143 infp = fopen(infile = *argv, "r"); 144 if (infp == NULL) { 145 warn("%s", *argv); 146 rval = 1; 147 continue; 148 } 149 rval |= decode(); 150 fclose(infp); 151 } while (*++argv); 152 } else { 153 infile = "stdin"; 154 infp = stdin; 155 rval = decode(); 156 } 157 exit(rval); 158 } 159 160 static int 161 decode(void) 162 { 163 int r, v; 164 165 if (rflag) { 166 /* relaxed alternative to decode2() */ 167 outfile = "/dev/stdout"; 168 outfp = stdout; 169 if (base64) 170 return (base64_decode()); 171 else 172 return (uu_decode()); 173 } 174 v = decode2(); 175 if (v == EOF) { 176 warnx("%s: missing or bad \"begin\" line", infile); 177 return (1); 178 } 179 for (r = v; cflag; r |= v) { 180 v = decode2(); 181 if (v == EOF) 182 break; 183 } 184 return (r); 185 } 186 187 static int 188 decode2(void) 189 { 190 int flags, fd, mode; 191 size_t n, m; 192 char *p, *q; 193 void *handle; 194 struct passwd *pw; 195 struct stat st; 196 char buf[PATH_MAX]; 197 198 base64 = 0; 199 /* search for header line */ 200 for (;;) { 201 if (fgets(buf, sizeof(buf), infp) == NULL) 202 return (EOF); 203 p = buf; 204 if (strncmp(p, "begin-base64 ", 13) == 0) { 205 base64 = 1; 206 p += 13; 207 } else if (strncmp(p, "begin ", 6) == 0) 208 p += 6; 209 else 210 continue; 211 /* p points to mode */ 212 q = strchr(p, ' '); 213 if (q == NULL) 214 continue; 215 *q++ = '\0'; 216 /* q points to filename */ 217 n = strlen(q); 218 while (n > 0 && (q[n-1] == '\n' || q[n-1] == '\r')) 219 q[--n] = '\0'; 220 /* found valid header? */ 221 if (n > 0) 222 break; 223 } 224 225 handle = setmode(p); 226 if (handle == NULL) { 227 warnx("%s: unable to parse file mode", infile); 228 return (1); 229 } 230 mode = getmode(handle, 0) & 0666; 231 free(handle); 232 233 if (sflag) { 234 /* don't strip, so try ~user/file expansion */ 235 p = NULL; 236 pw = NULL; 237 if (*q == '~') 238 p = strchr(q, '/'); 239 if (p != NULL) { 240 *p = '\0'; 241 pw = getpwnam(q + 1); 242 *p = '/'; 243 } 244 if (pw != NULL) { 245 n = strlen(pw->pw_dir); 246 if (buf + n > p) { 247 /* make room */ 248 m = strlen(p); 249 if (sizeof(buf) < n + m) { 250 warnx("%s: bad output filename", 251 infile); 252 return (1); 253 } 254 p = memmove(buf + n, p, m); 255 } 256 q = memcpy(p - n, pw->pw_dir, n); 257 } 258 } else { 259 /* strip down to leaf name */ 260 p = strrchr(q, '/'); 261 if (p != NULL) 262 q = p + 1; 263 } 264 if (!oflag) 265 outfile = q; 266 267 /* POSIX says "/dev/stdout" is a 'magic cookie' not a special file. */ 268 if (pflag || strcmp(outfile, "/dev/stdout") == 0) 269 outfp = stdout; 270 else { 271 flags = O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW; 272 if (lstat(outfile, &st) == 0) { 273 if (iflag) { 274 warnc(EEXIST, "%s: %s", infile, outfile); 275 return (0); 276 } 277 switch (st.st_mode & S_IFMT) { 278 case S_IFREG: 279 case S_IFLNK: 280 /* avoid symlink attacks */ 281 if (unlink(outfile) == 0 || errno == ENOENT) 282 break; 283 warn("%s: unlink %s", infile, outfile); 284 return (1); 285 case S_IFDIR: 286 warnc(EISDIR, "%s: %s", infile, outfile); 287 return (1); 288 default: 289 if (oflag) { 290 /* trust command-line names */ 291 flags &= ~(O_EXCL|O_NOFOLLOW); 292 break; 293 } 294 warnc(EEXIST, "%s: %s", infile, outfile); 295 return (1); 296 } 297 } else if (errno != ENOENT) { 298 warn("%s: %s", infile, outfile); 299 return (1); 300 } 301 if ((fd = open(outfile, flags, mode)) < 0 || 302 (outfp = fdopen(fd, "w")) == NULL) { 303 warn("%s: %s", infile, outfile); 304 return (1); 305 } 306 } 307 308 if (base64) 309 return (base64_decode()); 310 else 311 return (uu_decode()); 312 } 313 314 static int 315 get_line(char *buf, size_t size) 316 { 317 if (fgets(buf, size, infp) != NULL) 318 return (2); 319 if (rflag) 320 return (0); 321 warnx("%s: %s: short file", infile, outfile); 322 return (1); 323 } 324 325 static int 326 checkend(const char *ptr, const char *end, const char *msg) 327 { 328 size_t n; 329 330 n = strlen(end); 331 if (strncmp(ptr, end, n) != 0 || 332 strspn(ptr + n, " \t\r\n") != strlen(ptr + n)) { 333 warnx("%s: %s: %s", infile, outfile, msg); 334 return (1); 335 } 336 if (fclose(outfp) != 0) { 337 warn("%s: %s", infile, outfile); 338 return (1); 339 } 340 return (0); 341 } 342 343 static int 344 uu_decode(void) 345 { 346 int i, ch; 347 char *p; 348 char buf[PATH_MAX]; 349 350 /* for each input line */ 351 for (;;) { 352 switch (get_line(buf, sizeof(buf))) { 353 case 0: 354 return (0); 355 case 1: 356 return (1); 357 } 358 359 #define DEC(c) (((c) - ' ') & 077) /* single character decode */ 360 #define IS_DEC(c) ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) ) 361 362 #define OUT_OF_RANGE do { \ 363 warnx("%s: %s: character out of range: [%d-%d]", \ 364 infile, outfile, 1 + ' ', 077 + ' ' + 1); \ 365 return (1); \ 366 } while (0) 367 368 /* 369 * `i' is used to avoid writing out all the characters 370 * at the end of the file. 371 */ 372 p = buf; 373 if ((i = DEC(*p)) <= 0) 374 break; 375 for (++p; i > 0; p += 4, i -= 3) 376 if (i >= 3) { 377 if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) && 378 IS_DEC(*(p + 2)) && IS_DEC(*(p + 3)))) 379 OUT_OF_RANGE; 380 381 ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; 382 putc(ch, outfp); 383 ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; 384 putc(ch, outfp); 385 ch = DEC(p[2]) << 6 | DEC(p[3]); 386 putc(ch, outfp); 387 } 388 else { 389 if (i >= 1) { 390 if (!(IS_DEC(*p) && IS_DEC(*(p + 1)))) 391 OUT_OF_RANGE; 392 ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; 393 putc(ch, outfp); 394 } 395 if (i >= 2) { 396 if (!(IS_DEC(*(p + 1)) && 397 IS_DEC(*(p + 2)))) 398 OUT_OF_RANGE; 399 400 ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; 401 putc(ch, outfp); 402 } 403 if (i >= 3) { 404 if (!(IS_DEC(*(p + 2)) && 405 IS_DEC(*(p + 3)))) 406 OUT_OF_RANGE; 407 ch = DEC(p[2]) << 6 | DEC(p[3]); 408 putc(ch, outfp); 409 } 410 } 411 } 412 switch (get_line(buf, sizeof(buf))) { 413 case 0: 414 return (0); 415 case 1: 416 return (1); 417 default: 418 return (checkend(buf, "end", "no \"end\" line")); 419 } 420 } 421 422 static int 423 base64_decode(void) 424 { 425 int n; 426 char inbuf[PATH_MAX]; 427 unsigned char outbuf[PATH_MAX * 4]; 428 429 for (;;) { 430 switch (get_line(inbuf, sizeof(inbuf))) { 431 case 0: 432 return (0); 433 case 1: 434 return (1); 435 } 436 n = b64_pton(inbuf, outbuf, sizeof(outbuf)); 437 if (n < 0) 438 break; 439 fwrite(outbuf, 1, n, outfp); 440 } 441 return (checkend(inbuf, "====", 442 "error decoding base64 input stream")); 443 } 444 445 static void 446 usage(void) 447 { 448 switch (pmode) { 449 case MODE_DECODE: 450 (void)fprintf(stderr, 451 "usage: uudecode [-cimprs] [file ...]\n" 452 " uudecode [-i] -o output_file [file]\n"); 453 break; 454 case MODE_B64DECODE: 455 (void)fprintf(stderr, 456 "usage: b64decode [-ciprs] [file ...]\n" 457 " b64decode [-i] -o output_file [file]\n"); 458 break; 459 } 460 exit(1); 461 } 462