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