1 /* yydecode utility -- http://nerv.cx/liyang/
2
3 Copyright (C) 1994, 1995 Free Software Foundation, Inc.
4
5 This product is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
8 any later version.
9
10 This product is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this product; see the file COPYING. If not, write to
17 the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
18 02111-1307, USA. */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include "system.h"
25 #include "getopt.h"
26 #include "crc32.h"
27 #include "yydecode.h"
28 #include "evilchar.h"
29 #include "base64tab.h"
30
31 /* entry point */
32 int main(int argc, char * const *argv);
33
34 /* static prototypes */
35 static u_int32_t atocrc32(char *str);
36 static int decoded_file_open(struct decoded_file *f);
37 /*static struct decoded_file *decoded_file_lookup(struct decoded_file **list, const char *filename, const char *forced_outname);
38 static int read_yenc(const char *inname, const char *forced_outname, struct decoded_file **decoded_list, int whinge);
39 static int read_stduu(const char *inname, struct decoded_file *f);
40 static int read_base64(const char *inname, struct decoded_file *f);*/
41 static int decode(const char *inname, const char *forced_outname, struct decoded_file **decoded_list);
42 static void usage(int status);
43
44 /* extern variables */
45 const char *program_name = NULL;
46
47 /* command-line flags */
48 static const char *opt_directory = NULL;
49 static int opt_evil_filename = 0;
50 static int opt_clobber_filename = 0;
51 static int opt_force_overwrite = 0;
52 static int opt_write_broken = 0;
53 static int opt_broken_encoder = 0;
54 static int opt_remove_broken = 0;
55 static int opt_large_parts = 0;
56 static int opt_verbose = 0;
57
58 /* code starts here */
59
atocrc32(char * str)60 static u_int32_t atocrc32(char *str)
61 {
62 return (u_int32_t)strtoul((char *)str, NULL, 16);
63 }
64
fgets_(char * s,int size,FILE * stream)65 static char *fgets_(char *s, int size, FILE *stream)
66 {
67 int i;
68 int c = '\0', d;
69 char *p = s;
70
71 if(size <= 1)
72 return NULL;
73
74 c = fgetc(stream);
75 if(c == EOF)
76 return NULL;
77 ungetc(c, stream);
78
79 for(i = size; i > 2; i--)
80 {
81 c = fgetc(stream);
82 if((c == '\x0a') || (c == '\x0d'))
83 {
84 /* ick. Still, don't leave any surprises. */
85 *p++ = '\n';
86 break;
87 }
88 if(c == EOF)
89 break;
90 *p++ = (char)c;
91 }
92 *p++ = '\0';
93
94 /* We could do with all the empty lines being eaten up, but, as above... */
95 do
96 {
97 d = fgetc(stream);
98 if(d == EOF)
99 return s;
100 }
101 while(((d == '\x0a') || (d == '\x0d')) && (d != c));
102 ungetc(d, stream);
103
104 return s;
105 }
106
decoded_file_open(struct decoded_file * f)107 static int decoded_file_open(struct decoded_file *f)
108 {
109 int fd;
110
111 if(f->handle)
112 return EXIT_SUCCESS;
113
114 if(!strcmp(f->outname, "-"))
115 {
116 f->handle = stdout;
117 return EXIT_SUCCESS;
118 }
119
120 if(f->previously_existed)
121 return EXIT_FAILURE;
122
123 /* is_seekable (despite the misleading name) is always zero on the
124 * first run. Make sure here that we're not trying to re-open a
125 * seekable file. */
126 if(!f->is_seekable)
127 {
128 /* blame wget for the name */
129 int clobber_num;
130 char *clobber_part;
131
132 /* If it's writable, we'll say it exists. (because that's the
133 * only case when we can overwrite it, and that's all we care
134 * about.) Otherwise, we pretend it doesn't, and let open()
135 * below whinge instead. */
136 f->previously_existed = access(f->outname, W_OK) ? 0 : 1;
137
138 for(clobber_num = 1, clobber_part = f->outname + strlen(f->outname);
139 f->previously_existed && !opt_force_overwrite && opt_clobber_filename;
140 clobber_num++)
141 {
142 if(opt_verbose > 1)
143 error(0, 0, _("%s: Output exists; trying .%i"), f->outname, clobber_num);
144
145 sprintf(clobber_part, ".%i", clobber_num);
146 f->previously_existed = access(f->outname, W_OK) ? 0 : 1;
147 }
148
149 fd = open(f->outname, O_WRONLY | O_CREAT
150 /* Ask for exclusiveness if the file existed before we
151 * started, and we didn't force overwriting. In a way, we
152 * already know that this is going to fail, but we're lazy,
153 * so we let open() generate the error message for us. */
154 | (f->previously_existed && !opt_force_overwrite
155 ? O_EXCL : 0)
156 /* Note that we truncate (overwriting in the process) a file
157 * only if we're not writing broken parts. */
158 | (f->previously_existed && opt_force_overwrite && !opt_write_broken
159 ? O_TRUNC : 0),
160 f->mode);
161 if(fd >= 0)
162 close(fd);
163 else
164 {
165 f->previously_existed = 1;
166 return EXIT_FAILURE;
167 }
168 }
169
170 /* w+ truncates the file; if that's what we had wanted, then open()
171 * above would have already done it for us. */
172 if(!(f->handle = fopen(f->outname, "r+b")))
173 {
174 f->previously_existed = 1;
175 return EXIT_FAILURE;
176 }
177
178 /* If it didn't fail above, then we 0wnz this file now <g> */
179 f->previously_existed = 0;
180
181 f->is_seekable = ftell(f->handle) >= 0;
182
183 return EXIT_SUCCESS;
184 }
185
decoded_file_lookup(struct decoded_file ** list,const char * filename,int mode,const char * forced_outname)186 static struct decoded_file * decoded_file_lookup(struct decoded_file **list,
187 const char *filename, int mode, const char *forced_outname)
188 {
189 struct decoded_file **last = list;
190 struct decoded_file *p = *list;
191 char *pf;
192
193 for(; p; p = p->next)
194 {
195 if(!strcmp(p->filename, filename))
196 return p;
197 last = &p->next;
198 }
199
200 /* Requested file doesn't exist in list -- set one up. */
201 p = malloc(sizeof(struct decoded_file));
202 memset(p, 0, sizeof(struct decoded_file));
203
204 /* Not sure why we bother checking /dev/stdout -- uudecode does. */
205 if(forced_outname && !strcmp(forced_outname, "/dev/stdout"))
206 forced_outname = "-";
207
208 p->filename = strdup(filename);
209 p->mode = mode;
210 {
211 const char *fn;
212 fn = forced_outname ? forced_outname : filename;
213 p->outname = malloc(strlen(fn) + 32); /* leave some space for clobbering the file name */
214 strcpy(p->outname, fn);
215 }
216
217 /* don't apply evil char checking for forced output filenames */
218 if(!forced_outname)
219 {
220 for(pf = p->outname; *pf; pf++)
221 {
222 if((int)char_evilness[*pf & 0xff] > opt_evil_filename)
223 *pf = '_';
224 }
225 }
226
227 crc32_init_ctx(&p->crc32_context);
228
229 return *last = p;
230 }
231
232 /* Various macros, although only mostly used by read_yenc() */
233 #define ASSERT(expr) \
234 if(!(expr)) \
235 { \
236 error(0, 0, _("%s: Assertion `%s' failed -- output may be corrupt"), \
237 c->inname, #expr); \
238 return EXIT_FAILURE; \
239 }
240
241 #define B64DECODE(c) base64_tab[(size_t)(c)]
242
243 #define UUDECODE(c) (((c) - ' ') & 077)
244
245 #define SEARCH_MARKER(marker) \
246 if(!fgets_(buf, BUFSIZ, stdin)) \
247 { \
248 error(0, 0, _("%s: %s:%i: No `%s' marker found"), \
249 c->inname, f->outname, c->part, (marker)); \
250 return EXIT_FAILURE; \
251 } \
252 if(!strncmp(buf, marker, lenof(marker))) \
253 break;
254
255 #define PARSE_TAG_OPTIONAL(tag, convertfn, assignto) \
256 if((p = strstr(buf, tag))) \
257 assignto = convertfn(p + lenof(tag))
258
259 #define PARSE_TAG(tag, convertfn, assignto) \
260 PARSE_TAG_OPTIONAL(tag, convertfn, assignto); \
261 else \
262 { \
263 error(0, 0, _("%s: %s:%i: No `%s' tag found"), \
264 c->inname, f->outname, c->part, tag); \
265 return EXIT_FAILURE; \
266 }
267
read_base64(struct decode_context * c,struct decoded_file * f,char * buf)268 static int read_base64(struct decode_context *c, struct decoded_file *f, char *buf)
269 {
270 int exit_status = EXIT_EOF;
271 int last_data = 0;
272
273 /* We don't use c->out, so the CRC32 is never calculated. */
274 c->total_parts = 1;
275
276 if(opt_verbose > 0)
277 error(0, 0, _("%s: %s: base64 encoding"), c->inname, f->outname);
278
279 for(;;)
280 {
281 int i;
282 char *pi, *po;
283
284 if(!fgets_(buf, BUFSIZ, stdin))
285 {
286 error(0, 0, _("%s: %s: Short file"), c->inname, f->outname);
287 return EXIT_FAILURE;
288 }
289
290 if(!memcmp(buf, B64MARKER_END, lenof(B64MARKER_END)))
291 break;
292
293 if(last_data)
294 {
295 error(0, 0, _("%s: %s: warning: Data following `=' padding character"), c->inname, f->outname);
296 exit_status = EXIT_WARNING;
297 break;
298 }
299
300 /* Rather than taking the convoluted (and frankly, nasty) algo-
301 * rithm used by uudecode to ignore characters outside of that
302 * defined by RFC2045 (which obsoletes RFC1521, referred to by
303 * uuencode), we make two passes over the input: once to remove
304 * said characters, while a second pass decodes the input. */
305
306 /* Note this also removes the EOL markers fgets_ leaves behind. */
307 for(po = pi = buf; *pi; pi++)
308 {
309 if((*pi & 0x80) || (base64_tab[(size_t)*pi] == 0177))
310 continue;
311 *po++ = *pi;
312 if(*pi == '=')
313 {
314 last_data = 1;
315 exit_status = EXIT_SUCCESS;
316 if((po - buf) % 4)
317 continue;
318 break;
319 }
320 if(last_data)
321 {
322 error(0, 0, _("%s: %s: warning: Data following `=' padding character"), c->inname, f->outname);
323 exit_status = EXIT_WARNING;
324 po--; /* undo the last character */
325 break;
326 }
327 }
328 *po = '\0';
329
330 /* the resulting input line length should be a multiple of 4 */
331 i = po - buf;
332 if(i % 4)
333 {
334 error(0, 0, _("%s: %s: Illegal line -- ignored"), c->inname, f->outname);
335 exit_status = EXIT_FAILURE;
336 continue;
337 }
338 else if(i == 0)
339 continue;
340 i /= 4;
341
342 for(po = pi = buf; i; pi += 4, i--)
343 {
344 *po++ = B64DECODE(pi[0]) << 2 | B64DECODE(pi[1]) >> 4;
345 *po++ = B64DECODE(pi[1]) << 4 | B64DECODE(pi[2]) >> 2;
346 *po++ = B64DECODE(pi[2]) << 6 | B64DECODE(pi[3]);
347
348 if(pi[3] == '=')
349 {
350 po--;
351 if(pi[2] == '=')
352 {
353 po--;
354 if(pi[1] == '=')
355 po--;
356 }
357 break;
358 }
359 }
360
361 #undef B64DECODE
362
363 if(fwrite(buf, po - buf, 1, f->handle) != 1)
364 {
365 error(0, errno, _("%s: %s"), c->inname, f->outname);
366 return EXIT_FAILURE;
367 }
368 }
369
370 c->status = part_intact;
371 return exit_status;
372 }
373
read_stduu(struct decode_context * c,struct decoded_file * f,char * buf)374 static int read_stduu(struct decode_context *c, struct decoded_file *f, char *buf)
375 {
376 int exit_status = EXIT_EOF;
377 int line;
378
379 /* We don't use c->out, so the CRC32 is never calculated. */
380 c->total_parts = 1;
381
382 if(opt_verbose > 0)
383 error(0, 0, _("%s: %s: uu encoding"), c->inname, f->outname);
384
385 for(line = 1; ; line++)
386 {
387 int i, n, l;
388 char *pi, *po;
389 int pad = 0;
390
391 if(!fgets_(buf, BUFSIZ, stdin))
392 {
393 error(0, 0, _("%s: %s: unexpected end of file at line #%i"), c->inname, f->outname, line);
394 return EXIT_FAILURE;
395 }
396
397 po = pi = buf;
398 n = *pi++ - ' ';
399 if(n < 0) /* ignores empty lines */
400 continue;
401 n &= 077;
402 if(n == 0)
403 {
404 exit_status = EXIT_SUCCESS;
405 break;
406 }
407
408 for(l = 0; pi[l] >= ' '; )
409 l++;
410
411 /* count should be a multiple of 4 */
412 if(l != ((n + 2) / 3) * 4)
413 {
414 i = (l * 3) / 4;
415 if(opt_write_broken)
416 {
417 /* write however many bytes we can see */
418 n = i;
419 error(0, 0, _("%s: %s: warning: malformed line #%i"), c->inname, f->outname, line);
420 }
421 else
422 {
423 if(n > i)
424 {
425 pad = n - i;
426 n = i;
427 error(0, 0, _("%s: %s: short line #%i (output padded)"), c->inname, f->outname, line);
428 }
429 else
430 error(0, 0, _("%s: %s: overlong line #%i (truncated)"), c->inname, f->outname, line);
431 exit_status = EXIT_FAILURE;
432 }
433 }
434
435 /* At this point, we're guaranteed (ha!) to have enough
436 * input to work with, whatever the value of n, so no need
437 * to check for malformed lines. */
438 for(i = n / 3; i; pi += 4, i--)
439 {
440 *po++ = UUDECODE(pi[0]) << 2 | UUDECODE(pi[1]) >> 4;
441 *po++ = UUDECODE(pi[1]) << 4 | UUDECODE(pi[2]) >> 2;
442 *po++ = UUDECODE(pi[2]) << 6 | UUDECODE(pi[3]);
443 }
444
445 switch(n % 3)
446 {
447 case 2:
448 *po++ = UUDECODE(pi[0]) << 2 | UUDECODE(pi[1]) >> 4;
449 *po++ = UUDECODE(pi[1]) << 4 | UUDECODE(pi[2]) >> 2;
450 break;
451 case 1:
452 *po++ = UUDECODE(pi[0]) << 2 | UUDECODE(pi[1]) >> 4;
453 break;
454 }
455
456 #undef UUDECODE
457
458 if(pad)
459 {
460 memset(po, 0, pad);
461 po += pad;
462 }
463
464 if(fwrite(buf, po - buf, 1, f->handle) != 1)
465 {
466 error(0, errno, _("%s: %s:1"), c->inname, f->outname);
467 return EXIT_FAILURE;
468 }
469 }
470
471 if((fgets_(buf, BUFSIZ, stdin) == NULL) || strcmp(buf, UUMARKER_END))
472 {
473 error(0, 0, _("%s: %s:1: No `end' line"), c->inname, f->outname);
474 return EXIT_FAILURE;
475 }
476
477 c->status = part_intact;
478 return exit_status;
479 }
480
read_yenc(struct decode_context * c,struct decoded_file * f,char * buf)481 static int read_yenc(struct decode_context *c, struct decoded_file *f, char *buf)
482 {
483 int is_multi_part = 0;
484 int has_more_parts = 0;
485 char *p;
486 char *pout;
487
488 int unrecognised_escapes[0x100];
489 off_t part_begin = 0;
490 off_t part_end = 0;
491
492 memset(unrecognised_escapes, 0, sizeof(unrecognised_escapes));
493
494 /* ick. Non-zero on entry. Needed to detect multi-part files */
495 c->part = -1;
496 c->total_parts = 0;
497 PARSE_TAG_OPTIONAL(YTAG_PART, (int)atol, c->part);
498 PARSE_TAG_OPTIONAL(YTAG_TOTAL, (int)atol, c->total_parts);
499
500 /* Pretend single-part articles have a `part=1' tag */
501 is_multi_part = c->part != -1;
502 if(!is_multi_part)
503 c->part = 1;
504
505 ASSERT(c->part > 0);
506
507 if(c->total_parts)
508 {
509 ASSERT(c->part <= c->total_parts);
510 }
511 else
512 {
513 /* we've at least this many parts; can't tell for now */
514 has_more_parts = 1;
515 if(c->total_parts < c->part)
516 c->total_parts = c->part;
517 }
518
519 { /* Assert that we can handle the line width.
520 * sizeof(buf) should be 8kb, hope that's sufficient. */
521 int line_width;
522
523 PARSE_TAG(YTAG_LINE, (int)atol, line_width);
524 ASSERT(line_width >= 0);
525 ASSERT(line_width < BUFSIZ);
526 }
527
528 /* opt_verbose == 1: report the first part only.
529 * opt_verbose == 2: report all parts. */
530 if((!f->total_parts && (opt_verbose > 0))
531 || (opt_verbose > 1))
532 error(0, 0, _("%s: %s:%i: yEnc file (%s%i parts)"), c->inname,
533 f->outname, c->part, has_more_parts ? ">=" : "", c->total_parts);
534
535 {
536 off_t file_size = 0;
537
538 PARSE_TAG(YTAG_SIZE, (off_t)atol, file_size);
539 ASSERT(file_size >= 0);
540
541 if(f->total_parts && (f->total_size != file_size))
542 error(0, 0, _("%s: %s:%i: warning: File size mismatch -- previous parts says %li, this part %li; ignoring this part"),
543 c->inname, f->outname, c->part, f->total_size, file_size);
544 else
545 f->total_size = file_size;
546 }
547
548 if(!is_multi_part)
549 part_end = f->total_size;
550
551 /* make sure we've got a valid handle in f->handle */
552 if(decoded_file_open(f) != EXIT_SUCCESS)
553 {
554 /* Only show error for the first part. */
555 if(!f->total_parts)
556 error(0, errno, _("%s: %s:%i"), c->inname, f->outname, c->part);
557 return EXIT_FAILURE;
558 }
559
560 /* Is this a multi-part article? */
561 if(is_multi_part)
562 {
563 for(;;)
564 {
565 SEARCH_MARKER(YMARKER_PART);
566 error(0, 0, _("%s: %s:%i: No `=ypart' line found after `=ybegin'"),
567 c->inname, f->outname, c->part);
568 return EXIT_FAILURE;
569 }
570
571 PARSE_TAG(YTAG_BEGIN, (off_t)atol, part_begin);
572 part_begin--;
573 ASSERT(part_begin >= 0);
574
575 PARSE_TAG(YTAG_END, (off_t)atol, part_end);
576 ASSERT(part_end >= 0);
577 ASSERT(part_end >= part_begin);
578 ASSERT(part_end <= f->total_size);
579
580 /* This fails on stdout, but we ignore that. */
581 (void)fseek(f->handle, part_begin, SEEK_SET);
582 }
583
584 /* Prepare the output buffer. */
585 if((part_end - part_begin > PART_SIZE_SOFT_LIMIT) && !opt_large_parts)
586 {
587 error(0, 0, _("%s: %s:%i: Not going to malloc() %lu bytes (broken header?); use --large-parts"),
588 c->inname, f->outname, c->part, part_end - part_begin);
589 return EXIT_FAILURE;
590 }
591
592 if(!(pout = c->out = malloc((size_t)(part_end - part_begin))))
593 {
594 error(0, 0, _("%s: %s:%i: Unable to malloc() %lu bytes"),
595 c->inname, f->outname, c->part, part_end - part_begin);
596 return EXIT_FAILURE;
597 }
598
599 /* Begin decoding! */
600 for(;/* each line */;)
601 {
602 /* Breaks out if =yend found. */
603 SEARCH_MARKER(YMARKER_END);
604
605 p = buf + strlen(buf) - 1;
606 while((p >= buf) && ((*p == '\r') || (*p == '\n')))
607 --p;
608 *++p = '\0';
609
610 for(p = buf; *p; p++)
611 {
612 if(pout - c->out >= part_end - part_begin)
613 {
614 error(0, 0, _("%s: %s:%i: Part longer than expected"),
615 c->inname, f->outname, c->part);
616 c->out_size = pout - c->out;
617 return EXIT_FAILURE;
618 }
619 if(*p != '=')
620 {
621 *pout++ = *p - 42;
622 continue;
623 }
624
625 switch(*++p)
626 {
627 default: /* Be liberal in what we accept. */
628 if(!unrecognised_escapes[*p & 0xff])
629 { /* Just once is enough. */
630 error(0, 0, _("%s: %s:%i: warning: Unrecognised escape code `\\%o' (allowing it anyway)"),
631 c->inname, f->outname, c->part, *p);
632 }
633 unrecognised_escapes[*p & 0xff]++;
634 /* NUL TAB LF CR */
635 case '@': case 'I': case 'J': case 'M':
636 /* = . ??? */
637 case '}': case 'n': case '`':
638 *pout++ = *p - '@' - 42;
639 break;
640 }
641 }
642 }
643 c->out_size = pout - c->out;
644
645 { /* verify the CRC32 sum */
646 u_int32_t part_crc32;
647 struct crc32_ctx crc32_context;
648
649 crc32_init_ctx(&crc32_context);
650 crc32_process_bytes(c->out, c->out_size, &crc32_context);
651 crc32_finish_ctx(&crc32_context);
652
653 if(is_multi_part)
654 {
655 u_int32_t file_crc32 = 0;
656
657 PARSE_TAG_OPTIONAL(YTAG_CRC32, atocrc32, file_crc32);
658 if(file_crc32 && f->total_parts && f->crc32 && (f->crc32 != file_crc32))
659 {
660 error(0, 0, _("%s: %s:%i: warning: File CRC mismatch in trailer -- previous parts says %08x, this part %08x -- ignoring this part"),
661 c->inname, f->outname, c->part, f->crc32, file_crc32);
662 }
663 else if(file_crc32)
664 f->crc32 = file_crc32;
665
666 PARSE_TAG(YTAG_PCRC32, atocrc32, part_crc32);
667 }
668 else
669 {
670 PARSE_TAG(YTAG_CRC32, atocrc32, part_crc32);
671 }
672
673 if(part_crc32 != crc32_read_ctx(&crc32_context))
674 {
675 error(0, 0, _("%s: %s:%i: Part CRC32 error -- got 0x%08x, should be 0x%08x"),
676 c->inname, f->outname, c->part, crc32_read_ctx(&crc32_context), part_crc32);
677 return EXIT_FAILURE;
678 }
679 }
680
681 /* Check the trailer part number. */
682 {
683 int trailer_part = 0;
684 PARSE_TAG_OPTIONAL(YTAG_PART, (int)atol, trailer_part);
685 if(!is_multi_part)
686 trailer_part++;
687 ASSERT(trailer_part > 0);
688
689 if(trailer_part != c->part)
690 {
691 error(0, 0, _("%s: %s:%i: warning: Part number mismatch in trailer (part %i); ignoring trailer"),
692 c->inname, f->outname, c->part, trailer_part);
693 }
694 }
695
696 /* All of the following are warnings, since the CRC matched. And
697 * frankly, I trust the CRC more than I trust the ability of some
698 * random sample of the human population to be able to code. */
699 {
700 off_t part_size = 0;
701
702 PARSE_TAG(YTAG_SIZE, (off_t)atol, part_size);
703 ASSERT(part_size >= 0);
704
705 if((size_t)part_size != c->out_size)
706 {
707 error(0, 0, _("%s: %s:%i: warning: Wrong part size -- decoded %lu bytes, expected %lu"),
708 c->inname, f->outname, c->part, c->out_size, part_size);
709 }
710
711 if(part_size != part_end - part_begin)
712 {
713 error(0, 0, _("%s: %s:%i: warning: Part size/range mismatch -- %lu != %lu-%lu"),
714 c->inname, f->outname, c->part, part_size, part_end, part_begin);
715 }
716 }
717
718 c->status = part_intact;
719
720 return EXIT_SUCCESS;
721 }
722
decode(inname,forced_outname,decoded_list)723 static int decode(inname, forced_outname, decoded_list)
724 const char *inname;
725 const char *forced_outname;
726 struct decoded_file **decoded_list;
727 {
728 int exit_status = EXIT_SUCCESS;
729 static struct decoded_file *f;
730 static read_enc re = NULL;
731 int no_begin = 1;
732
733 char buf[BUFSIZ];
734 char name[BUFSIZ];
735 char *p;
736
737 struct decode_context c;
738
739 #define INIT_DECODE_CTX() \
740 memset(&c, 0, sizeof(c)); \
741 c.inname = inname; \
742 c.status = part_broken; /* Assume broken unless we verify otherwise */
743
744 INIT_DECODE_CTX();
745
746 if(opt_verbose > 1)
747 error(0, 0, _("%s: Processing"), inname);
748
749 while(re || fgets_(buf, BUFSIZ, stdin))
750 {
751 int mode = 0644;
752 const char *outname;
753
754 if(re)
755 goto read_file;
756
757 outname = name;
758 if(!strncmp(buf, YMARKER_BEGIN, lenof(YMARKER_BEGIN)))
759 {
760 /* This is parsed again in read_yenc(). Somewhat wasteful. ;_; */
761 PARSE_TAG_OPTIONAL(YTAG_PART, (int)atol, c.part);
762 if(!c.part)
763 c.part++;
764
765 /* parse the name tag */
766 if(!(p = strstr(buf, YTAG_NAME)))
767 {
768 error(0, 0, _("%s: No `%s' tag found"), inname, YTAG_NAME);
769 exit_status = EXIT_FAILURE;
770 continue;
771 }
772 strcpy(name, p + lenof(YTAG_NAME));
773 p = name;
774
775 /* yEnc 1.3 allows for trailing spaces and quoted filenames */
776 p += strlen(p) - 1;
777 while((p > outname) && ((*p == '\r') || (*p == '\n') || (*p == ' ')))
778 *p-- = '\0';
779 if((p > outname) && (*p == '"') && (*outname == '"'))
780 {
781 *p = '\0';
782 ++outname;
783 }
784
785 re = read_yenc;
786 }
787 else if(!strncmp(buf, UUMARKER_BEGIN, lenof(UUMARKER_BEGIN)))
788 {
789 if(sscanf(buf + lenof(UUMARKER_BEGIN), "%o %[^\n]", &mode, name) != 2)
790 {
791 exit_status = EXIT_FAILURE;
792 continue;
793 }
794 c.part = 1;
795 re = read_stduu;
796 }
797 else if(!strncmp(buf, B64MARKER_BEGIN, lenof(B64MARKER_BEGIN)))
798 {
799 if(sscanf(buf + lenof(B64MARKER_BEGIN), "%o %[^\n]", &mode, name) != 2)
800 {
801 exit_status = EXIT_FAILURE;
802 continue;
803 }
804 c.part = 1;
805 re = read_base64;
806 }
807 else continue;
808
809 f = decoded_file_lookup(decoded_list, outname, mode, forced_outname);
810
811 /* Don't bother decoding duplicated parts. */
812 if((f->total_parts >= c.part)
813 && ((f->status[c.part - 1] == part_intact)
814 || (f->status[c.part - 1] == part_duplicated)))
815 {
816 /* This is OK, because out == NULL */
817 c.status = part_duplicated;
818 exit_status = EXIT_SUCCESS;
819 goto exit_skip;
820 }
821
822 /* make sure we've got a valid handle in f->handle */
823 if(decoded_file_open(f) != EXIT_SUCCESS)
824 {
825 /* Only show error for the first part. */
826 if(!f->total_parts)
827 error(0, errno, _("%s: %s"), c.inname, f->outname);
828
829 /* NASTY HACK: total parts isn't parsed by this stage
830 * yet; but we force it nonzero so that f->total_parts
831 * will get incremented below. */
832 c.total_parts = c.part;
833
834 exit_status = EXIT_FAILURE;
835 goto exit_skip;
836 }
837
838 read_file:
839
840 exit_status = (*re)(&c, f, buf);
841
842 exit_skip:
843
844 if(c.out)
845 {
846 /* Commit the part to file. */
847 if((c.status == part_intact) || opt_write_broken)
848 {
849 /* f always set before out */
850 if(fwrite(c.out, c.out_size, 1, f->handle) != 1)
851 {
852 error(0, errno, _("%s: %s:%i"), c.inname, f->outname, c.part);
853 return EXIT_FAILURE;
854 }
855 f->bytes_written += c.out_size;
856 }
857
858 /* are we getting the parts sequentially? */
859 if(c.part == f->crc32_prev_part + 1)
860 {
861 crc32_process_bytes(c.out, c.out_size, &f->crc32_context);
862 f->crc32_prev_part = c.part;
863 }
864 else
865 f->crc32_prev_part = -1;
866
867 free(c.out);
868 c.out = NULL;
869 }
870
871 if(f)
872 {
873 if(f->total_parts < c.total_parts)
874 {
875 enum part_status *ps;
876
877 ps = (enum part_status *)malloc(c.total_parts * sizeof(enum part_status));
878 memcpy(ps, f->status, f->total_parts * sizeof(enum part_status));
879 memset(ps + f->total_parts, 0, (c.total_parts - f->total_parts) * sizeof(enum part_status));
880 if(f->status)
881 free(f->status);
882 f->status = ps;
883 f->total_parts = c.total_parts;
884 }
885 f->status[c.part - 1] = c.status;
886
887 if(f->is_seekable && f->handle && (f->handle != stdout))
888 {
889 fclose(f->handle);
890 f->handle = NULL;
891 }
892 }
893
894 no_begin = 0;
895
896 /* does the handler want to hold on to stdin? */
897 if(exit_status == EXIT_EOF)
898 {
899 exit_status = EXIT_SUCCESS;
900 break;
901 }
902
903 re = NULL;
904 INIT_DECODE_CTX();
905 }
906
907 if(no_begin)
908 {
909 error(0, 0, _("%s: No `=ybegin' or `begin' line found"), inname);
910 exit_status = EXIT_FAILURE;
911 }
912
913 return exit_status;
914 }
915
916
917 #undef PARSE_TAG
918 #undef PARSE_TAG_OPTIONAL
919 #undef SEARCH_MARKER
920 #undef UUDECODE
921 #undef B64DECODE
922 #undef ASSERT
923
924 static void
usage(status)925 usage(status)
926 int status;
927 {
928 if(status)
929 fprintf(stderr, _("Try `%s --help' for more information.\n"),
930 program_name);
931 else
932 {
933 #ifdef __DJGPP__
934 setmode(fileno(stdout), O_TEXT);
935 #endif /* __DJGPP__ */
936 printf(_("\
937 %s %s -- Copyright (C) Liyang HU 2002-2003, http://nerv.cx/liyang/\n\
938 Usage: %s [FILE] ...\n\
939 -o, --output-file=FILE direct all output to FILE (use - for stdout)\n\
940 -D, --directory=DIR write output files to DIR\n\
941 -e, --evil-filename allow evil characters in filename\n\
942 -c, --clobber-filename append .n to filename if it already exists\n\
943 -f, --force-overwrite overwrite output file; truncate unless -b is used.\n\
944 -b, --write-broken write decoded part even if verified to be broken,\n\
945 and don't append .broken to the filename\n\
946 -i, --broken-encoder work around broken encoders (where CRC=00000001)\n\
947 -r, --remove-broken unlink instead of renaming broken files\n\
948 -l, --large-parts expect parts larger than %uk\n\
949 -h, --help display this help and exit\n\
950 -v, --verbose detailed progress reports (repeat for more)\n\
951 -V, --version output version information and exit\n\
952 Refer to the man page for details.\n"),
953 PACKAGE, VERSION,
954 program_name,
955 PART_SIZE_SOFT_LIMIT >> 10);
956 }
957 exit(status);
958 }
959
960 int
main(argc,argv)961 main(argc, argv)
962 int argc;
963 char * const *argv;
964 {
965 int opt;
966 int exit_status;
967 const char *outname;
968 struct decoded_file *decoded_list = NULL;
969
970 struct option longopts[] =
971 {
972 { "output-file", required_argument, NULL, (int)'o' },
973 { "directory", required_argument, NULL, (int)'D' },
974 { "evil-filename", no_argument, NULL, (int)'e' },
975 { "clobber-filename", no_argument, NULL, (int)'c' },
976 { "force-overwrite", no_argument, NULL, (int)'f' },
977 { "write-broken", no_argument, NULL, (int)'b' },
978 { "broken-encoder", no_argument, NULL, (int)'i' },
979 { "remove-broken", no_argument, NULL, (int)'r' },
980 { "large-parts", no_argument, NULL, (int)'l' },
981 { "help", no_argument, NULL, (int)'h' },
982 { "verbose", no_argument, NULL, (int)'v' },
983 { "version", no_argument, NULL, (int)'V' },
984 { NULL, 0, NULL, 0 }
985 };
986
987 program_name = argv[0];
988 outname = NULL;
989
990 #ifdef WIN32
991 /* Never underestimate stupid people in large numbers... */
992 _setmode(_fileno(stdin), _O_BINARY);
993 _setmode(_fileno(stdout), _O_BINARY);
994 #endif
995
996 #ifdef __DJGPP__
997 _fmode = O_BINARY;
998 setmode(fileno(stdout), O_BINARY);
999 if (isatty(fileno(stdin)) == 0)
1000 setmode(fileno(stdin), O_BINARY);
1001 if (isatty(fileno(stdin)) !=0)
1002 setbuf(stdin, NULL);
1003 #endif /* __DJGPP__ */
1004
1005 while(opt = getopt_long(argc, argv, "o:D:ecfbirlhvV", longopts, (int *)NULL),
1006 opt != EOF)
1007 {
1008 switch(opt)
1009 {
1010 case 'o':
1011 outname = optarg;
1012 break;
1013
1014 case 'D':
1015 opt_directory = optarg;
1016 break;
1017
1018 case 'e':
1019 opt_evil_filename++;
1020 break;
1021
1022 case 'c':
1023 opt_clobber_filename = 1;
1024 break;
1025
1026 case 'f':
1027 opt_force_overwrite = 1;
1028 break;
1029
1030 case 'b':
1031 opt_write_broken = 1;
1032 break;
1033
1034 case 'i':
1035 opt_broken_encoder = 1;
1036 break;
1037
1038 case 'r':
1039 opt_remove_broken = 1;
1040 break;
1041
1042 case 'l':
1043 opt_large_parts = 1;
1044 break;
1045
1046 case 'h':
1047 usage(EXIT_SUCCESS);
1048
1049 case 'v':
1050 opt_verbose++;
1051 break;
1052
1053 case 'V':
1054 printf("%s %s\n", PACKAGE, VERSION);
1055 exit(EXIT_SUCCESS);
1056
1057 case 0:
1058 break;
1059
1060 default:
1061 usage(EXIT_FAILURE);
1062 }
1063 }
1064
1065 if(optind == argc)
1066 exit_status = decode("stdin", outname, &decoded_list);
1067 else
1068 {
1069 char current_directory[BUFSIZ];
1070
1071 if(opt_directory)
1072 getcwd(current_directory, sizeof(current_directory));
1073
1074 exit_status = EXIT_SUCCESS;
1075 do
1076 {
1077 int det_return;
1078
1079 /* note the order: we want to exit this loop in
1080 * opt_directory, if it is specified. */
1081 if(opt_directory)
1082 chdir(current_directory);
1083
1084 if(!freopen(argv[optind], "rb", stdin))
1085 {
1086 error(0, errno, "%s", argv[optind]);
1087 exit_status = EXIT_FAILURE;
1088 break;
1089 }
1090
1091 if(opt_directory && chdir(opt_directory))
1092 {
1093 error(0, errno, "%s", opt_directory);
1094 exit_status = EXIT_FAILURE;
1095 break;
1096 }
1097
1098 if((det_return = decode(argv[optind], outname, &decoded_list))
1099 != EXIT_SUCCESS)
1100 exit_status = det_return;
1101 }
1102 while(++optind < argc);
1103 }
1104
1105 /* determine exit_status from decoded_list instead: */
1106 exit_status = EXIT_SUCCESS;
1107 while(decoded_list)
1108 {
1109 int broken_file = 0;
1110 struct decoded_file *f = decoded_list;
1111 int i;
1112
1113 if(!f->previously_existed)
1114 {
1115 /* Did we write to this file at all? Yes: */
1116
1117 for(i = 0; i < f->total_parts; i++)
1118 {
1119 switch(f->status[i])
1120 {
1121 case part_missing:
1122 error(0, 0, _("%s:%i: Part missing (of %i parts)"),
1123 f->outname, i + 1, f->total_parts);
1124 break;
1125 case part_broken:
1126 error(0, 0, _("%s:%i: Part broken (of %i parts)"),
1127 f->outname, i + 1, f->total_parts);
1128 break;
1129 case part_duplicated:
1130 error(0, 0, _("%s:%i: warning: Part duplicated (of %i parts); ignored"),
1131 f->outname, i + 1, f->total_parts);
1132 case part_intact:
1133 continue;
1134 }
1135 broken_file = 1;
1136 exit_status = EXIT_FAILURE;
1137 }
1138
1139 if(f->bytes_written != f->total_size)
1140 {
1141 error(0, 0, _("%s: wrote %li bytes; should have been %li"),
1142 f->outname, f->bytes_written, f->total_size);
1143 broken_file = 1;
1144 exit_status = EXIT_FAILURE;
1145 }
1146
1147 /* go over the output file once again to calculate the CRC */
1148 if((f->crc32_prev_part == -1) && f->is_seekable)
1149 {
1150 char buf[BUFSIZ];
1151 size_t bytes_read;
1152
1153 decoded_file_open(f);
1154 fseek(f->handle, 0, SEEK_SET);
1155 crc32_init_ctx(&f->crc32_context);
1156 while((bytes_read = fread(buf, 1, BUFSIZ, f->handle)))
1157 crc32_process_bytes(buf, bytes_read, &f->crc32_context);
1158
1159 f->crc32_prev_part = 0;
1160 }
1161
1162 if(f->handle && (f->handle != stdout))
1163 fclose(f->handle);
1164
1165 crc32_finish_ctx(&f->crc32_context);
1166 if((f->crc32_prev_part >= 0) && f->crc32 && (f->crc32 != crc32_read_ctx(&f->crc32_context)))
1167 {
1168 if (!opt_broken_encoder || f->crc32 != 0x00000001)
1169 {
1170 error(0, 0, _("%s: File CRC error -- got 0x%08x, should be 0x%08x"),
1171 f->outname, crc32_read_ctx(&f->crc32_context), f->crc32);
1172 broken_file = 1;
1173 exit_status = EXIT_FAILURE;
1174 }
1175 else
1176 error(0, 0, _("%s: warning: File encoded with a broken encoder (fake CRC: 0x00000001, should be 0x%08x) -- please pester the sender to upgrade their software"),
1177 f->outname, crc32_read_ctx(&f->crc32_context));
1178
1179 }
1180
1181 if(broken_file)
1182 {
1183
1184 /* If we have a broken file:
1185 * WB RB ACTION
1186 * 0 0 rename
1187 * 0 1 unlink
1188 * 1 0 nothing
1189 * 1 1 rename */
1190
1191 /* When both WB and RB are specified, the file is `removed' in
1192 * the sense that there is no longer a file with that name. */
1193
1194 if(opt_write_broken == opt_remove_broken)
1195 {
1196 char *outname_broken = malloc(strlen(f->outname)
1197 + lenof(YYDEC_BROKEN_SUFFIX) + 1);
1198 strcpy(outname_broken, f->outname);
1199 strcat(outname_broken, YYDEC_BROKEN_SUFFIX);
1200 error(0, 0, "%s: renamed to %s", f->outname, outname_broken);
1201 rename(f->outname, outname_broken);
1202 free(outname_broken);
1203 }
1204 else if(!opt_write_broken && opt_remove_broken)
1205 unlink(f->outname);
1206 }
1207
1208 } /* previously_existed == we haven't touched this file:
1209 * do nothing. (except release malloc()'d storage) */
1210
1211 decoded_list = f->next;
1212 if(f->filename)
1213 free(f->filename);
1214 if(f->outname)
1215 free(f->outname);
1216 if(f->status)
1217 free(f->status);
1218 free(f);
1219 }
1220
1221 exit(exit_status);
1222 }
1223
1224