Testsuite: fix output for 615, missing spool dir
[exim.git] / src / src / mime.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004, 2015 */
6 /* License: GPL */
7
8 #include "exim.h"
9 #ifdef WITH_CONTENT_SCAN        /* entire file */
10 #include "mime.h"
11 #include <sys/stat.h>
12
13 FILE *mime_stream = NULL;
14 uschar *mime_current_boundary = NULL;
15
16 static mime_header mime_header_list[] = {
17   { US"content-type:",              13, &mime_content_type },
18   { US"content-disposition:",       20, &mime_content_disposition },
19   { US"content-transfer-encoding:", 26, &mime_content_transfer_encoding },
20   { US"content-id:",                11, &mime_content_id },
21   { US"content-description:",       20, &mime_content_description }
22 };
23
24 static int mime_header_list_size = nelem(mime_header_list);
25
26 static mime_parameter mime_parameter_list[] = {
27   { US"name=",     5, &mime_filename },
28   { US"filename=", 9, &mime_filename },
29   { US"charset=",  8, &mime_charset  },
30   { US"boundary=", 9, &mime_boundary }
31 };
32
33
34 /*************************************************
35 * set MIME anomaly level + text                  *
36 *************************************************/
37
38 /* Small wrapper to set the two expandables which
39    give info on detected "problems" in MIME
40    encodings. Indexes are defined in mime.h. */
41
42 void
43 mime_set_anomaly(int idx)
44 {
45 struct anom {
46   int level;
47   const uschar * text;
48 } anom[] = { {1, CUS"Broken Quoted-Printable encoding detected"},
49              {2, CUS"Broken BASE64 encoding detected"} };
50
51 mime_anomaly_level = anom[idx].level;
52 mime_anomaly_text =  anom[idx].text;
53 }
54
55
56 /*************************************************
57 * decode quoted-printable chars                  *
58 *************************************************/
59
60 /* gets called when we hit a =
61    returns: new pointer position
62    result code in c:
63           -2 - decode error
64           -1 - soft line break, no char
65            0-255 - char to write
66 */
67
68 static uschar *
69 mime_decode_qp_char(uschar *qp_p, int *c)
70 {
71 uschar *initial_pos = qp_p;
72
73 /* advance one char */
74 qp_p++;
75
76 /* Check for two hex digits and decode them */
77 if (isxdigit(*qp_p) && isxdigit(qp_p[1]))
78   {
79   /* Do hex conversion */
80   *c = (isdigit(*qp_p) ? *qp_p - '0' : toupper(*qp_p) - 'A' + 10) <<4;
81   qp_p++;
82   *c |= isdigit(*qp_p) ? *qp_p - '0' : toupper(*qp_p) - 'A' + 10;
83   return qp_p + 1;
84   }
85
86 /* tab or whitespace may follow just ignore it if it precedes \n */
87 while (*qp_p == '\t' || *qp_p == ' ' || *qp_p == '\r')
88   qp_p++;
89
90 if (*qp_p == '\n')      /* hit soft line break */
91   {
92   *c = -1;
93   return qp_p;
94   }
95
96 /* illegal char here */
97 *c = -2;
98 return initial_pos;
99 }
100
101
102 /* just dump MIME part without any decoding */
103 static ssize_t
104 mime_decode_asis(FILE* in, FILE* out, uschar* boundary)
105 {
106   ssize_t len, size = 0;
107   uschar buffer[MIME_MAX_LINE_LENGTH];
108
109   while(fgets(CS buffer, MIME_MAX_LINE_LENGTH, mime_stream) != NULL)
110     {
111     if (boundary != NULL
112        && Ustrncmp(buffer, "--", 2) == 0
113        && Ustrncmp((buffer+2), boundary, Ustrlen(boundary)) == 0
114        )
115       break;
116
117     len = Ustrlen(buffer);
118     if (fwrite(buffer, 1, (size_t)len, out) < len)
119       return -1;
120     size += len;
121     } /* while */
122   return size;
123 }
124
125
126
127 /* decode quoted-printable MIME part */
128 static ssize_t
129 mime_decode_qp(FILE* in, FILE* out, uschar* boundary)
130 {
131 uschar ibuf[MIME_MAX_LINE_LENGTH], obuf[MIME_MAX_LINE_LENGTH];
132 uschar *ipos, *opos;
133 ssize_t len, size = 0;
134
135 while (fgets(CS ibuf, MIME_MAX_LINE_LENGTH, in) != NULL)
136   {
137   if (boundary != NULL
138      && Ustrncmp(ibuf, "--", 2) == 0
139      && Ustrncmp((ibuf+2), boundary, Ustrlen(boundary)) == 0
140      )
141     break; /* todo: check for missing boundary */
142
143   ipos = ibuf;
144   opos = obuf;
145
146   while (*ipos != 0)
147     {
148     if (*ipos == '=')
149       {
150       int decode_qp_result;
151
152       ipos = mime_decode_qp_char(ipos, &decode_qp_result);
153
154       if (decode_qp_result == -2)
155         {
156         /* Error from decoder. ipos is unchanged. */
157         mime_set_anomaly(MIME_ANOMALY_BROKEN_QP);
158         *opos = '=';
159         ++opos;
160         ++ipos;
161         }
162       else if (decode_qp_result == -1)
163         break;
164       else if (decode_qp_result >= 0)
165         {
166         *opos = decode_qp_result;
167         ++opos;
168         }
169       }
170     else
171       {
172       *opos = *ipos;
173       ++opos;
174       ++ipos;
175       }
176     }
177   /* something to write? */
178   len = opos - obuf;
179   if (len > 0)
180     {
181     if (fwrite(obuf, 1, len, out) != len) return -1; /* error */
182     size += len;
183     }
184   }
185 return size;
186 }
187
188
189 static FILE *
190 mime_get_decode_file(uschar *pname, uschar *fname)
191 {
192 FILE *f = NULL;
193 uschar *filename;
194
195 filename = (uschar *)malloc(2048);
196
197 if (pname && fname)
198   {
199   (void)string_format(filename, 2048, "%s/%s", pname, fname);
200   f = modefopen(filename,"wb+",SPOOL_MODE);
201   }
202 else if (!pname)
203   f = modefopen(fname,"wb+",SPOOL_MODE);
204 else if (!fname)
205   {
206   int file_nr = 0;
207   int result = 0;
208
209   /* must find first free sequential filename */
210   do
211     {
212     struct stat mystat;
213     (void)string_format(filename, 2048,
214       "%s/%s-%05u", pname, message_id, file_nr++);
215     /* security break */
216     if (file_nr >= 1024)
217       break;
218     result = stat(CS filename, &mystat);
219     } while(result != -1);
220
221   f = modefopen(filename, "wb+", SPOOL_MODE);
222   }
223
224 /* set expansion variable */
225 mime_decoded_filename = filename;
226
227 return f;
228 }
229
230
231 int
232 mime_decode(const uschar **listptr)
233 {
234 int sep = 0;
235 const uschar *list = *listptr;
236 uschar *option;
237 uschar option_buffer[1024];
238 uschar decode_path[1024];
239 FILE *decode_file = NULL;
240 long f_pos = 0;
241 ssize_t size_counter = 0;
242 ssize_t (*decode_function)(FILE*, FILE*, uschar*);
243
244 if (mime_stream == NULL)
245   return FAIL;
246
247 f_pos = ftell(mime_stream);
248
249 /* build default decode path (will exist since MBOX must be spooled up) */
250 (void)string_format(decode_path,1024,"%s/scan/%s",spool_directory,message_id);
251
252 /* try to find 1st option */
253 if ((option = string_nextinlist(&list, &sep,
254                                 option_buffer,
255                                 sizeof(option_buffer))) != NULL)
256   {
257   /* parse 1st option */
258   if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) )
259     /* explicitly no decoding */
260     return FAIL;
261
262   if (Ustrcmp(option,"default") == 0)
263     /* explicit default path + file names */
264     goto DEFAULT_PATH;
265
266   if (option[0] == '/')
267     {
268     struct stat statbuf;
269
270     memset(&statbuf,0,sizeof(statbuf));
271
272     /* assume either path or path+file name */
273     if ( (stat(CS option, &statbuf) == 0) && S_ISDIR(statbuf.st_mode) )
274       /* is directory, use it as decode_path */
275       decode_file = mime_get_decode_file(option, NULL);
276     else
277       /* does not exist or is a file, use as full file name */
278       decode_file = mime_get_decode_file(NULL, option);
279     }
280   else
281     /* assume file name only, use default path */
282     decode_file = mime_get_decode_file(decode_path, option);
283   }
284 else
285   {
286   /* no option? patch default path */
287 DEFAULT_PATH:
288   decode_file = mime_get_decode_file(decode_path, NULL);
289   }
290
291 if (!decode_file)
292   return DEFER;
293
294 /* decode according to mime type */
295 decode_function =
296   !mime_content_transfer_encoding
297   ? mime_decode_asis    /* no encoding, dump as-is */
298   : Ustrcmp(mime_content_transfer_encoding, "base64") == 0
299   ? mime_decode_base64
300   : Ustrcmp(mime_content_transfer_encoding, "quoted-printable") == 0
301   ? mime_decode_qp
302   : mime_decode_asis;   /* unknown encoding type, just dump as-is */
303
304 size_counter = decode_function(mime_stream, decode_file, mime_current_boundary);
305
306 clearerr(mime_stream);
307 if (fseek(mime_stream, f_pos, SEEK_SET))
308   return DEFER;
309
310 if (fclose(decode_file) != 0 || size_counter < 0)
311   return DEFER;
312
313 /* round up to the next KiB */
314 mime_content_size = (size_counter + 1023) / 1024;
315
316 return OK;
317 }
318
319
320 static int
321 mime_get_header(FILE *f, uschar *header)
322 {
323 int c = EOF;
324 int done = 0;
325 int header_value_mode = 0;
326 int header_open_brackets = 0;
327 int num_copied = 0;
328
329 while(!done)
330   {
331   if ((c = fgetc(f)) == EOF) break;
332
333   /* always skip CRs */
334   if (c == '\r') continue;
335
336   if (c == '\n')
337     {
338     if (num_copied > 0)
339       {
340       /* look if next char is '\t' or ' ' */
341       if ((c = fgetc(f)) == EOF) break;
342       if ( (c == '\t') || (c == ' ') ) continue;
343       (void)ungetc(c,f);
344       }
345     /* end of the header, terminate with ';' */
346     c = ';';
347     done = 1;
348     }
349
350   /* skip control characters */
351   if (c < 32) continue;
352
353   if (header_value_mode)
354     {
355     /* --------- value mode ----------- */
356     /* skip leading whitespace */
357     if ( ((c == '\t') || (c == ' ')) && (header_value_mode == 1) )
358       continue;
359
360       /* we have hit a non-whitespace char, start copying value data */
361       header_value_mode = 2;
362
363       if (c == '"')       /* flip "quoted" mode */
364         header_value_mode = header_value_mode==2 ? 3 : 2;
365
366       /* leave value mode on unquoted ';' */
367       if (header_value_mode == 2 && c == ';') {
368         header_value_mode = 0;
369       };
370       /* -------------------------------- */
371     }
372   else
373     {
374     /* -------- non-value mode -------- */
375     /* skip whitespace + tabs */
376     if ( (c == ' ') || (c == '\t') )
377       continue;
378     if (c == '\\')
379       {
380       /* quote next char. can be used
381       to escape brackets. */
382       if ((c = fgetc(f)) == EOF) break;
383       }
384     else if (c == '(')
385       {
386       header_open_brackets++;
387       continue;
388       }
389     else if ((c == ')') && header_open_brackets)
390       {
391       header_open_brackets--;
392       continue;
393       }
394     else if ( (c == '=') && !header_open_brackets ) /* enter value mode */
395       header_value_mode = 1;
396
397     /* skip chars while we are in a comment */
398     if (header_open_brackets > 0)
399       continue;
400     /* -------------------------------- */
401     }
402
403   /* copy the char to the buffer */
404   header[num_copied++] = (uschar)c;
405
406   /* break if header buffer is full */
407   if (num_copied > MIME_MAX_HEADER_SIZE-1)
408     done = 1;
409   }
410
411 if ((num_copied > 0) && (header[num_copied-1] != ';'))
412   header[num_copied-1] = ';';
413
414 /* 0-terminate */
415 header[num_copied] = '\0';
416
417 /* return 0 for EOF or empty line */
418 if ((c == EOF) || (num_copied == 1))
419   return 0;
420 else
421   return 1;
422 }
423
424
425 static void
426 mime_vars_reset(void)
427 {
428 mime_anomaly_level     = 0;
429 mime_anomaly_text      = NULL;
430 mime_boundary          = NULL;
431 mime_charset           = NULL;
432 mime_decoded_filename  = NULL;
433 mime_filename          = NULL;
434 mime_content_description = NULL;
435 mime_content_disposition = NULL;
436 mime_content_id        = NULL;
437 mime_content_transfer_encoding = NULL;
438 mime_content_type      = NULL;
439 mime_is_multipart      = 0;
440 mime_content_size      = 0;
441 }
442
443
444 /* Grab a parameter value, dealing with quoting.
445
446 Arguments:
447  str    Input string.  Updated on return to point to terminating ; or NUL
448
449 Return:
450  Allocated string with parameter value
451 */
452 static uschar *
453 mime_param_val(uschar ** sp)
454 {
455 uschar * s = *sp;
456 uschar * val = NULL;
457 int size = 0, ptr = 0;
458
459 /* debug_printf("   considering paramval '%s'\n", s); */
460
461 while (*s && *s != ';')         /* ; terminates */
462   if (*s == '"')
463     {
464     s++;                        /* skip opening " */
465     while (*s && *s != '"')     /* " protects ; */
466       val = string_cat(val, &size, &ptr, s++, 1);
467     if (*s) s++;                /* skip closing " */
468     }
469   else
470     val = string_cat(val, &size, &ptr, s++, 1);
471 if (val) val[ptr] = '\0';
472 *sp = s;
473 return val;
474 }
475
476 static uschar *
477 mime_next_semicolon(uschar * s)
478 {
479 while (*s && *s != ';')         /* ; terminates */
480   if (*s == '"')
481     {
482     s++;                        /* skip opening " */
483     while (*s && *s != '"')     /* " protects ; */
484       s++;
485     if (*s) s++;                /* skip closing " */
486     }
487   else
488     s++;
489 return s;
490 }
491
492
493 static uschar *
494 rfc2231_to_2047(const uschar * fname, const uschar * charset, int * len)
495 {
496 int size = 0, ptr = 0;
497 uschar * val = string_cat(NULL, &size, &ptr, US"=?", 2);
498 uschar c;
499
500 if (charset)
501   val = string_cat(val, &size, &ptr, charset, Ustrlen(charset));
502 val = string_cat(val, &size, &ptr, US"?Q?", 3);
503
504 while ((c = *fname))
505   if (c == '%' && isxdigit(fname[1]) && isxdigit(fname[2]))
506     {
507     val = string_cat(val, &size, &ptr, US"=", 1);
508     val = string_cat(val, &size, &ptr, ++fname, 2);
509     fname += 2;
510     }
511   else
512     val = string_cat(val, &size, &ptr, fname++, 1);
513
514 val = string_cat(val, &size, &ptr, US"?=", 2);
515 val[*len = ptr] = '\0';
516 return val;
517 }
518
519
520 int
521 mime_acl_check(uschar *acl, FILE *f, struct mime_boundary_context *context,
522     uschar **user_msgptr, uschar **log_msgptr)
523 {
524 int rc = OK;
525 uschar * header = NULL;
526 struct mime_boundary_context nested_context;
527
528 /* reserve a line buffer to work in */
529 header = store_get(MIME_MAX_HEADER_SIZE+1);
530
531 /* Not actually used at the moment, but will be vital to fixing
532  * some RFC 2046 nonconformance later... */
533 nested_context.parent = context;
534
535 /* loop through parts */
536 while(1)
537   {
538   /* reset all per-part mime variables */
539   mime_vars_reset();
540
541   /* If boundary is null, we assume that *f is positioned on the start of
542   headers (for example, at the very beginning of a message.  If a boundary is
543   given, we must first advance to it to reach the start of the next header
544   block.  */
545
546   /* NOTE -- there's an error here -- RFC2046 specifically says to
547    * check for outer boundaries.  This code doesn't do that, and
548    * I haven't fixed this.
549    *
550    * (I have moved partway towards adding support, however, by adding
551    * a "parent" field to my new boundary-context structure.)
552    */
553   if (context) for (;;)
554     {
555     if (!fgets(CS header, MIME_MAX_HEADER_SIZE, f))
556       {
557       /* Hit EOF or read error. Ugh. */
558       DEBUG(D_acl) debug_printf("MIME: Hit EOF ...\n");
559       return rc;
560       }
561
562     /* boundary line must start with 2 dashes */
563     if (  Ustrncmp(header, "--", 2) == 0
564        && Ustrncmp(header+2, context->boundary, Ustrlen(context->boundary)) == 0
565        )
566       {                 /* found boundary */
567       if (Ustrncmp((header+2+Ustrlen(context->boundary)), "--", 2) == 0)
568         {
569         /* END boundary found */
570         DEBUG(D_acl) debug_printf("MIME: End boundary found %s\n",
571           context->boundary);
572         return rc;
573         }
574
575       DEBUG(D_acl) debug_printf("MIME: Next part with boundary %s\n",
576         context->boundary);
577       break;
578       }
579     }
580
581   /* parse headers, set up expansion variables */
582   while (mime_get_header(f, header))
583     {
584     struct mime_header * mh;
585
586     /* look for interesting headers */
587     for (mh = mime_header_list;
588          mh < mime_header_list + mime_header_list_size;
589          mh++) if (strncmpic(mh->name, header, mh->namelen) == 0)
590       {
591       uschar * p = header + mh->namelen;
592       uschar * q;
593
594       /* grab the value (normalize to lower case)
595       and copy to its corresponding expansion variable */
596
597       for (q = p; *q != ';' && *q; q++) ;
598       *mh->value = string_copynlc(p, q-p);
599       DEBUG(D_acl) debug_printf("MIME: found %s header, value is '%s'\n",
600         mh->name, *mh->value);
601
602       if (*(p = q)) p++;                        /* jump past the ; */
603
604         {
605         uschar * mime_fname = NULL;
606         uschar * mime_fname_rfc2231 = NULL;
607         uschar * mime_filename_charset = NULL;
608         BOOL decoding_failed = FALSE;
609
610         /* grab all param=value tags on the remaining line,
611         check if they are interesting */
612
613         while (*p)
614           {
615           mime_parameter * mp;
616
617           DEBUG(D_acl) debug_printf("MIME:   considering paramlist '%s'\n", p);
618
619           if (  !mime_filename
620              && strncmpic(CUS"content-disposition:", header, 20) == 0
621              && strncmpic(CUS"filename*", p, 9) == 0
622              )
623             {                                   /* RFC 2231 filename */
624             uschar * q;
625
626             /* find value of the filename */
627             p += 9;
628             while(*p != '=' && *p) p++;
629             if (*p) p++;                        /* p is filename or NUL */
630             q = mime_param_val(&p);             /* p now trailing ; or NUL */
631
632             if (q && *q)
633               {
634               uschar * temp_string, * err_msg;
635               int slen;
636
637               /* build up an un-decoded filename over successive
638               filename*= parameters (for use when 2047 decode fails) */
639
640               mime_fname_rfc2231 = string_sprintf("%#s%s",
641                 mime_fname_rfc2231, q);
642
643               if (!decoding_failed)
644                 {
645                 int size;
646                 if (!mime_filename_charset)
647                   {
648                   uschar * s = q;
649
650                   /* look for a ' in the "filename" */
651                   while(*s != '\'' && *s) s++;  /* s is 1st ' or NUL */
652
653                   if ((size = s-q) > 0)
654                     mime_filename_charset = string_copyn(q, size);
655
656                   if (*(p = s)) p++;
657                   while(*p == '\'') p++;        /* p is after 2nd ' */
658                   }
659                 else
660                   p = q;
661
662                 DEBUG(D_acl) debug_printf("MIME:    charset %s fname '%s'\n",
663                   mime_filename_charset ? mime_filename_charset : US"<NULL>", p);
664
665                 temp_string = rfc2231_to_2047(p, mime_filename_charset, &slen);
666                 DEBUG(D_acl) debug_printf("MIME:    2047-name %s\n", temp_string);
667
668                 temp_string = rfc2047_decode(temp_string, FALSE, NULL, ' ',
669                   NULL, &err_msg);
670                 DEBUG(D_acl) debug_printf("MIME:    plain-name %s\n", temp_string);
671
672                 size = Ustrlen(temp_string);
673
674                 if (size == slen)
675                   decoding_failed = TRUE;
676                 else
677                   /* build up a decoded filename over successive
678                   filename*= parameters */
679
680                   mime_filename = mime_fname = mime_fname
681                     ? string_sprintf("%s%s", mime_fname, temp_string)
682                     : temp_string;
683                 }
684               }
685             }
686
687           else
688             /* look for interesting parameters */
689             for (mp = mime_parameter_list;
690                  mp < mime_parameter_list + nelem(mime_parameter_list);
691                  mp++
692                 ) if (strncmpic(mp->name, p, mp->namelen) == 0)
693               {
694               uschar * q;
695               uschar * dummy_errstr;
696
697               /* grab the value and copy to its expansion variable */
698               p += mp->namelen;
699               q = mime_param_val(&p);           /* p now trailing ; or NUL */
700
701               *mp->value = q && *q
702                 ? rfc2047_decode(q, check_rfc2047_length, NULL, 32, NULL,
703                     &dummy_errstr)
704                 : NULL;
705               DEBUG(D_acl) debug_printf(
706                 "MIME:  found %s parameter in %s header, value '%s'\n",
707                 mp->name, mh->name, *mp->value);
708
709               break;                    /* done matching param names */
710               }
711
712
713           /* There is something, but not one of our interesting parameters.
714              Advance past the next semicolon */
715           p = mime_next_semicolon(p);
716           if (*p) p++;
717           }                             /* param scan on line */
718
719         if (strncmpic(CUS"content-disposition:", header, 20) == 0)
720           {
721           if (decoding_failed) mime_filename = mime_fname_rfc2231;
722
723           DEBUG(D_acl) debug_printf(
724             "MIME:  found %s parameter in %s header, value is '%s'\n",
725             "filename", mh->name, mime_filename);
726           }
727         }
728       }
729     }
730
731   /* set additional flag variables (easier access) */
732   if (  mime_content_type
733      && Ustrncmp(mime_content_type,"multipart",9) == 0
734      )
735     mime_is_multipart = 1;
736
737   /* Make a copy of the boundary pointer.
738      Required since mime_boundary is global
739      and can be overwritten further down in recursion */
740   nested_context.boundary = mime_boundary;
741
742   /* raise global counter */
743   mime_part_count++;
744
745   /* copy current file handle to global variable */
746   mime_stream = f;
747   mime_current_boundary = context ? context->boundary : 0;
748
749   /* Note the context */
750   mime_is_coverletter = !(context && context->context == MBC_ATTACHMENT);
751
752   /* call ACL handling function */
753   rc = acl_check(ACL_WHERE_MIME, NULL, acl, user_msgptr, log_msgptr);
754
755   mime_stream = NULL;
756   mime_current_boundary = NULL;
757
758   if (rc != OK) break;
759
760   /* If we have a multipart entity and a boundary, go recursive */
761   if ( (mime_content_type != NULL) &&
762        (nested_context.boundary != NULL) &&
763        (Ustrncmp(mime_content_type,"multipart",9) == 0) )
764     {
765     DEBUG(D_acl)
766       debug_printf("MIME: Entering multipart recursion, boundary '%s'\n",
767         nested_context.boundary);
768
769     nested_context.context =
770       context && context->context == MBC_ATTACHMENT
771       ? MBC_ATTACHMENT
772       :    Ustrcmp(mime_content_type,"multipart/alternative") == 0
773         || Ustrcmp(mime_content_type,"multipart/related") == 0
774       ? MBC_COVERLETTER_ALL
775       : MBC_COVERLETTER_ONESHOT;
776
777     rc = mime_acl_check(acl, f, &nested_context, user_msgptr, log_msgptr);
778     if (rc != OK) break;
779     }
780   else if ( (mime_content_type != NULL) &&
781           (Ustrncmp(mime_content_type,"message/rfc822",14) == 0) )
782     {
783     const uschar *rfc822name = NULL;
784     uschar filename[2048];
785     int file_nr = 0;
786     int result = 0;
787
788     /* must find first free sequential filename */
789     do
790       {
791       struct stat mystat;
792       (void)string_format(filename, 2048,
793         "%s/scan/%s/__rfc822_%05u", spool_directory, message_id, file_nr++);
794       /* security break */
795       if (file_nr >= 128)
796         goto NO_RFC822;
797       result = stat(CS filename,&mystat);
798       } while (result != -1);
799
800     rfc822name = filename;
801
802     /* decode RFC822 attachment */
803     mime_decoded_filename = NULL;
804     mime_stream = f;
805     mime_current_boundary = context ? context->boundary : NULL;
806     mime_decode(&rfc822name);
807     mime_stream = NULL;
808     mime_current_boundary = NULL;
809     if (!mime_decoded_filename)         /* decoding failed */
810       {
811       log_write(0, LOG_MAIN,
812            "mime_regex acl condition warning - could not decode RFC822 MIME part to file.");
813       rc = DEFER;
814       goto out;
815       }
816     mime_decoded_filename = NULL;
817     }
818
819 NO_RFC822:
820   /* If the boundary of this instance is NULL, we are finished here */
821   if (!context) break;
822
823   if (context->context == MBC_COVERLETTER_ONESHOT)
824     context->context = MBC_ATTACHMENT;
825   }
826
827 out:
828 mime_vars_reset();
829 return rc;
830 }
831
832 #endif  /*WITH_CONTENT_SCAN*/
833
834 /* vi: sw ai sw=2
835 */