Commit | Line | Data |
---|---|---|
8523533c TK |
1 | /************************************************* |
2 | * Exim - an Internet mail transport agent * | |
3 | *************************************************/ | |
4 | ||
5 | /* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */ | |
6 | /* License: GPL */ | |
7 | ||
8 | /* Code for unpacking MIME containers. Called from acl.c. */ | |
9 | ||
10 | #include "exim.h" | |
11 | #ifdef WITH_OLD_DEMIME | |
12 | ||
13 | #include "demime.h" | |
14 | ||
15 | uschar demime_reason_buffer[1024]; | |
16 | struct file_extension *file_extensions = NULL; | |
17 | ||
18 | int demime(uschar **listptr) { | |
19 | int sep = 0; | |
20 | uschar *list = *listptr; | |
21 | uschar *option; | |
22 | uschar option_buffer[64]; | |
c6e692d7 | 23 | unsigned long mbox_size; |
8523533c TK |
24 | FILE *mbox_file; |
25 | uschar defer_error_buffer[1024]; | |
26 | int demime_rc = 0; | |
8e669ac1 | 27 | |
8523533c TK |
28 | /* reset found_extension variable */ |
29 | found_extension = NULL; | |
8e669ac1 | 30 | |
8523533c TK |
31 | /* try to find 1st option */ |
32 | if ((option = string_nextinlist(&list, &sep, | |
33 | option_buffer, | |
34 | sizeof(option_buffer))) != NULL) { | |
8e669ac1 | 35 | |
8523533c TK |
36 | /* parse 1st option */ |
37 | if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) ) { | |
38 | /* explicitly no demimeing */ | |
39 | return FAIL; | |
40 | }; | |
41 | } | |
42 | else { | |
43 | /* no options -> no demimeing */ | |
44 | return FAIL; | |
45 | }; | |
8e669ac1 | 46 | |
8523533c | 47 | /* make sure the eml mbox file is spooled up */ |
8544e77a | 48 | mbox_file = spool_mbox(&mbox_size, NULL); |
8e669ac1 | 49 | |
8523533c TK |
50 | if (mbox_file == NULL) { |
51 | /* error while spooling */ | |
52 | log_write(0, LOG_MAIN|LOG_PANIC, | |
53 | "demime acl condition: error while creating mbox spool file"); | |
8e669ac1 | 54 | return DEFER; |
8523533c | 55 | }; |
8e669ac1 | 56 | |
8523533c TK |
57 | /* call demimer if not already done earlier */ |
58 | if (!demime_ok) | |
59 | demime_rc = mime_demux(mbox_file, defer_error_buffer); | |
8e669ac1 | 60 | |
f1e894f3 | 61 | (void)fclose(mbox_file); |
8e669ac1 | 62 | |
8523533c TK |
63 | if (demime_rc == DEFER) { |
64 | /* temporary failure (DEFER => DEFER) */ | |
65 | log_write(0, LOG_MAIN, | |
66 | "demime acl condition: %s", defer_error_buffer); | |
67 | return DEFER; | |
68 | }; | |
8e669ac1 | 69 | |
8523533c TK |
70 | /* set demime_ok to avoid unpacking again */ |
71 | demime_ok = 1; | |
8e669ac1 | 72 | |
8523533c TK |
73 | /* check for file extensions, if there */ |
74 | while (option != NULL) { | |
75 | struct file_extension *this_extension = file_extensions; | |
8e669ac1 | 76 | |
8523533c TK |
77 | /* Look for the wildcard. If it is found, we always return true. |
78 | The user must then use a custom condition to evaluate demime_errorlevel */ | |
79 | if (Ustrcmp(option,"*") == 0) { | |
80 | found_extension = NULL; | |
81 | return OK; | |
82 | }; | |
83 | ||
84 | /* loop thru extension list */ | |
8e669ac1 | 85 | while (this_extension != NULL) { |
8523533c TK |
86 | if (strcmpic(option, this_extension->file_extension_string) == 0) { |
87 | /* found one */ | |
88 | found_extension = this_extension->file_extension_string; | |
89 | return OK; | |
90 | }; | |
91 | this_extension = this_extension->next; | |
92 | }; | |
8e669ac1 | 93 | |
8523533c TK |
94 | /* grab next extension from option list */ |
95 | option = string_nextinlist(&list, &sep, | |
96 | option_buffer, | |
97 | sizeof(option_buffer)); | |
98 | }; | |
8e669ac1 | 99 | |
8523533c TK |
100 | /* nothing found */ |
101 | return FAIL; | |
102 | } | |
103 | ||
104 | ||
105 | /************************************************* | |
106 | * small hex_str -> integer conversion function * | |
107 | *************************************************/ | |
108 | ||
109 | /* needed for quoted-printable | |
110 | */ | |
111 | ||
112 | unsigned int mime_hstr_i(uschar *cptr) { | |
113 | unsigned int i, j = 0; | |
8e669ac1 | 114 | |
8523533c TK |
115 | while (cptr && *cptr && isxdigit(*cptr)) { |
116 | i = *cptr++ - '0'; | |
117 | if (9 < i) i -= 7; | |
118 | j <<= 4; | |
119 | j |= (i & 0x0f); | |
120 | } | |
8e669ac1 | 121 | |
8523533c TK |
122 | return(j); |
123 | } | |
124 | ||
125 | ||
126 | /************************************************* | |
127 | * decode quoted-printable chars * | |
128 | *************************************************/ | |
129 | ||
130 | /* gets called when we hit a = | |
131 | returns: new pointer position | |
132 | result code in c: | |
133 | -2 - decode error | |
134 | -1 - soft line break, no char | |
135 | 0-255 - char to write | |
136 | */ | |
137 | ||
138 | uschar *mime_decode_qp(uschar *qp_p,int *c) { | |
139 | uschar hex[] = {0,0,0}; | |
140 | int nan = 0; | |
141 | uschar *initial_pos = qp_p; | |
8e669ac1 | 142 | |
8523533c TK |
143 | /* advance one char */ |
144 | qp_p++; | |
8e669ac1 | 145 | |
8523533c TK |
146 | REPEAT_FIRST: |
147 | if ( (*qp_p == '\t') || (*qp_p == ' ') || (*qp_p == '\r') ) { | |
148 | /* tab or whitespace may follow | |
149 | just ignore it, but remember | |
150 | that this is not a valid hex | |
151 | encoding any more */ | |
152 | nan = 1; | |
153 | qp_p++; | |
154 | goto REPEAT_FIRST; | |
155 | } | |
156 | else if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) { | |
157 | /* this is a valid hex char, if nan is unset */ | |
158 | if (nan) { | |
159 | /* this is illegal */ | |
160 | *c = -2; | |
161 | return initial_pos; | |
162 | } | |
163 | else { | |
164 | hex[0] = *qp_p; | |
165 | qp_p++; | |
166 | }; | |
167 | } | |
8e669ac1 | 168 | else if (*qp_p == '\n') { |
8523533c TK |
169 | /* hit soft line break already, continue */ |
170 | *c = -1; | |
171 | return qp_p; | |
172 | } | |
173 | else { | |
174 | /* illegal char here */ | |
175 | *c = -2; | |
176 | return initial_pos; | |
177 | }; | |
8e669ac1 | 178 | |
8523533c TK |
179 | if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) { |
180 | if (hex[0] > 0) { | |
181 | hex[1] = *qp_p; | |
182 | /* do hex conversion */ | |
183 | *c = mime_hstr_i(hex); | |
184 | qp_p++; | |
185 | return qp_p; | |
186 | } | |
187 | else { | |
188 | /* huh ? */ | |
189 | *c = -2; | |
8e669ac1 | 190 | return initial_pos; |
8523533c TK |
191 | }; |
192 | } | |
193 | else { | |
194 | /* illegal char */ | |
195 | *c = -2; | |
8e669ac1 | 196 | return initial_pos; |
8523533c | 197 | }; |
8e669ac1 | 198 | |
8523533c TK |
199 | } |
200 | ||
201 | ||
202 | /************************************************* | |
203 | * open new dump file * | |
204 | *************************************************/ | |
205 | ||
206 | /* open new dump file | |
207 | returns: -2 soft error | |
208 | or file #, FILE * in f | |
209 | */ | |
210 | ||
211 | int mime_get_dump_file(uschar *extension, FILE **f, uschar *info) { | |
212 | uschar file_name[1024]; | |
213 | int result; | |
214 | unsigned int file_nr; | |
215 | uschar default_extension[] = ".com"; | |
216 | uschar *p; | |
8e669ac1 | 217 | |
8523533c TK |
218 | if (extension == NULL) |
219 | extension = default_extension; | |
8e669ac1 | 220 | |
8523533c TK |
221 | /* scan the proposed extension. |
222 | if it is longer than 4 chars, or | |
223 | contains exotic chars, use the default extension */ | |
8e669ac1 | 224 | |
8523533c TK |
225 | /* if (Ustrlen(extension) > 4) { |
226 | extension = default_extension; | |
227 | }; | |
8e669ac1 PH |
228 | */ |
229 | ||
8523533c | 230 | p = extension+1; |
8e669ac1 | 231 | |
8523533c TK |
232 | while (*p != 0) { |
233 | *p = (uschar)tolower((uschar)*p); | |
234 | if ( (*p < 97) || (*p > 122) ) { | |
235 | extension = default_extension; | |
8e669ac1 | 236 | break; |
8523533c TK |
237 | }; |
238 | p++; | |
239 | }; | |
8e669ac1 | 240 | |
8523533c TK |
241 | /* find a new file to write to */ |
242 | file_nr = 0; | |
243 | do { | |
244 | struct stat mystat; | |
8e669ac1 | 245 | |
b07e6aa3 | 246 | (void)string_format(file_name,1024,"%s/scan/%s/%s-%05u%s",spool_directory,message_id,message_id,file_nr,extension); |
8523533c TK |
247 | file_nr++; |
248 | if (file_nr >= MIME_SANITY_MAX_DUMP_FILES) { | |
249 | /* max parts reached */ | |
250 | mime_trigger_error(MIME_ERRORLEVEL_TOO_MANY_PARTS); | |
251 | break; | |
252 | }; | |
253 | result = stat(CS file_name,&mystat); | |
254 | } | |
255 | while(result != -1); | |
8e669ac1 | 256 | |
2632889e | 257 | *f = modefopen(file_name,"wb+",SPOOL_MODE); |
8523533c TK |
258 | if (*f == NULL) { |
259 | /* cannot open new dump file, disk full ? -> soft error */ | |
b07e6aa3 | 260 | (void)string_format(info, 1024,"unable to open dump file"); |
8523533c TK |
261 | return -2; |
262 | }; | |
8e669ac1 | 263 | |
8523533c TK |
264 | return file_nr; |
265 | } | |
266 | ||
267 | ||
268 | /************************************************* | |
269 | * Find a string in a mime header * | |
270 | *************************************************/ | |
271 | ||
272 | /* Find a string in a mime header, and optionally fill in | |
273 | the value associated with it into *value | |
8e669ac1 | 274 | |
8523533c TK |
275 | returns: 0 - nothing found |
276 | 1 - found param | |
277 | 2 - found param + value | |
278 | */ | |
279 | ||
280 | int mime_header_find(uschar *header, uschar *param, uschar **value) { | |
281 | uschar *needle; | |
8e669ac1 | 282 | |
8523533c TK |
283 | needle = strstric(header,param,FALSE); |
284 | if (needle != NULL) { | |
285 | if (value != NULL) { | |
286 | needle += Ustrlen(param); | |
287 | if (*needle == '=') { | |
288 | uschar *value_start; | |
289 | uschar *value_end; | |
8e669ac1 | 290 | |
8523533c TK |
291 | value_start = needle + 1; |
292 | value_end = strstric(value_start,US";",FALSE); | |
293 | if (value_end != NULL) { | |
294 | /* allocate mem for value */ | |
295 | *value = (uschar *)malloc((value_end - value_start)+1); | |
296 | if (*value == NULL) | |
297 | return 0; | |
8e669ac1 | 298 | |
8523533c TK |
299 | Ustrncpy(*value,value_start,(value_end - value_start)); |
300 | (*value)[(value_end - value_start)] = '\0'; | |
301 | return 2; | |
302 | }; | |
303 | }; | |
304 | }; | |
305 | return 1; | |
306 | }; | |
307 | return 0; | |
308 | } | |
309 | ||
310 | ||
311 | /************************************************* | |
312 | * Read a line of MIME input * | |
313 | *************************************************/ | |
314 | /* returns status code, one of | |
315 | MIME_READ_LINE_EOF 0 | |
316 | MIME_READ_LINE_OK 1 | |
317 | MIME_READ_LINE_OVERFLOW 2 | |
318 | ||
319 | In header mode, the line will be "cooked". | |
320 | */ | |
321 | ||
322 | int mime_read_line(FILE *f, int mime_demux_mode, uschar *buffer, long *num_copied) { | |
323 | int c = EOF; | |
324 | int done = 0; | |
325 | int header_value_mode = 0; | |
326 | int header_open_brackets = 0; | |
8e669ac1 | 327 | |
8523533c | 328 | *num_copied = 0; |
8e669ac1 | 329 | |
8523533c | 330 | while(!done) { |
8e669ac1 | 331 | |
8523533c TK |
332 | c = fgetc(f); |
333 | if (c == EOF) break; | |
8e669ac1 | 334 | |
8523533c TK |
335 | /* --------- header mode -------------- */ |
336 | if (mime_demux_mode == MIME_DEMUX_MODE_MIME_HEADERS) { | |
8e669ac1 | 337 | |
8523533c TK |
338 | /* always skip CRs */ |
339 | if (c == '\r') continue; | |
8e669ac1 | 340 | |
8523533c TK |
341 | if (c == '\n') { |
342 | if ((*num_copied) > 0) { | |
343 | /* look if next char is '\t' or ' ' */ | |
344 | c = fgetc(f); | |
345 | if (c == EOF) break; | |
346 | if ( (c == '\t') || (c == ' ') ) continue; | |
f1e894f3 | 347 | (void)ungetc(c,f); |
8523533c TK |
348 | }; |
349 | /* end of the header, terminate with ';' */ | |
350 | c = ';'; | |
351 | done = 1; | |
352 | }; | |
8e669ac1 | 353 | |
8523533c TK |
354 | /* skip control characters */ |
355 | if (c < 32) continue; | |
8e669ac1 | 356 | |
8523533c TK |
357 | /* skip whitespace + tabs */ |
358 | if ( (c == ' ') || (c == '\t') ) | |
359 | continue; | |
360 | ||
361 | if (header_value_mode) { | |
362 | /* --------- value mode ----------- */ | |
363 | /* skip quotes */ | |
364 | if (c == '"') continue; | |
8e669ac1 | 365 | |
8523533c TK |
366 | /* leave value mode on ';' */ |
367 | if (c == ';') { | |
368 | header_value_mode = 0; | |
369 | }; | |
370 | /* -------------------------------- */ | |
371 | } | |
372 | else { | |
373 | /* -------- non-value mode -------- */ | |
374 | if (c == '\\') { | |
375 | /* quote next char. can be used | |
376 | to escape brackets. */ | |
377 | c = fgetc(f); | |
378 | if (c == EOF) break; | |
379 | } | |
380 | else if (c == '(') { | |
381 | header_open_brackets++; | |
382 | continue; | |
383 | } | |
384 | else if ((c == ')') && header_open_brackets) { | |
385 | header_open_brackets--; | |
386 | continue; | |
387 | } | |
388 | else if ( (c == '=') && !header_open_brackets ) { | |
389 | /* enter value mode */ | |
8e669ac1 | 390 | header_value_mode = 1; |
8523533c | 391 | }; |
8e669ac1 | 392 | |
8523533c TK |
393 | /* skip chars while we are in a comment */ |
394 | if (header_open_brackets > 0) | |
395 | continue; | |
396 | /* -------------------------------- */ | |
397 | }; | |
398 | } | |
399 | /* ------------------------------------ */ | |
400 | else { | |
401 | /* ----------- non-header mode -------- */ | |
402 | /* break on '\n' */ | |
403 | if (c == '\n') | |
404 | done = 1; | |
405 | /* ------------------------------------ */ | |
406 | }; | |
8e669ac1 | 407 | |
8523533c TK |
408 | /* copy the char to the buffer */ |
409 | buffer[*num_copied] = (uschar)c; | |
410 | /* raise counter */ | |
411 | (*num_copied)++; | |
8e669ac1 | 412 | |
8523533c TK |
413 | /* break if buffer is full */ |
414 | if (*num_copied > MIME_SANITY_MAX_LINE_LENGTH-1) { | |
415 | done = 1; | |
416 | }; | |
417 | } | |
8e669ac1 | 418 | |
8523533c TK |
419 | /* 0-terminate */ |
420 | buffer[*num_copied] = '\0'; | |
8e669ac1 | 421 | |
8523533c TK |
422 | if (*num_copied > MIME_SANITY_MAX_LINE_LENGTH-1) |
423 | return MIME_READ_LINE_OVERFLOW; | |
424 | else | |
425 | if (c == EOF) | |
426 | return MIME_READ_LINE_EOF; | |
427 | else | |
428 | return MIME_READ_LINE_OK; | |
429 | } | |
430 | ||
431 | ||
432 | /************************************************* | |
433 | * Check for a MIME boundary * | |
434 | *************************************************/ | |
435 | ||
436 | /* returns: 0 - no boundary found | |
437 | 1 - start boundary found | |
438 | 2 - end boundary found | |
439 | */ | |
440 | ||
441 | int mime_check_boundary(uschar *line, struct boundary *boundaries) { | |
442 | struct boundary *thisboundary = boundaries; | |
443 | uschar workbuf[MIME_SANITY_MAX_LINE_LENGTH+1]; | |
444 | unsigned int i,j=0; | |
8e669ac1 | 445 | |
8523533c TK |
446 | /* check for '--' first */ |
447 | if (Ustrncmp(line,"--",2) == 0) { | |
8e669ac1 | 448 | |
8523533c TK |
449 | /* strip tab and space */ |
450 | for (i = 2; i < Ustrlen(line); i++) { | |
451 | if ((line[i] != ' ') && (line[i] != '\t')) { | |
452 | workbuf[j] = line[i]; | |
384152a6 | 453 | j++; |
8523533c TK |
454 | }; |
455 | }; | |
456 | workbuf[j+1]='\0'; | |
457 | ||
458 | while(thisboundary != NULL) { | |
459 | if (Ustrncmp(workbuf,thisboundary->boundary_string,Ustrlen(thisboundary->boundary_string)) == 0) { | |
460 | if (Ustrncmp(&workbuf[Ustrlen(thisboundary->boundary_string)],"--",2) == 0) { | |
461 | /* final boundary found */ | |
462 | return 2; | |
8e669ac1 | 463 | }; |
8523533c TK |
464 | return 1; |
465 | }; | |
466 | thisboundary = thisboundary->next; | |
467 | }; | |
468 | }; | |
8e669ac1 PH |
469 | |
470 | return 0; | |
8523533c TK |
471 | } |
472 | ||
473 | ||
474 | /************************************************* | |
475 | * Check for start of a UUENCODE block * | |
476 | *************************************************/ | |
477 | ||
478 | /* returns 0 for no hit, | |
479 | >0 for hit | |
480 | */ | |
481 | ||
482 | int mime_check_uu_start(uschar *line, uschar *uu_file_extension, int *has_tnef) { | |
8e669ac1 | 483 | |
8523533c TK |
484 | if ( (strncmpic(line,US"begin ",6) == 0)) { |
485 | uschar *uu_filename = &line[6]; | |
8e669ac1 | 486 | |
8523533c TK |
487 | /* skip perms, if present */ |
488 | Ustrtoul(&line[6],&uu_filename,10); | |
8e669ac1 | 489 | |
8523533c TK |
490 | /* advance one char */ |
491 | uu_filename++; | |
8e669ac1 | 492 | |
8523533c TK |
493 | /* This should be the filename. |
494 | Check if winmail.dat is present, | |
495 | which indicates TNEF. */ | |
496 | if (strncmpic(uu_filename,US"winmail.dat",11) == 0) { | |
8e669ac1 | 497 | *has_tnef = 1; |
8523533c | 498 | }; |
8e669ac1 | 499 | |
8523533c TK |
500 | /* reverse to dot if present, |
501 | copy up to 4 chars for the extension */ | |
502 | if (Ustrrchr(uu_filename,'.') != NULL) | |
503 | uu_filename = Ustrrchr(uu_filename,'.'); | |
8e669ac1 | 504 | |
8523533c TK |
505 | return sscanf(CS uu_filename, "%4[.0-9A-Za-z]",CS uu_file_extension); |
506 | } | |
507 | else { | |
508 | /* nothing found */ | |
509 | return 0; | |
510 | }; | |
511 | } | |
512 | ||
513 | ||
514 | /************************************************* | |
515 | * Decode a uu line * | |
516 | *************************************************/ | |
517 | ||
518 | /* returns number of decoded bytes | |
519 | -2 for soft errors | |
520 | */ | |
521 | ||
522 | int warned_about_uudec_line_sanity_1 = 0; | |
523 | int warned_about_uudec_line_sanity_2 = 0; | |
524 | long uu_decode_line(uschar *line, uschar **data, long line_len, uschar *info) { | |
525 | uschar *p; | |
526 | long num_decoded = 0; | |
527 | uschar tmp_c; | |
528 | uschar *work; | |
529 | int uu_decoded_line_len, uu_encoded_line_len; | |
8e669ac1 | 530 | |
8523533c TK |
531 | /* allocate memory for data and work buffer */ |
532 | *data = (uschar *)malloc(line_len); | |
533 | if (*data == NULL) { | |
b07e6aa3 | 534 | (void)string_format(info, 1024,"unable to allocate %lu bytes",line_len); |
8523533c TK |
535 | return -2; |
536 | }; | |
537 | ||
538 | work = (uschar *)malloc(line_len); | |
539 | if (work == NULL) { | |
b07e6aa3 | 540 | (void)string_format(info, 1024,"unable to allocate %lu bytes",line_len); |
8523533c TK |
541 | return -2; |
542 | }; | |
8e669ac1 | 543 | |
8523533c | 544 | memcpy(work,line,line_len); |
8e669ac1 | 545 | |
8523533c TK |
546 | /* First char is line length |
547 | This is microsofts way of getting it. Scary. */ | |
548 | if (work[0] < 32) { | |
549 | /* ignore this line */ | |
550 | return 0; | |
551 | } | |
552 | else { | |
553 | uu_decoded_line_len = uudec[work[0]]; | |
554 | }; | |
8e669ac1 | 555 | |
8523533c TK |
556 | p = &work[1]; |
557 | ||
558 | while (*p > 32) { | |
559 | *p = uudec[*p]; | |
560 | p++; | |
561 | }; | |
562 | ||
563 | uu_encoded_line_len = (p - &work[1]); | |
564 | p = &work[1]; | |
8e669ac1 | 565 | |
8523533c TK |
566 | /* check that resulting line length is a multiple of 4 */ |
567 | if ( ( uu_encoded_line_len % 4 ) != 0) { | |
568 | if (!warned_about_uudec_line_sanity_1) { | |
569 | mime_trigger_error(MIME_ERRORLEVEL_UU_MISALIGNED); | |
570 | warned_about_uudec_line_sanity_1 = 1; | |
571 | }; | |
572 | return -1; | |
573 | }; | |
574 | ||
575 | /* check that the line length matches */ | |
576 | if ( ( (((uu_encoded_line_len/4)*3)-2) > uu_decoded_line_len ) || (((uu_encoded_line_len/4)*3) < uu_decoded_line_len) ) { | |
577 | if (!warned_about_uudec_line_sanity_2) { | |
578 | mime_trigger_error(MIME_ERRORLEVEL_UU_LINE_LENGTH); | |
579 | warned_about_uudec_line_sanity_2 = 1; | |
580 | }; | |
581 | return -1; | |
582 | }; | |
583 | ||
584 | while ( ((p - &work[1]) < uu_encoded_line_len) && (num_decoded < uu_decoded_line_len)) { | |
8e669ac1 | 585 | |
8523533c TK |
586 | /* byte 0 ---------------------- */ |
587 | if ((p - &work[1] + 1) >= uu_encoded_line_len) { | |
588 | return 0; | |
589 | } | |
8e669ac1 | 590 | |
8523533c TK |
591 | (*data)[num_decoded] = *p; |
592 | (*data)[num_decoded] <<= 2; | |
8e669ac1 | 593 | |
8523533c TK |
594 | tmp_c = *(p+1); |
595 | tmp_c >>= 4; | |
596 | (*data)[num_decoded] |= tmp_c; | |
8e669ac1 | 597 | |
8523533c TK |
598 | num_decoded++; |
599 | p++; | |
8e669ac1 | 600 | |
8523533c TK |
601 | /* byte 1 ---------------------- */ |
602 | if ((p - &work[1] + 1) >= uu_encoded_line_len) { | |
603 | return 0; | |
604 | } | |
8e669ac1 | 605 | |
8523533c TK |
606 | (*data)[num_decoded] = *p; |
607 | (*data)[num_decoded] <<= 4; | |
8e669ac1 | 608 | |
8523533c TK |
609 | tmp_c = *(p+1); |
610 | tmp_c >>= 2; | |
611 | (*data)[num_decoded] |= tmp_c; | |
8e669ac1 | 612 | |
8523533c TK |
613 | num_decoded++; |
614 | p++; | |
8e669ac1 | 615 | |
8523533c TK |
616 | /* byte 2 ---------------------- */ |
617 | if ((p - &work[1] + 1) >= uu_encoded_line_len) { | |
618 | return 0; | |
619 | } | |
8e669ac1 | 620 | |
8523533c TK |
621 | (*data)[num_decoded] = *p; |
622 | (*data)[num_decoded] <<= 6; | |
8e669ac1 | 623 | |
8523533c | 624 | (*data)[num_decoded] |= *(p+1); |
8e669ac1 | 625 | |
8523533c TK |
626 | num_decoded++; |
627 | p+=2; | |
8e669ac1 | 628 | |
8523533c | 629 | }; |
8e669ac1 | 630 | |
8523533c TK |
631 | return uu_decoded_line_len; |
632 | } | |
633 | ||
634 | ||
635 | /************************************************* | |
636 | * Decode a b64 or qp line * | |
637 | *************************************************/ | |
638 | ||
639 | /* returns number of decoded bytes | |
640 | -1 for hard errors | |
641 | -2 for soft errors | |
642 | */ | |
643 | ||
644 | int warned_about_b64_line_length = 0; | |
645 | int warned_about_b64_line_sanity = 0; | |
646 | int warned_about_b64_illegal_char = 0; | |
647 | int warned_about_qp_line_sanity = 0; | |
648 | long mime_decode_line(int mime_demux_mode,uschar *line, uschar **data, long max_data_len, uschar *info) { | |
649 | uschar *p; | |
650 | long num_decoded = 0; | |
651 | int offset = 0; | |
652 | uschar tmp_c; | |
8e669ac1 | 653 | |
8523533c TK |
654 | /* allocate memory for data */ |
655 | *data = (uschar *)malloc(max_data_len); | |
656 | if (*data == NULL) { | |
b07e6aa3 | 657 | (void)string_format(info, 1024,"unable to allocate %lu bytes",max_data_len); |
8523533c TK |
658 | return -2; |
659 | }; | |
8e669ac1 | 660 | |
8523533c TK |
661 | if (mime_demux_mode == MIME_DEMUX_MODE_BASE64) { |
662 | /* ---------------------------------------------- */ | |
8e669ac1 | 663 | |
8523533c TK |
664 | /* NULL out trailing '\r' and '\n' chars */ |
665 | while (Ustrrchr(line,'\r') != NULL) { | |
666 | *(Ustrrchr(line,'\r')) = '\0'; | |
667 | }; | |
668 | while (Ustrrchr(line,'\n') != NULL) { | |
669 | *(Ustrrchr(line,'\n')) = '\0'; | |
670 | }; | |
8e669ac1 | 671 | |
8523533c TK |
672 | /* check maximum base 64 line length */ |
673 | if (Ustrlen(line) > MIME_SANITY_MAX_B64_LINE_LENGTH ) { | |
674 | if (!warned_about_b64_line_length) { | |
675 | mime_trigger_error(MIME_ERRORLEVEL_B64_LINE_LENGTH); | |
676 | warned_about_b64_line_length = 1; | |
677 | }; | |
678 | }; | |
679 | ||
680 | p = line; | |
681 | offset = 0; | |
682 | while (*(p+offset) != '\0') { | |
683 | /* hit illegal char ? */ | |
684 | if (b64[*(p+offset)] == 128) { | |
685 | if (!warned_about_b64_illegal_char) { | |
686 | mime_trigger_error(MIME_ERRORLEVEL_B64_ILLEGAL_CHAR); | |
687 | warned_about_b64_illegal_char = 1; | |
688 | }; | |
689 | offset++; | |
690 | } | |
691 | else { | |
692 | *p = b64[*(p+offset)]; | |
693 | p++; | |
694 | }; | |
695 | }; | |
696 | *p = 255; | |
8e669ac1 | 697 | |
8523533c TK |
698 | /* check that resulting line length is a multiple of 4 */ |
699 | if ( ( (p - &line[0]) % 4 ) != 0) { | |
700 | if (!warned_about_b64_line_sanity) { | |
701 | mime_trigger_error(MIME_ERRORLEVEL_B64_MISALIGNED); | |
702 | warned_about_b64_line_sanity = 1; | |
703 | }; | |
704 | }; | |
8e669ac1 | 705 | |
8523533c TK |
706 | /* line is translated, start bit shifting */ |
707 | p = line; | |
708 | num_decoded = 0; | |
8e669ac1 | 709 | |
8523533c | 710 | while(*p != 255) { |
8e669ac1 | 711 | |
8523533c TK |
712 | /* byte 0 ---------------------- */ |
713 | if (*(p+1) == 255) { | |
714 | break; | |
715 | } | |
8e669ac1 | 716 | |
8523533c TK |
717 | (*data)[num_decoded] = *p; |
718 | (*data)[num_decoded] <<= 2; | |
8e669ac1 | 719 | |
8523533c TK |
720 | tmp_c = *(p+1); |
721 | tmp_c >>= 4; | |
722 | (*data)[num_decoded] |= tmp_c; | |
8e669ac1 | 723 | |
8523533c TK |
724 | num_decoded++; |
725 | p++; | |
8e669ac1 | 726 | |
8523533c TK |
727 | /* byte 1 ---------------------- */ |
728 | if (*(p+1) == 255) { | |
729 | break; | |
730 | } | |
8e669ac1 | 731 | |
8523533c TK |
732 | (*data)[num_decoded] = *p; |
733 | (*data)[num_decoded] <<= 4; | |
8e669ac1 | 734 | |
8523533c TK |
735 | tmp_c = *(p+1); |
736 | tmp_c >>= 2; | |
737 | (*data)[num_decoded] |= tmp_c; | |
8e669ac1 | 738 | |
8523533c TK |
739 | num_decoded++; |
740 | p++; | |
8e669ac1 | 741 | |
8523533c TK |
742 | /* byte 2 ---------------------- */ |
743 | if (*(p+1) == 255) { | |
744 | break; | |
745 | } | |
8e669ac1 | 746 | |
8523533c TK |
747 | (*data)[num_decoded] = *p; |
748 | (*data)[num_decoded] <<= 6; | |
8e669ac1 | 749 | |
8523533c | 750 | (*data)[num_decoded] |= *(p+1); |
8e669ac1 | 751 | |
8523533c TK |
752 | num_decoded++; |
753 | p+=2; | |
8e669ac1 | 754 | |
8523533c | 755 | }; |
8e669ac1 | 756 | return num_decoded; |
8523533c TK |
757 | /* ---------------------------------------------- */ |
758 | } | |
759 | else if (mime_demux_mode == MIME_DEMUX_MODE_QP) { | |
760 | /* ---------------------------------------------- */ | |
761 | p = line; | |
8e669ac1 | 762 | |
8523533c TK |
763 | while (*p != 0) { |
764 | if (*p == '=') { | |
765 | int decode_qp_result; | |
8e669ac1 | 766 | |
8523533c | 767 | p = mime_decode_qp(p,&decode_qp_result); |
8e669ac1 | 768 | |
8523533c TK |
769 | if (decode_qp_result == -2) { |
770 | /* Error from decoder. p is unchanged. */ | |
771 | if (!warned_about_qp_line_sanity) { | |
772 | mime_trigger_error(MIME_ERRORLEVEL_QP_ILLEGAL_CHAR); | |
773 | warned_about_qp_line_sanity = 1; | |
774 | }; | |
775 | (*data)[num_decoded] = '='; | |
776 | num_decoded++; | |
777 | p++; | |
778 | } | |
779 | else if (decode_qp_result == -1) { | |
8e669ac1 | 780 | /* End of the line with soft line break. |
8523533c TK |
781 | Bail out. */ |
782 | goto QP_RETURN; | |
783 | } | |
784 | else if (decode_qp_result >= 0) { | |
785 | (*data)[num_decoded] = decode_qp_result; | |
786 | num_decoded++; | |
787 | }; | |
788 | } | |
789 | else { | |
790 | (*data)[num_decoded] = *p; | |
791 | num_decoded++; | |
792 | p++; | |
793 | }; | |
794 | }; | |
795 | QP_RETURN: | |
796 | return num_decoded; | |
797 | /* ---------------------------------------------- */ | |
798 | }; | |
8e669ac1 | 799 | |
8523533c TK |
800 | return 0; |
801 | } | |
802 | ||
803 | ||
804 | ||
805 | /************************************************* | |
806 | * Log demime errors and set mime error level * | |
807 | *************************************************/ | |
808 | ||
809 | /* This sets the global demime_reason expansion | |
810 | variable and the demime_errorlevel gauge. */ | |
811 | ||
812 | void mime_trigger_error(int level, uschar *format, ...) { | |
813 | char *f; | |
814 | va_list ap; | |
815 | ||
816 | if( (f = malloc(16384+23)) != NULL ) { | |
817 | /* first log the incident */ | |
818 | sprintf(f,"demime acl condition: "); | |
819 | f+=22; | |
820 | va_start(ap, format); | |
b07e6aa3 | 821 | (void)string_vformat(US f, 16383,(char *)format, ap); |
8523533c TK |
822 | va_end(ap); |
823 | f-=22; | |
e0df1c83 | 824 | log_write(0, LOG_MAIN, "%s", f); |
8523533c TK |
825 | /* then copy to demime_reason_buffer if new |
826 | level is greater than old level */ | |
827 | if (level > demime_errorlevel) { | |
828 | demime_errorlevel = level; | |
829 | Ustrcpy(demime_reason_buffer, US f); | |
830 | demime_reason = demime_reason_buffer; | |
831 | }; | |
832 | free(f); | |
833 | }; | |
834 | } | |
835 | ||
836 | /************************************************* | |
837 | * Demultiplex MIME stream. * | |
838 | *************************************************/ | |
839 | ||
840 | /* We can handle BASE64, QUOTED-PRINTABLE, and UUENCODE. | |
841 | UUENCODE does not need to have a proper | |
842 | transfer-encoding header, we detect it with "begin" | |
843 | ||
844 | This function will report human parsable errors in | |
845 | *info. | |
846 | ||
847 | returns DEFER -> soft error (see *info) | |
848 | OK -> EOF hit, all ok | |
849 | */ | |
850 | ||
851 | int mime_demux(FILE *f, uschar *info) { | |
852 | int mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS; | |
853 | int uu_mode = MIME_UU_MODE_OFF; | |
854 | FILE *mime_dump_file = NULL; | |
855 | FILE *uu_dump_file = NULL; | |
856 | uschar *line; | |
857 | int mime_read_line_status = MIME_READ_LINE_OK; | |
858 | long line_len; | |
859 | struct boundary *boundaries = NULL; | |
860 | struct mime_part mime_part_p; | |
861 | int has_tnef = 0; | |
862 | int has_rfc822 = 0; | |
8e669ac1 | 863 | |
8523533c TK |
864 | /* allocate room for our linebuffer */ |
865 | line = (uschar *)malloc(MIME_SANITY_MAX_LINE_LENGTH); | |
866 | if (line == NULL) { | |
b07e6aa3 | 867 | (void)string_format(info, 1024,"unable to allocate %u bytes",MIME_SANITY_MAX_LINE_LENGTH); |
8523533c TK |
868 | return DEFER; |
869 | }; | |
8e669ac1 | 870 | |
8523533c TK |
871 | /* clear MIME header structure */ |
872 | memset(&mime_part_p,0,sizeof(mime_part)); | |
8e669ac1 | 873 | |
8523533c TK |
874 | /* ----------------------- start demux loop --------------------- */ |
875 | while (mime_read_line_status == MIME_READ_LINE_OK) { | |
8e669ac1 | 876 | |
8523533c TK |
877 | /* read a line of input. Depending on the mode we are in, |
878 | the returned format will differ. */ | |
879 | mime_read_line_status = mime_read_line(f,mime_demux_mode,line,&line_len); | |
8e669ac1 | 880 | |
8523533c TK |
881 | if (mime_read_line_status == MIME_READ_LINE_OVERFLOW) { |
882 | mime_trigger_error(MIME_ERRORLEVEL_LONG_LINE); | |
883 | /* despite the error, continue .. */ | |
884 | mime_read_line_status = MIME_READ_LINE_OK; | |
885 | continue; | |
886 | } | |
887 | else if (mime_read_line_status == MIME_READ_LINE_EOF) { | |
888 | break; | |
889 | }; | |
890 | ||
891 | if (mime_demux_mode == MIME_DEMUX_MODE_MIME_HEADERS) { | |
892 | /* -------------- header mode --------------------- */ | |
8e669ac1 | 893 | |
8523533c TK |
894 | /* Check for an empty line, which is the end of the headers. |
895 | In HEADER mode, the line is returned "cooked", with the | |
896 | final '\n' replaced by a ';' */ | |
897 | if (line_len == 1) { | |
898 | int tmp; | |
8e669ac1 | 899 | |
8523533c TK |
900 | /* We have reached the end of the headers. Start decoding |
901 | with the collected settings. */ | |
902 | if (mime_part_p.seen_content_transfer_encoding > 1) { | |
903 | mime_demux_mode = mime_part_p.seen_content_transfer_encoding; | |
904 | } | |
905 | else { | |
906 | /* default to plain mode if no specific encoding type found */ | |
907 | mime_demux_mode = MIME_DEMUX_MODE_PLAIN; | |
908 | }; | |
8e669ac1 | 909 | |
8523533c TK |
910 | /* open new dump file */ |
911 | tmp = mime_get_dump_file(mime_part_p.extension, &mime_dump_file, info); | |
912 | if (tmp < 0) { | |
913 | return DEFER; | |
914 | }; | |
915 | ||
916 | /* clear out mime_part */ | |
917 | memset(&mime_part_p,0,sizeof(mime_part)); | |
918 | } | |
919 | else { | |
920 | /* Another header to check for file extensions, | |
921 | encoding type and boundaries */ | |
922 | if (strncmpic(US"content-type:",line,Ustrlen("content-type:")) == 0) { | |
923 | /* ---------------------------- Content-Type header ------------------------------- */ | |
924 | uschar *value = line; | |
8e669ac1 | 925 | |
8523533c TK |
926 | /* check for message/partial MIME type and reject it */ |
927 | if (mime_header_find(line,US"message/partial",NULL) > 0) | |
928 | mime_trigger_error(MIME_ERRORLEVEL_MESSAGE_PARTIAL); | |
929 | ||
930 | /* check for TNEF content type, remember to unpack TNEF later. */ | |
931 | if (mime_header_find(line,US"application/ms-tnef",NULL) > 0) | |
932 | has_tnef = 1; | |
8e669ac1 | 933 | |
8523533c TK |
934 | /* check for message/rfcxxx attachments */ |
935 | if (mime_header_find(line,US"message/rfc822",NULL) > 0) | |
936 | has_rfc822 = 1; | |
8e669ac1 | 937 | |
8523533c TK |
938 | /* find the file extension, but do not fill it in |
939 | it is already set, since content-disposition has | |
940 | precedence. */ | |
941 | if (mime_part_p.extension == NULL) { | |
942 | if (mime_header_find(line,US"name",&value) == 2) { | |
943 | if (Ustrlen(value) > MIME_SANITY_MAX_FILENAME) | |
944 | mime_trigger_error(MIME_ERRORLEVEL_FILENAME_LENGTH); | |
8e669ac1 | 945 | mime_part_p.extension = value; |
8523533c TK |
946 | mime_part_p.extension = Ustrrchr(value,'.'); |
947 | if (mime_part_p.extension == NULL) { | |
948 | /* file without extension, setting | |
949 | NULL will use the default extension later */ | |
950 | mime_part_p.extension = NULL; | |
951 | } | |
952 | else { | |
953 | struct file_extension *this_extension = | |
954 | (struct file_extension *)malloc(sizeof(file_extension)); | |
8e669ac1 PH |
955 | |
956 | this_extension->file_extension_string = | |
8523533c TK |
957 | (uschar *)malloc(Ustrlen(mime_part_p.extension)+1); |
958 | Ustrcpy(this_extension->file_extension_string, | |
959 | mime_part_p.extension+1); | |
960 | this_extension->next = file_extensions; | |
961 | file_extensions = this_extension; | |
962 | }; | |
963 | }; | |
964 | }; | |
8e669ac1 | 965 | |
8523533c TK |
966 | /* find a boundary and add it to the list, if present */ |
967 | value = line; | |
968 | if (mime_header_find(line,US"boundary",&value) == 2) { | |
969 | struct boundary *thisboundary; | |
970 | ||
971 | if (Ustrlen(value) > MIME_SANITY_MAX_BOUNDARY_LENGTH) { | |
8e669ac1 | 972 | mime_trigger_error(MIME_ERRORLEVEL_BOUNDARY_LENGTH); |
8523533c TK |
973 | } |
974 | else { | |
975 | thisboundary = (struct boundary*)malloc(sizeof(boundary)); | |
976 | thisboundary->next = boundaries; | |
977 | thisboundary->boundary_string = value; | |
978 | boundaries = thisboundary; | |
979 | }; | |
980 | }; | |
8e669ac1 | 981 | |
8523533c TK |
982 | if (mime_part_p.seen_content_type == 0) { |
983 | mime_part_p.seen_content_type = 1; | |
984 | } | |
985 | else { | |
986 | mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS); | |
987 | }; | |
988 | /* ---------------------------------------------------------------------------- */ | |
989 | } | |
990 | else if (strncmpic(US"content-transfer-encoding:",line,Ustrlen("content-transfer-encoding:")) == 0) { | |
991 | /* ---------------------------- Content-Transfer-Encoding header -------------- */ | |
8e669ac1 | 992 | |
8523533c TK |
993 | if (mime_part_p.seen_content_transfer_encoding == 0) { |
994 | if (mime_header_find(line,US"base64",NULL) > 0) { | |
995 | mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_BASE64; | |
996 | } | |
997 | else if (mime_header_find(line,US"quoted-printable",NULL) > 0) { | |
998 | mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_QP; | |
999 | } | |
1000 | else { | |
1001 | mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_PLAIN; | |
1002 | }; | |
1003 | } | |
1004 | else { | |
1005 | mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS); | |
1006 | }; | |
1007 | /* ---------------------------------------------------------------------------- */ | |
1008 | } | |
1009 | else if (strncmpic(US"content-disposition:",line,Ustrlen("content-disposition:")) == 0) { | |
1010 | /* ---------------------------- Content-Disposition header -------------------- */ | |
1011 | uschar *value = line; | |
8e669ac1 | 1012 | |
8523533c TK |
1013 | if (mime_part_p.seen_content_disposition == 0) { |
1014 | mime_part_p.seen_content_disposition = 1; | |
8e669ac1 | 1015 | |
8523533c TK |
1016 | if (mime_header_find(line,US"filename",&value) == 2) { |
1017 | if (Ustrlen(value) > MIME_SANITY_MAX_FILENAME) | |
1018 | mime_trigger_error(MIME_ERRORLEVEL_FILENAME_LENGTH); | |
1019 | mime_part_p.extension = value; | |
1020 | mime_part_p.extension = Ustrrchr(value,'.'); | |
1021 | if (mime_part_p.extension == NULL) { | |
1022 | /* file without extension, setting | |
1023 | NULL will use the default extension later */ | |
1024 | mime_part_p.extension = NULL; | |
1025 | } | |
1026 | else { | |
1027 | struct file_extension *this_extension = | |
1028 | (struct file_extension *)malloc(sizeof(file_extension)); | |
8e669ac1 PH |
1029 | |
1030 | this_extension->file_extension_string = | |
8523533c TK |
1031 | (uschar *)malloc(Ustrlen(mime_part_p.extension)+1); |
1032 | Ustrcpy(this_extension->file_extension_string, | |
1033 | mime_part_p.extension+1); | |
1034 | this_extension->next = file_extensions; | |
1035 | file_extensions = this_extension; | |
1036 | }; | |
1037 | }; | |
1038 | } | |
1039 | else { | |
1040 | mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS); | |
1041 | }; | |
1042 | /* ---------------------------------------------------------------------------- */ | |
1043 | }; | |
1044 | }; /* End of header checks */ | |
1045 | /* ------------------------------------------------ */ | |
1046 | } | |
1047 | else { | |
1048 | /* -------------- non-header mode ----------------- */ | |
1049 | int tmp; | |
1050 | ||
1051 | if (uu_mode == MIME_UU_MODE_OFF) { | |
1052 | uschar uu_file_extension[5]; | |
1053 | /* We are not currently decoding UUENCODE | |
1054 | Check for possible UUENCODE start tag. */ | |
1055 | if (mime_check_uu_start(line,uu_file_extension,&has_tnef)) { | |
1056 | /* possible UUENCODING start detected. | |
1057 | Set unconfirmed mode first. */ | |
1058 | uu_mode = MIME_UU_MODE_UNCONFIRMED; | |
1059 | /* open new uu dump file */ | |
1060 | tmp = mime_get_dump_file(uu_file_extension, &uu_dump_file, info); | |
1061 | if (tmp < 0) { | |
1062 | free(line); | |
1063 | return DEFER; | |
1064 | }; | |
1065 | }; | |
1066 | } | |
1067 | else { | |
1068 | uschar *data; | |
1069 | long data_len = 0; | |
1070 | ||
1071 | if (uu_mode == MIME_UU_MODE_UNCONFIRMED) { | |
1072 | /* We are in unconfirmed UUENCODE mode. */ | |
8e669ac1 | 1073 | |
8523533c | 1074 | data_len = uu_decode_line(line,&data,line_len,info); |
8e669ac1 | 1075 | |
8523533c TK |
1076 | if (data_len == -2) { |
1077 | /* temp error, turn off uudecode mode */ | |
1078 | if (uu_dump_file != NULL) { | |
f1e894f3 | 1079 | (void)fclose(uu_dump_file); uu_dump_file = NULL; |
8523533c TK |
1080 | }; |
1081 | uu_mode = MIME_UU_MODE_OFF; | |
1082 | return DEFER; | |
1083 | } | |
1084 | else if (data_len == -1) { | |
1085 | if (uu_dump_file != NULL) { | |
f1e894f3 | 1086 | (void)fclose(uu_dump_file); uu_dump_file = NULL; |
8523533c TK |
1087 | }; |
1088 | uu_mode = MIME_UU_MODE_OFF; | |
1089 | data_len = 0; | |
1090 | } | |
1091 | else if (data_len > 0) { | |
1092 | /* we have at least decoded a valid byte | |
1093 | turn on confirmed mode */ | |
1094 | uu_mode = MIME_UU_MODE_CONFIRMED; | |
1095 | }; | |
1096 | } | |
1097 | else if (uu_mode == MIME_UU_MODE_CONFIRMED) { | |
1098 | /* If we are in confirmed UU mode, | |
1099 | check for single "end" tag on line */ | |
1100 | if ((strncmpic(line,US"end",3) == 0) && (line[3] < 32)) { | |
1101 | if (uu_dump_file != NULL) { | |
f1e894f3 | 1102 | (void)fclose(uu_dump_file); uu_dump_file = NULL; |
8523533c TK |
1103 | }; |
1104 | uu_mode = MIME_UU_MODE_OFF; | |
1105 | } | |
1106 | else { | |
1107 | data_len = uu_decode_line(line,&data,line_len,info); | |
1108 | if (data_len == -2) { | |
1109 | /* temp error, turn off uudecode mode */ | |
1110 | if (uu_dump_file != NULL) { | |
f1e894f3 | 1111 | (void)fclose(uu_dump_file); uu_dump_file = NULL; |
8523533c TK |
1112 | }; |
1113 | uu_mode = MIME_UU_MODE_OFF; | |
1114 | return DEFER; | |
1115 | } | |
1116 | else if (data_len == -1) { | |
1117 | /* skip this line */ | |
1118 | data_len = 0; | |
1119 | }; | |
1120 | }; | |
1121 | }; | |
8e669ac1 | 1122 | |
8523533c TK |
1123 | /* write data to dump file, if available */ |
1124 | if (data_len > 0) { | |
1125 | if (fwrite(data,1,data_len,uu_dump_file) < data_len) { | |
1126 | /* short write */ | |
b07e6aa3 | 1127 | (void)string_format(info, 1024,"short write on uudecode dump file"); |
8523533c | 1128 | free(line); |
8e669ac1 | 1129 | return DEFER; |
8523533c TK |
1130 | }; |
1131 | }; | |
1132 | }; | |
8e669ac1 | 1133 | |
8523533c TK |
1134 | if (mime_demux_mode != MIME_DEMUX_MODE_SCANNING) { |
1135 | /* Non-scanning and Non-header mode. That means | |
1136 | we are currently decoding data to the dump | |
1137 | file. */ | |
1138 | ||
1139 | /* Check for a known boundary. */ | |
1140 | tmp = mime_check_boundary(line,boundaries); | |
1141 | if (tmp == 1) { | |
1142 | /* We have hit a known start boundary. | |
1143 | That will put us back in header mode. */ | |
1144 | mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS; | |
1145 | if (mime_dump_file != NULL) { | |
1146 | /* if the attachment was a RFC822 message, recurse into it */ | |
1147 | if (has_rfc822) { | |
1148 | has_rfc822 = 0; | |
1149 | rewind(mime_dump_file); | |
1150 | mime_demux(mime_dump_file,info); | |
1151 | }; | |
8e669ac1 | 1152 | |
f1e894f3 | 1153 | (void)fclose(mime_dump_file); mime_dump_file = NULL; |
8523533c TK |
1154 | }; |
1155 | } | |
1156 | else if (tmp == 2) { | |
1157 | /* We have hit a known end boundary. | |
1158 | That puts us into scanning mode, which will end when we hit another known start boundary */ | |
1159 | mime_demux_mode = MIME_DEMUX_MODE_SCANNING; | |
1160 | if (mime_dump_file != NULL) { | |
1161 | /* if the attachment was a RFC822 message, recurse into it */ | |
1162 | if (has_rfc822) { | |
1163 | has_rfc822 = 0; | |
1164 | rewind(mime_dump_file); | |
1165 | mime_demux(mime_dump_file,info); | |
1166 | }; | |
8e669ac1 | 1167 | |
f1e894f3 | 1168 | (void)fclose(mime_dump_file); mime_dump_file = NULL; |
8523533c TK |
1169 | }; |
1170 | } | |
1171 | else { | |
1172 | uschar *data; | |
1173 | long data_len = 0; | |
8e669ac1 | 1174 | |
8523533c TK |
1175 | /* decode the line with the appropriate method */ |
1176 | if (mime_demux_mode == MIME_DEMUX_MODE_PLAIN) { | |
1177 | /* in plain mode, just dump the line */ | |
1178 | data = line; | |
1179 | data_len = line_len; | |
1180 | } | |
1181 | else if ( (mime_demux_mode == MIME_DEMUX_MODE_QP) || (mime_demux_mode == MIME_DEMUX_MODE_BASE64) ) { | |
1182 | data_len = mime_decode_line(mime_demux_mode,line,&data,line_len,info); | |
1183 | if (data_len < 0) { | |
1184 | /* Error reported from the line decoder. */ | |
1185 | data_len = 0; | |
1186 | }; | |
1187 | }; | |
8e669ac1 | 1188 | |
8523533c TK |
1189 | /* write data to dump file */ |
1190 | if (data_len > 0) { | |
1191 | if (fwrite(data,1,data_len,mime_dump_file) < data_len) { | |
1192 | /* short write */ | |
b07e6aa3 | 1193 | (void)string_format(info, 1024,"short write on dump file"); |
8523533c | 1194 | free(line); |
8e669ac1 | 1195 | return DEFER; |
8523533c TK |
1196 | }; |
1197 | }; | |
8e669ac1 | 1198 | |
8523533c TK |
1199 | }; |
1200 | } | |
1201 | else { | |
1202 | /* Scanning mode. We end up here after a end boundary. | |
1203 | This will usually be at the end of a message or at | |
1204 | the end of a MIME container. | |
1205 | We need to look for another start boundary to get | |
1206 | back into header mode. */ | |
1207 | if (mime_check_boundary(line,boundaries) == 1) { | |
1208 | mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS; | |
1209 | }; | |
8e669ac1 | 1210 | |
8523533c TK |
1211 | }; |
1212 | /* ------------------------------------------------ */ | |
1213 | }; | |
1214 | }; | |
1215 | /* ----------------------- end demux loop ----------------------- */ | |
8e669ac1 | 1216 | |
8523533c TK |
1217 | /* close files, they could still be open */ |
1218 | if (mime_dump_file != NULL) | |
f1e894f3 | 1219 | (void)fclose(mime_dump_file); |
8523533c | 1220 | if (uu_dump_file != NULL) |
f1e894f3 | 1221 | (void)fclose(uu_dump_file); |
8e669ac1 | 1222 | |
8523533c TK |
1223 | /* release line buffer */ |
1224 | free(line); | |
8e669ac1 | 1225 | |
8523533c TK |
1226 | /* FIXME: release boundary buffers. |
1227 | Not too much of a problem since | |
1228 | this instance of exim is not resident. */ | |
8e669ac1 | 1229 | |
8523533c TK |
1230 | if (has_tnef) { |
1231 | uschar file_name[1024]; | |
1232 | /* at least one file could be TNEF encoded. | |
1233 | attempt to send all decoded files thru the TNEF decoder */ | |
8e669ac1 | 1234 | |
b07e6aa3 | 1235 | (void)string_format(file_name,1024,"%s/scan/%s",spool_directory,message_id); |
8523533c TK |
1236 | /* Removed FTTB. We need to decide on TNEF inclusion */ |
1237 | /* mime_unpack_tnef(file_name); */ | |
1238 | }; | |
1239 | ||
1240 | return 0; | |
1241 | } | |
1242 | ||
1243 | #endif |