-/* $Cambridge: exim/src/src/demime.c,v 1.3 2004/12/29 16:24:03 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
FILE *mbox_file;
uschar defer_error_buffer[1024];
int demime_rc = 0;
-
+
/* reset found_extension variable */
found_extension = NULL;
-
+
/* try to find 1st option */
if ((option = string_nextinlist(&list, &sep,
option_buffer,
sizeof(option_buffer))) != NULL) {
-
+
/* parse 1st option */
if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) ) {
/* explicitly no demimeing */
/* no options -> no demimeing */
return FAIL;
};
-
+
/* make sure the eml mbox file is spooled up */
- mbox_file = spool_mbox(&mbox_size);
-
+ mbox_file = spool_mbox(&mbox_size, NULL);
+
if (mbox_file == NULL) {
/* error while spooling */
log_write(0, LOG_MAIN|LOG_PANIC,
"demime acl condition: error while creating mbox spool file");
- return DEFER;
+ return DEFER;
};
-
+
/* call demimer if not already done earlier */
if (!demime_ok)
demime_rc = mime_demux(mbox_file, defer_error_buffer);
-
- fclose(mbox_file);
-
+
+ (void)fclose(mbox_file);
+
if (demime_rc == DEFER) {
/* temporary failure (DEFER => DEFER) */
log_write(0, LOG_MAIN,
"demime acl condition: %s", defer_error_buffer);
return DEFER;
};
-
+
/* set demime_ok to avoid unpacking again */
demime_ok = 1;
-
+
/* check for file extensions, if there */
while (option != NULL) {
struct file_extension *this_extension = file_extensions;
-
+
/* Look for the wildcard. If it is found, we always return true.
The user must then use a custom condition to evaluate demime_errorlevel */
if (Ustrcmp(option,"*") == 0) {
};
/* loop thru extension list */
- while (this_extension != NULL) {
+ while (this_extension != NULL) {
if (strcmpic(option, this_extension->file_extension_string) == 0) {
/* found one */
found_extension = this_extension->file_extension_string;
};
this_extension = this_extension->next;
};
-
+
/* grab next extension from option list */
option = string_nextinlist(&list, &sep,
option_buffer,
sizeof(option_buffer));
};
-
+
/* nothing found */
return FAIL;
}
unsigned int mime_hstr_i(uschar *cptr) {
unsigned int i, j = 0;
-
+
while (cptr && *cptr && isxdigit(*cptr)) {
i = *cptr++ - '0';
if (9 < i) i -= 7;
j <<= 4;
j |= (i & 0x0f);
}
-
+
return(j);
}
uschar hex[] = {0,0,0};
int nan = 0;
uschar *initial_pos = qp_p;
-
+
/* advance one char */
qp_p++;
-
+
REPEAT_FIRST:
if ( (*qp_p == '\t') || (*qp_p == ' ') || (*qp_p == '\r') ) {
/* tab or whitespace may follow
qp_p++;
};
}
- else if (*qp_p == '\n') {
+ else if (*qp_p == '\n') {
/* hit soft line break already, continue */
*c = -1;
return qp_p;
*c = -2;
return initial_pos;
};
-
+
if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) {
if (hex[0] > 0) {
hex[1] = *qp_p;
else {
/* huh ? */
*c = -2;
- return initial_pos;
+ return initial_pos;
};
}
else {
/* illegal char */
*c = -2;
- return initial_pos;
+ return initial_pos;
};
-
+
}
unsigned int file_nr;
uschar default_extension[] = ".com";
uschar *p;
-
+
if (extension == NULL)
extension = default_extension;
-
+
/* scan the proposed extension.
if it is longer than 4 chars, or
contains exotic chars, use the default extension */
-
+
/* if (Ustrlen(extension) > 4) {
extension = default_extension;
};
-*/
-
+*/
+
p = extension+1;
-
+
while (*p != 0) {
*p = (uschar)tolower((uschar)*p);
if ( (*p < 97) || (*p > 122) ) {
extension = default_extension;
- break;
+ break;
};
p++;
};
-
+
/* find a new file to write to */
file_nr = 0;
do {
struct stat mystat;
-
- snprintf(CS file_name,1024,"%s/scan/%s/%s-%05u%s",spool_directory,message_id,message_id,file_nr,extension);
+
+ (void)string_format(file_name,1024,"%s/scan/%s/%s-%05u%s",spool_directory,message_id,message_id,file_nr,extension);
file_nr++;
if (file_nr >= MIME_SANITY_MAX_DUMP_FILES) {
/* max parts reached */
result = stat(CS file_name,&mystat);
}
while(result != -1);
-
- *f = fopen(CS file_name,"w+");
+
+ *f = modefopen(file_name,"wb+",SPOOL_MODE);
if (*f == NULL) {
/* cannot open new dump file, disk full ? -> soft error */
- snprintf(CS info, 1024,"unable to open dump file");
+ (void)string_format(info, 1024,"unable to open dump file");
return -2;
};
-
+
return file_nr;
}
/* Find a string in a mime header, and optionally fill in
the value associated with it into *value
-
+
returns: 0 - nothing found
1 - found param
2 - found param + value
int mime_header_find(uschar *header, uschar *param, uschar **value) {
uschar *needle;
-
+
needle = strstric(header,param,FALSE);
if (needle != NULL) {
if (value != NULL) {
if (*needle == '=') {
uschar *value_start;
uschar *value_end;
-
+
value_start = needle + 1;
value_end = strstric(value_start,US";",FALSE);
if (value_end != NULL) {
*value = (uschar *)malloc((value_end - value_start)+1);
if (*value == NULL)
return 0;
-
+
Ustrncpy(*value,value_start,(value_end - value_start));
(*value)[(value_end - value_start)] = '\0';
return 2;
int done = 0;
int header_value_mode = 0;
int header_open_brackets = 0;
-
+
*num_copied = 0;
-
+
while(!done) {
-
+
c = fgetc(f);
if (c == EOF) break;
-
+
/* --------- header mode -------------- */
if (mime_demux_mode == MIME_DEMUX_MODE_MIME_HEADERS) {
-
+
/* always skip CRs */
if (c == '\r') continue;
-
+
if (c == '\n') {
if ((*num_copied) > 0) {
/* look if next char is '\t' or ' ' */
c = fgetc(f);
if (c == EOF) break;
if ( (c == '\t') || (c == ' ') ) continue;
- ungetc(c,f);
+ (void)ungetc(c,f);
};
/* end of the header, terminate with ';' */
c = ';';
done = 1;
};
-
+
/* skip control characters */
if (c < 32) continue;
-
+
/* skip whitespace + tabs */
if ( (c == ' ') || (c == '\t') )
continue;
/* --------- value mode ----------- */
/* skip quotes */
if (c == '"') continue;
-
+
/* leave value mode on ';' */
if (c == ';') {
header_value_mode = 0;
}
else if ( (c == '=') && !header_open_brackets ) {
/* enter value mode */
- header_value_mode = 1;
+ header_value_mode = 1;
};
-
+
/* skip chars while we are in a comment */
if (header_open_brackets > 0)
continue;
done = 1;
/* ------------------------------------ */
};
-
+
/* copy the char to the buffer */
buffer[*num_copied] = (uschar)c;
/* raise counter */
(*num_copied)++;
-
+
/* break if buffer is full */
if (*num_copied > MIME_SANITY_MAX_LINE_LENGTH-1) {
done = 1;
};
}
-
+
/* 0-terminate */
buffer[*num_copied] = '\0';
-
+
if (*num_copied > MIME_SANITY_MAX_LINE_LENGTH-1)
return MIME_READ_LINE_OVERFLOW;
else
struct boundary *thisboundary = boundaries;
uschar workbuf[MIME_SANITY_MAX_LINE_LENGTH+1];
unsigned int i,j=0;
-
+
/* check for '--' first */
if (Ustrncmp(line,"--",2) == 0) {
-
+
/* strip tab and space */
for (i = 2; i < Ustrlen(line); i++) {
if ((line[i] != ' ') && (line[i] != '\t')) {
workbuf[j] = line[i];
- j++;
+ j++;
};
};
workbuf[j+1]='\0';
if (Ustrncmp(&workbuf[Ustrlen(thisboundary->boundary_string)],"--",2) == 0) {
/* final boundary found */
return 2;
- };
+ };
return 1;
};
thisboundary = thisboundary->next;
};
};
-
- return 0;
+
+ return 0;
}
*/
int mime_check_uu_start(uschar *line, uschar *uu_file_extension, int *has_tnef) {
-
+
if ( (strncmpic(line,US"begin ",6) == 0)) {
uschar *uu_filename = &line[6];
-
+
/* skip perms, if present */
Ustrtoul(&line[6],&uu_filename,10);
-
+
/* advance one char */
uu_filename++;
-
+
/* This should be the filename.
Check if winmail.dat is present,
which indicates TNEF. */
if (strncmpic(uu_filename,US"winmail.dat",11) == 0) {
- *has_tnef = 1;
+ *has_tnef = 1;
};
-
+
/* reverse to dot if present,
copy up to 4 chars for the extension */
if (Ustrrchr(uu_filename,'.') != NULL)
uu_filename = Ustrrchr(uu_filename,'.');
-
+
return sscanf(CS uu_filename, "%4[.0-9A-Za-z]",CS uu_file_extension);
}
else {
uschar tmp_c;
uschar *work;
int uu_decoded_line_len, uu_encoded_line_len;
-
+
/* allocate memory for data and work buffer */
*data = (uschar *)malloc(line_len);
if (*data == NULL) {
- snprintf(CS info, 1024,"unable to allocate %lu bytes",line_len);
+ (void)string_format(info, 1024,"unable to allocate %lu bytes",line_len);
return -2;
};
work = (uschar *)malloc(line_len);
if (work == NULL) {
- snprintf(CS info, 1024,"unable to allocate %lu bytes",line_len);
+ (void)string_format(info, 1024,"unable to allocate %lu bytes",line_len);
return -2;
};
-
+
memcpy(work,line,line_len);
-
+
/* First char is line length
This is microsofts way of getting it. Scary. */
if (work[0] < 32) {
else {
uu_decoded_line_len = uudec[work[0]];
};
-
+
p = &work[1];
while (*p > 32) {
uu_encoded_line_len = (p - &work[1]);
p = &work[1];
-
+
/* check that resulting line length is a multiple of 4 */
if ( ( uu_encoded_line_len % 4 ) != 0) {
if (!warned_about_uudec_line_sanity_1) {
};
while ( ((p - &work[1]) < uu_encoded_line_len) && (num_decoded < uu_decoded_line_len)) {
-
+
/* byte 0 ---------------------- */
if ((p - &work[1] + 1) >= uu_encoded_line_len) {
return 0;
}
-
+
(*data)[num_decoded] = *p;
(*data)[num_decoded] <<= 2;
-
+
tmp_c = *(p+1);
tmp_c >>= 4;
(*data)[num_decoded] |= tmp_c;
-
+
num_decoded++;
p++;
-
+
/* byte 1 ---------------------- */
if ((p - &work[1] + 1) >= uu_encoded_line_len) {
return 0;
}
-
+
(*data)[num_decoded] = *p;
(*data)[num_decoded] <<= 4;
-
+
tmp_c = *(p+1);
tmp_c >>= 2;
(*data)[num_decoded] |= tmp_c;
-
+
num_decoded++;
p++;
-
+
/* byte 2 ---------------------- */
if ((p - &work[1] + 1) >= uu_encoded_line_len) {
return 0;
}
-
+
(*data)[num_decoded] = *p;
(*data)[num_decoded] <<= 6;
-
+
(*data)[num_decoded] |= *(p+1);
-
+
num_decoded++;
p+=2;
-
+
};
-
+
return uu_decoded_line_len;
}
long num_decoded = 0;
int offset = 0;
uschar tmp_c;
-
+
/* allocate memory for data */
*data = (uschar *)malloc(max_data_len);
if (*data == NULL) {
- snprintf(CS info, 1024,"unable to allocate %lu bytes",max_data_len);
+ (void)string_format(info, 1024,"unable to allocate %lu bytes",max_data_len);
return -2;
};
-
+
if (mime_demux_mode == MIME_DEMUX_MODE_BASE64) {
/* ---------------------------------------------- */
-
+
/* NULL out trailing '\r' and '\n' chars */
while (Ustrrchr(line,'\r') != NULL) {
*(Ustrrchr(line,'\r')) = '\0';
while (Ustrrchr(line,'\n') != NULL) {
*(Ustrrchr(line,'\n')) = '\0';
};
-
+
/* check maximum base 64 line length */
if (Ustrlen(line) > MIME_SANITY_MAX_B64_LINE_LENGTH ) {
if (!warned_about_b64_line_length) {
};
};
*p = 255;
-
+
/* check that resulting line length is a multiple of 4 */
if ( ( (p - &line[0]) % 4 ) != 0) {
if (!warned_about_b64_line_sanity) {
warned_about_b64_line_sanity = 1;
};
};
-
+
/* line is translated, start bit shifting */
p = line;
num_decoded = 0;
-
+
while(*p != 255) {
-
+
/* byte 0 ---------------------- */
if (*(p+1) == 255) {
break;
}
-
+
(*data)[num_decoded] = *p;
(*data)[num_decoded] <<= 2;
-
+
tmp_c = *(p+1);
tmp_c >>= 4;
(*data)[num_decoded] |= tmp_c;
-
+
num_decoded++;
p++;
-
+
/* byte 1 ---------------------- */
if (*(p+1) == 255) {
break;
}
-
+
(*data)[num_decoded] = *p;
(*data)[num_decoded] <<= 4;
-
+
tmp_c = *(p+1);
tmp_c >>= 2;
(*data)[num_decoded] |= tmp_c;
-
+
num_decoded++;
p++;
-
+
/* byte 2 ---------------------- */
if (*(p+1) == 255) {
break;
}
-
+
(*data)[num_decoded] = *p;
(*data)[num_decoded] <<= 6;
-
+
(*data)[num_decoded] |= *(p+1);
-
+
num_decoded++;
p+=2;
-
+
};
- return num_decoded;
+ return num_decoded;
/* ---------------------------------------------- */
}
else if (mime_demux_mode == MIME_DEMUX_MODE_QP) {
/* ---------------------------------------------- */
p = line;
-
+
while (*p != 0) {
if (*p == '=') {
int decode_qp_result;
-
+
p = mime_decode_qp(p,&decode_qp_result);
-
+
if (decode_qp_result == -2) {
/* Error from decoder. p is unchanged. */
if (!warned_about_qp_line_sanity) {
p++;
}
else if (decode_qp_result == -1) {
- /* End of the line with soft line break.
+ /* End of the line with soft line break.
Bail out. */
goto QP_RETURN;
}
return num_decoded;
/* ---------------------------------------------- */
};
-
+
return 0;
}
sprintf(f,"demime acl condition: ");
f+=22;
va_start(ap, format);
- vsnprintf(f, 16383,(char *)format, ap);
+ (void)string_vformat(US f, 16383,(char *)format, ap);
va_end(ap);
f-=22;
- log_write(0, LOG_MAIN, f);
+ log_write(0, LOG_MAIN, "%s", f);
/* then copy to demime_reason_buffer if new
level is greater than old level */
if (level > demime_errorlevel) {
struct mime_part mime_part_p;
int has_tnef = 0;
int has_rfc822 = 0;
-
+
/* allocate room for our linebuffer */
line = (uschar *)malloc(MIME_SANITY_MAX_LINE_LENGTH);
if (line == NULL) {
- snprintf(CS info, 1024,"unable to allocate %u bytes",MIME_SANITY_MAX_LINE_LENGTH);
+ (void)string_format(info, 1024,"unable to allocate %u bytes",MIME_SANITY_MAX_LINE_LENGTH);
return DEFER;
};
-
+
/* clear MIME header structure */
memset(&mime_part_p,0,sizeof(mime_part));
-
+
/* ----------------------- start demux loop --------------------- */
while (mime_read_line_status == MIME_READ_LINE_OK) {
-
+
/* read a line of input. Depending on the mode we are in,
the returned format will differ. */
mime_read_line_status = mime_read_line(f,mime_demux_mode,line,&line_len);
-
+
if (mime_read_line_status == MIME_READ_LINE_OVERFLOW) {
mime_trigger_error(MIME_ERRORLEVEL_LONG_LINE);
/* despite the error, continue .. */
if (mime_demux_mode == MIME_DEMUX_MODE_MIME_HEADERS) {
/* -------------- header mode --------------------- */
-
+
/* Check for an empty line, which is the end of the headers.
In HEADER mode, the line is returned "cooked", with the
final '\n' replaced by a ';' */
if (line_len == 1) {
int tmp;
-
+
/* We have reached the end of the headers. Start decoding
with the collected settings. */
if (mime_part_p.seen_content_transfer_encoding > 1) {
/* default to plain mode if no specific encoding type found */
mime_demux_mode = MIME_DEMUX_MODE_PLAIN;
};
-
+
/* open new dump file */
tmp = mime_get_dump_file(mime_part_p.extension, &mime_dump_file, info);
if (tmp < 0) {
if (strncmpic(US"content-type:",line,Ustrlen("content-type:")) == 0) {
/* ---------------------------- Content-Type header ------------------------------- */
uschar *value = line;
-
+
/* check for message/partial MIME type and reject it */
if (mime_header_find(line,US"message/partial",NULL) > 0)
mime_trigger_error(MIME_ERRORLEVEL_MESSAGE_PARTIAL);
/* check for TNEF content type, remember to unpack TNEF later. */
if (mime_header_find(line,US"application/ms-tnef",NULL) > 0)
has_tnef = 1;
-
+
/* check for message/rfcxxx attachments */
if (mime_header_find(line,US"message/rfc822",NULL) > 0)
has_rfc822 = 1;
-
+
/* find the file extension, but do not fill it in
it is already set, since content-disposition has
precedence. */
if (mime_header_find(line,US"name",&value) == 2) {
if (Ustrlen(value) > MIME_SANITY_MAX_FILENAME)
mime_trigger_error(MIME_ERRORLEVEL_FILENAME_LENGTH);
- mime_part_p.extension = value;
+ mime_part_p.extension = value;
mime_part_p.extension = Ustrrchr(value,'.');
if (mime_part_p.extension == NULL) {
/* file without extension, setting
else {
struct file_extension *this_extension =
(struct file_extension *)malloc(sizeof(file_extension));
-
- this_extension->file_extension_string =
+
+ this_extension->file_extension_string =
(uschar *)malloc(Ustrlen(mime_part_p.extension)+1);
Ustrcpy(this_extension->file_extension_string,
mime_part_p.extension+1);
};
};
};
-
+
/* find a boundary and add it to the list, if present */
value = line;
if (mime_header_find(line,US"boundary",&value) == 2) {
struct boundary *thisboundary;
if (Ustrlen(value) > MIME_SANITY_MAX_BOUNDARY_LENGTH) {
- mime_trigger_error(MIME_ERRORLEVEL_BOUNDARY_LENGTH);
+ mime_trigger_error(MIME_ERRORLEVEL_BOUNDARY_LENGTH);
}
else {
thisboundary = (struct boundary*)malloc(sizeof(boundary));
boundaries = thisboundary;
};
};
-
+
if (mime_part_p.seen_content_type == 0) {
mime_part_p.seen_content_type = 1;
}
}
else if (strncmpic(US"content-transfer-encoding:",line,Ustrlen("content-transfer-encoding:")) == 0) {
/* ---------------------------- Content-Transfer-Encoding header -------------- */
-
+
if (mime_part_p.seen_content_transfer_encoding == 0) {
if (mime_header_find(line,US"base64",NULL) > 0) {
mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_BASE64;
else if (strncmpic(US"content-disposition:",line,Ustrlen("content-disposition:")) == 0) {
/* ---------------------------- Content-Disposition header -------------------- */
uschar *value = line;
-
+
if (mime_part_p.seen_content_disposition == 0) {
mime_part_p.seen_content_disposition = 1;
-
+
if (mime_header_find(line,US"filename",&value) == 2) {
if (Ustrlen(value) > MIME_SANITY_MAX_FILENAME)
mime_trigger_error(MIME_ERRORLEVEL_FILENAME_LENGTH);
else {
struct file_extension *this_extension =
(struct file_extension *)malloc(sizeof(file_extension));
-
- this_extension->file_extension_string =
+
+ this_extension->file_extension_string =
(uschar *)malloc(Ustrlen(mime_part_p.extension)+1);
Ustrcpy(this_extension->file_extension_string,
mime_part_p.extension+1);
if (uu_mode == MIME_UU_MODE_UNCONFIRMED) {
/* We are in unconfirmed UUENCODE mode. */
-
+
data_len = uu_decode_line(line,&data,line_len,info);
-
+
if (data_len == -2) {
/* temp error, turn off uudecode mode */
if (uu_dump_file != NULL) {
- fclose(uu_dump_file); uu_dump_file = NULL;
+ (void)fclose(uu_dump_file); uu_dump_file = NULL;
};
uu_mode = MIME_UU_MODE_OFF;
return DEFER;
}
else if (data_len == -1) {
if (uu_dump_file != NULL) {
- fclose(uu_dump_file); uu_dump_file = NULL;
+ (void)fclose(uu_dump_file); uu_dump_file = NULL;
};
uu_mode = MIME_UU_MODE_OFF;
data_len = 0;
check for single "end" tag on line */
if ((strncmpic(line,US"end",3) == 0) && (line[3] < 32)) {
if (uu_dump_file != NULL) {
- fclose(uu_dump_file); uu_dump_file = NULL;
+ (void)fclose(uu_dump_file); uu_dump_file = NULL;
};
uu_mode = MIME_UU_MODE_OFF;
}
if (data_len == -2) {
/* temp error, turn off uudecode mode */
if (uu_dump_file != NULL) {
- fclose(uu_dump_file); uu_dump_file = NULL;
+ (void)fclose(uu_dump_file); uu_dump_file = NULL;
};
uu_mode = MIME_UU_MODE_OFF;
return DEFER;
};
};
};
-
+
/* write data to dump file, if available */
if (data_len > 0) {
if (fwrite(data,1,data_len,uu_dump_file) < data_len) {
/* short write */
- snprintf(CS info, 1024,"short write on uudecode dump file");
+ (void)string_format(info, 1024,"short write on uudecode dump file");
free(line);
- return DEFER;
+ return DEFER;
};
};
};
-
+
if (mime_demux_mode != MIME_DEMUX_MODE_SCANNING) {
/* Non-scanning and Non-header mode. That means
we are currently decoding data to the dump
rewind(mime_dump_file);
mime_demux(mime_dump_file,info);
};
-
- fclose(mime_dump_file); mime_dump_file = NULL;
+
+ (void)fclose(mime_dump_file); mime_dump_file = NULL;
};
}
else if (tmp == 2) {
rewind(mime_dump_file);
mime_demux(mime_dump_file,info);
};
-
- fclose(mime_dump_file); mime_dump_file = NULL;
+
+ (void)fclose(mime_dump_file); mime_dump_file = NULL;
};
}
else {
uschar *data;
long data_len = 0;
-
+
/* decode the line with the appropriate method */
if (mime_demux_mode == MIME_DEMUX_MODE_PLAIN) {
/* in plain mode, just dump the line */
data_len = 0;
};
};
-
+
/* write data to dump file */
if (data_len > 0) {
if (fwrite(data,1,data_len,mime_dump_file) < data_len) {
/* short write */
- snprintf(CS info, 1024,"short write on dump file");
+ (void)string_format(info, 1024,"short write on dump file");
free(line);
- return DEFER;
+ return DEFER;
};
};
-
+
};
}
else {
if (mime_check_boundary(line,boundaries) == 1) {
mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS;
};
-
+
};
/* ------------------------------------------------ */
};
};
/* ----------------------- end demux loop ----------------------- */
-
+
/* close files, they could still be open */
if (mime_dump_file != NULL)
- fclose(mime_dump_file);
+ (void)fclose(mime_dump_file);
if (uu_dump_file != NULL)
- fclose(uu_dump_file);
-
+ (void)fclose(uu_dump_file);
+
/* release line buffer */
free(line);
-
+
/* FIXME: release boundary buffers.
Not too much of a problem since
this instance of exim is not resident. */
-
+
if (has_tnef) {
uschar file_name[1024];
/* at least one file could be TNEF encoded.
attempt to send all decoded files thru the TNEF decoder */
-
- snprintf(CS file_name,1024,"%s/scan/%s",spool_directory,message_id);
+
+ (void)string_format(file_name,1024,"%s/scan/%s",spool_directory,message_id);
/* Removed FTTB. We need to decide on TNEF inclusion */
/* mime_unpack_tnef(file_name); */
};