uudecode.c revision 1.22
1/* $OpenBSD: uudecode.c,v 1.22 2015/10/09 01:37:09 deraadt 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 55static const char *infile, *outfile; 56static FILE *infp, *outfp; 57static int base64, cflag, iflag, oflag, pflag, rflag, sflag; 58 59static void usage(void); 60static int decode(void); 61static int decode2(void); 62static int uu_decode(void); 63static int base64_decode(void); 64 65enum program_mode { 66 MODE_DECODE, 67 MODE_B64DECODE 68} pmode; 69 70int 71main(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 (oflag || 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 exit(rval); 155} 156 157static int 158decode(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 184static int 185decode2(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[PATH_MAX]; 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)) < 0 || 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 311static int 312get_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 322static int 323checkend(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 340static int 341uu_decode(void) 342{ 343 int i, ch; 344 char *p; 345 char buf[PATH_MAX]; 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 do { \ 360 warnx("%s: %s: character out of range: [%d-%d]", \ 361 infile, outfile, 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) && IS_DEC(*(p + 1)) && 375 IS_DEC(*(p + 2)) && IS_DEC(*(p + 3)))) 376 OUT_OF_RANGE; 377 378 ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; 379 putc(ch, outfp); 380 ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; 381 putc(ch, outfp); 382 ch = DEC(p[2]) << 6 | DEC(p[3]); 383 putc(ch, outfp); 384 } 385 else { 386 if (i >= 1) { 387 if (!(IS_DEC(*p) && IS_DEC(*(p + 1)))) 388 OUT_OF_RANGE; 389 ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; 390 putc(ch, outfp); 391 } 392 if (i >= 2) { 393 if (!(IS_DEC(*(p + 1)) && 394 IS_DEC(*(p + 2)))) 395 OUT_OF_RANGE; 396 397 ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; 398 putc(ch, outfp); 399 } 400 if (i >= 3) { 401 if (!(IS_DEC(*(p + 2)) && 402 IS_DEC(*(p + 3)))) 403 OUT_OF_RANGE; 404 ch = DEC(p[2]) << 6 | DEC(p[3]); 405 putc(ch, outfp); 406 } 407 } 408 } 409 switch (get_line(buf, sizeof(buf))) { 410 case 0: 411 return (0); 412 case 1: 413 return (1); 414 default: 415 return (checkend(buf, "end", "no \"end\" line")); 416 } 417} 418 419static int 420base64_decode(void) 421{ 422 int n; 423 char inbuf[PATH_MAX]; 424 unsigned char outbuf[PATH_MAX * 4]; 425 426 for (;;) { 427 switch (get_line(inbuf, sizeof(inbuf))) { 428 case 0: 429 return (0); 430 case 1: 431 return (1); 432 } 433 n = b64_pton(inbuf, outbuf, sizeof(outbuf)); 434 if (n < 0) 435 break; 436 fwrite(outbuf, 1, n, outfp); 437 } 438 return (checkend(inbuf, "====", 439 "error decoding base64 input stream")); 440} 441 442static void 443usage(void) 444{ 445 switch (pmode) { 446 case MODE_DECODE: 447 (void)fprintf(stderr, 448 "usage: uudecode [-cimprs] [file ...]\n" 449 " uudecode [-i] -o output_file [file]\n"); 450 break; 451 case MODE_B64DECODE: 452 (void)fprintf(stderr, 453 "usage: b64decode [-ciprs] [file ...]\n" 454 " b64decode [-i] -o output_file [file]\n"); 455 break; 456 } 457 exit(1); 458} 459