/* $Cambridge: exim/exim-src/src/demime.c,v 1.9 2006/02/22 14:46:44 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */
/* License: GPL */
/* Code for unpacking MIME containers. Called from acl.c. */
#include "exim.h"
#ifdef WITH_OLD_DEMIME
#include "demime.h"
uschar demime_reason_buffer[1024];
struct file_extension *file_extensions = NULL;
int demime(uschar **listptr) {
int sep = 0;
uschar *list = *listptr;
uschar *option;
uschar option_buffer[64];
unsigned long mbox_size;
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 */
return FAIL;
};
}
else {
/* no options -> no demimeing */
return FAIL;
};
/* make sure the eml mbox file is spooled up */
mbox_file = spool_mbox(&mbox_size);
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;
};
/* call demimer if not already done earlier */
if (!demime_ok)
demime_rc = mime_demux(mbox_file, defer_error_buffer);
(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) {
found_extension = NULL;
return OK;
};
/* loop thru extension list */
while (this_extension != NULL) {
if (strcmpic(option, this_extension->file_extension_string) == 0) {
/* found one */
found_extension = this_extension->file_extension_string;
return OK;
};
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;
}
/*************************************************
* small hex_str -> integer conversion function *
*************************************************/
/* needed for quoted-printable
*/
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);
}
/*************************************************
* decode quoted-printable chars *
*************************************************/
/* gets called when we hit a =
returns: new pointer position
result code in c:
-2 - decode error
-1 - soft line break, no char
0-255 - char to write
*/
uschar *mime_decode_qp(uschar *qp_p,int *c) {
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
just ignore it, but remember
that this is not a valid hex
encoding any more */
nan = 1;
qp_p++;
goto REPEAT_FIRST;
}
else if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) {
/* this is a valid hex char, if nan is unset */
if (nan) {
/* this is illegal */
*c = -2;
return initial_pos;
}
else {
hex[0] = *qp_p;
qp_p++;
};
}
else if (*qp_p == '\n') {
/* hit soft line break already, continue */
*c = -1;
return qp_p;
}
else {
/* illegal char here */
*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;
/* do hex conversion */
*c = mime_hstr_i(hex);
qp_p++;
return qp_p;
}
else {
/* huh ? */
*c = -2;
return initial_pos;
};
}
else {
/* illegal char */
*c = -2;
return initial_pos;
};
}
/*************************************************
* open new dump file *
*************************************************/
/* open new dump file
returns: -2 soft error
or file #, FILE * in f
*/
int mime_get_dump_file(uschar *extension, FILE **f, uschar *info) {
uschar file_name[1024];
int result;
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;
};
p++;
};
/* find a new file to write to */
file_nr = 0;
do {
struct stat mystat;
(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 */
mime_trigger_error(MIME_ERRORLEVEL_TOO_MANY_PARTS);
break;
};
result = stat(CS file_name,&mystat);
}
while(result != -1);
*f = modefopen(file_name,"wb+",SPOOL_MODE);
if (*f == NULL) {
/* cannot open new dump file, disk full ? -> soft error */
(void)string_format(info, 1024,"unable to open dump file");
return -2;
};
return file_nr;
}
/*************************************************
* Find a string in a mime header *
*************************************************/
/* 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) {
needle += Ustrlen(param);
if (*needle == '=') {
uschar *value_start;
uschar *value_end;
value_start = needle + 1;
value_end = strstric(value_start,US";",FALSE);
if (value_end != NULL) {
/* allocate mem for value */
*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;
};
};
};
return 1;
};
return 0;
}
/*************************************************
* Read a line of MIME input *
*************************************************/
/* returns status code, one of
MIME_READ_LINE_EOF 0
MIME_READ_LINE_OK 1
MIME_READ_LINE_OVERFLOW 2
In header mode, the line will be "cooked".
*/
int mime_read_line(FILE *f, int mime_demux_mode, uschar *buffer, long *num_copied) {
int c = EOF;
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;
(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;
if (header_value_mode) {
/* --------- value mode ----------- */
/* skip quotes */
if (c == '"') continue;
/* leave value mode on ';' */
if (c == ';') {
header_value_mode = 0;
};
/* -------------------------------- */
}
else {
/* -------- non-value mode -------- */
if (c == '\\') {
/* quote next char. can be used
to escape brackets. */
c = fgetc(f);
if (c == EOF) break;
}
else if (c == '(') {
header_open_brackets++;
continue;
}
else if ((c == ')') && header_open_brackets) {
header_open_brackets--;
continue;
}
else if ( (c == '=') && !header_open_brackets ) {
/* enter value mode */
header_value_mode = 1;
};
/* skip chars while we are in a comment */
if (header_open_brackets > 0)
continue;
/* -------------------------------- */
};
}
/* ------------------------------------ */
else {
/* ----------- non-header mode -------- */
/* break on '\n' */
if (c == '\n')
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
if (c == EOF)
return MIME_READ_LINE_EOF;
else
return MIME_READ_LINE_OK;
}
/*************************************************
* Check for a MIME boundary *
*************************************************/
/* returns: 0 - no boundary found
1 - start boundary found
2 - end boundary found
*/
int mime_check_boundary(uschar *line, struct boundary *boundaries) {
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++;
};
};
workbuf[j+1]='\0';
while(thisboundary != NULL) {
if (Ustrncmp(workbuf,thisboundary->boundary_string,Ustrlen(thisboundary->boundary_string)) == 0) {
if (Ustrncmp(&workbuf[Ustrlen(thisboundary->boundary_string)],"--",2) == 0) {
/* final boundary found */
return 2;
};
return 1;
};
thisboundary = thisboundary->next;
};
};
return 0;
}
/*************************************************
* Check for start of a UUENCODE block *
*************************************************/
/* returns 0 for no hit,
>0 for hit
*/
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;
};
/* 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 {
/* nothing found */
return 0;
};
}
/*************************************************
* Decode a uu line *
*************************************************/
/* returns number of decoded bytes
-2 for soft errors
*/
int warned_about_uudec_line_sanity_1 = 0;
int warned_about_uudec_line_sanity_2 = 0;
long uu_decode_line(uschar *line, uschar **data, long line_len, uschar *info) {
uschar *p;
long num_decoded = 0;
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) {
(void)string_format(info, 1024,"unable to allocate %lu bytes",line_len);
return -2;
};
work = (uschar *)malloc(line_len);
if (work == NULL) {
(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) {
/* ignore this line */
return 0;
}
else {
uu_decoded_line_len = uudec[work[0]];
};
p = &work[1];
while (*p > 32) {
*p = uudec[*p];
p++;
};
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) {
mime_trigger_error(MIME_ERRORLEVEL_UU_MISALIGNED);
warned_about_uudec_line_sanity_1 = 1;
};
return -1;
};
/* check that the line length matches */
if ( ( (((uu_encoded_line_len/4)*3)-2) > uu_decoded_line_len ) || (((uu_encoded_line_len/4)*3) < uu_decoded_line_len) ) {
if (!warned_about_uudec_line_sanity_2) {
mime_trigger_error(MIME_ERRORLEVEL_UU_LINE_LENGTH);
warned_about_uudec_line_sanity_2 = 1;
};
return -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;
}
/*************************************************
* Decode a b64 or qp line *
*************************************************/
/* returns number of decoded bytes
-1 for hard errors
-2 for soft errors
*/
int warned_about_b64_line_length = 0;
int warned_about_b64_line_sanity = 0;
int warned_about_b64_illegal_char = 0;
int warned_about_qp_line_sanity = 0;
long mime_decode_line(int mime_demux_mode,uschar *line, uschar **data, long max_data_len, uschar *info) {
uschar *p;
long num_decoded = 0;
int offset = 0;
uschar tmp_c;
/* allocate memory for data */
*data = (uschar *)malloc(max_data_len);
if (*data == NULL) {
(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) {
mime_trigger_error(MIME_ERRORLEVEL_B64_LINE_LENGTH);
warned_about_b64_line_length = 1;
};
};
p = line;
offset = 0;
while (*(p+offset) != '\0') {
/* hit illegal char ? */
if (b64[*(p+offset)] == 128) {
if (!warned_about_b64_illegal_char) {
mime_trigger_error(MIME_ERRORLEVEL_B64_ILLEGAL_CHAR);
warned_about_b64_illegal_char = 1;
};
offset++;
}
else {
*p = b64[*(p+offset)];
p++;
};
};
*p = 255;
/* check that resulting line length is a multiple of 4 */
if ( ( (p - &line[0]) % 4 ) != 0) {
if (!warned_about_b64_line_sanity) {
mime_trigger_error(MIME_ERRORLEVEL_B64_MISALIGNED);
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;
/* ---------------------------------------------- */
}
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) {
mime_trigger_error(MIME_ERRORLEVEL_QP_ILLEGAL_CHAR);
warned_about_qp_line_sanity = 1;
};
(*data)[num_decoded] = '=';
num_decoded++;
p++;
}
else if (decode_qp_result == -1) {
/* End of the line with soft line break.
Bail out. */
goto QP_RETURN;
}
else if (decode_qp_result >= 0) {
(*data)[num_decoded] = decode_qp_result;
num_decoded++;
};
}
else {
(*data)[num_decoded] = *p;
num_decoded++;
p++;
};
};
QP_RETURN:
return num_decoded;
/* ---------------------------------------------- */
};
return 0;
}
/*************************************************
* Log demime errors and set mime error level *
*************************************************/
/* This sets the global demime_reason expansion
variable and the demime_errorlevel gauge. */
void mime_trigger_error(int level, uschar *format, ...) {
char *f;
va_list ap;
if( (f = malloc(16384+23)) != NULL ) {
/* first log the incident */
sprintf(f,"demime acl condition: ");
f+=22;
va_start(ap, format);
(void)string_vformat(US f, 16383,(char *)format, ap);
va_end(ap);
f-=22;
log_write(0, LOG_MAIN, f);
/* then copy to demime_reason_buffer if new
level is greater than old level */
if (level > demime_errorlevel) {
demime_errorlevel = level;
Ustrcpy(demime_reason_buffer, US f);
demime_reason = demime_reason_buffer;
};
free(f);
};
}
/*************************************************
* Demultiplex MIME stream. *
*************************************************/
/* We can handle BASE64, QUOTED-PRINTABLE, and UUENCODE.
UUENCODE does not need to have a proper
transfer-encoding header, we detect it with "begin"
This function will report human parsable errors in
*info.
returns DEFER -> soft error (see *info)
OK -> EOF hit, all ok
*/
int mime_demux(FILE *f, uschar *info) {
int mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS;
int uu_mode = MIME_UU_MODE_OFF;
FILE *mime_dump_file = NULL;
FILE *uu_dump_file = NULL;
uschar *line;
int mime_read_line_status = MIME_READ_LINE_OK;
long line_len;
struct boundary *boundaries = NULL;
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) {
(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 .. */
mime_read_line_status = MIME_READ_LINE_OK;
continue;
}
else if (mime_read_line_status == MIME_READ_LINE_EOF) {
break;
};
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) {
mime_demux_mode = mime_part_p.seen_content_transfer_encoding;
}
else {
/* 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) {
return DEFER;
};
/* clear out mime_part */
memset(&mime_part_p,0,sizeof(mime_part));
}
else {
/* Another header to check for file extensions,
encoding type and boundaries */
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_part_p.extension == NULL) {
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 = Ustrrchr(value,'.');
if (mime_part_p.extension == NULL) {
/* file without extension, setting
NULL will use the default extension later */
mime_part_p.extension = NULL;
}
else {
struct file_extension *this_extension =
(struct file_extension *)malloc(sizeof(file_extension));
this_extension->file_extension_string =
(uschar *)malloc(Ustrlen(mime_part_p.extension)+1);
Ustrcpy(this_extension->file_extension_string,
mime_part_p.extension+1);
this_extension->next = file_extensions;
file_extensions = this_extension;
};
};
};
/* 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);
}
else {
thisboundary = (struct boundary*)malloc(sizeof(boundary));
thisboundary->next = boundaries;
thisboundary->boundary_string = value;
boundaries = thisboundary;
};
};
if (mime_part_p.seen_content_type == 0) {
mime_part_p.seen_content_type = 1;
}
else {
mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS);
};
/* ---------------------------------------------------------------------------- */
}
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 (mime_header_find(line,US"quoted-printable",NULL) > 0) {
mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_QP;
}
else {
mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_PLAIN;
};
}
else {
mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS);
};
/* ---------------------------------------------------------------------------- */
}
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);
mime_part_p.extension = value;
mime_part_p.extension = Ustrrchr(value,'.');
if (mime_part_p.extension == NULL) {
/* file without extension, setting
NULL will use the default extension later */
mime_part_p.extension = NULL;
}
else {
struct file_extension *this_extension =
(struct file_extension *)malloc(sizeof(file_extension));
this_extension->file_extension_string =
(uschar *)malloc(Ustrlen(mime_part_p.extension)+1);
Ustrcpy(this_extension->file_extension_string,
mime_part_p.extension+1);
this_extension->next = file_extensions;
file_extensions = this_extension;
};
};
}
else {
mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS);
};
/* ---------------------------------------------------------------------------- */
};
}; /* End of header checks */
/* ------------------------------------------------ */
}
else {
/* -------------- non-header mode ----------------- */
int tmp;
if (uu_mode == MIME_UU_MODE_OFF) {
uschar uu_file_extension[5];
/* We are not currently decoding UUENCODE
Check for possible UUENCODE start tag. */
if (mime_check_uu_start(line,uu_file_extension,&has_tnef)) {
/* possible UUENCODING start detected.
Set unconfirmed mode first. */
uu_mode = MIME_UU_MODE_UNCONFIRMED;
/* open new uu dump file */
tmp = mime_get_dump_file(uu_file_extension, &uu_dump_file, info);
if (tmp < 0) {
free(line);
return DEFER;
};
};
}
else {
uschar *data;
long data_len = 0;
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) {
(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) {
(void)fclose(uu_dump_file); uu_dump_file = NULL;
};
uu_mode = MIME_UU_MODE_OFF;
data_len = 0;
}
else if (data_len > 0) {
/* we have at least decoded a valid byte
turn on confirmed mode */
uu_mode = MIME_UU_MODE_CONFIRMED;
};
}
else if (uu_mode == MIME_UU_MODE_CONFIRMED) {
/* If we are in confirmed UU mode,
check for single "end" tag on line */
if ((strncmpic(line,US"end",3) == 0) && (line[3] < 32)) {
if (uu_dump_file != NULL) {
(void)fclose(uu_dump_file); uu_dump_file = NULL;
};
uu_mode = MIME_UU_MODE_OFF;
}
else {
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) {
(void)fclose(uu_dump_file); uu_dump_file = NULL;
};
uu_mode = MIME_UU_MODE_OFF;
return DEFER;
}
else if (data_len == -1) {
/* skip this line */
data_len = 0;
};
};
};
/* write data to dump file, if available */
if (data_len > 0) {
if (fwrite(data,1,data_len,uu_dump_file) < data_len) {
/* short write */
(void)string_format(info, 1024,"short write on uudecode dump file");
free(line);
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
file. */
/* Check for a known boundary. */
tmp = mime_check_boundary(line,boundaries);
if (tmp == 1) {
/* We have hit a known start boundary.
That will put us back in header mode. */
mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS;
if (mime_dump_file != NULL) {
/* if the attachment was a RFC822 message, recurse into it */
if (has_rfc822) {
has_rfc822 = 0;
rewind(mime_dump_file);
mime_demux(mime_dump_file,info);
};
(void)fclose(mime_dump_file); mime_dump_file = NULL;
};
}
else if (tmp == 2) {
/* We have hit a known end boundary.
That puts us into scanning mode, which will end when we hit another known start boundary */
mime_demux_mode = MIME_DEMUX_MODE_SCANNING;
if (mime_dump_file != NULL) {
/* if the attachment was a RFC822 message, recurse into it */
if (has_rfc822) {
has_rfc822 = 0;
rewind(mime_dump_file);
mime_demux(mime_dump_file,info);
};
(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 = line;
data_len = line_len;
}
else if ( (mime_demux_mode == MIME_DEMUX_MODE_QP) || (mime_demux_mode == MIME_DEMUX_MODE_BASE64) ) {
data_len = mime_decode_line(mime_demux_mode,line,&data,line_len,info);
if (data_len < 0) {
/* Error reported from the line decoder. */
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 */
(void)string_format(info, 1024,"short write on dump file");
free(line);
return DEFER;
};
};
};
}
else {
/* Scanning mode. We end up here after a end boundary.
This will usually be at the end of a message or at
the end of a MIME container.
We need to look for another start boundary to get
back into header mode. */
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)
(void)fclose(mime_dump_file);
if (uu_dump_file != NULL)
(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 */
(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); */
};
return 0;
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1