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