--- /dev/null
+/* $Cambridge: exim/src/src/demime.c,v 1.1.2.1 2004/12/02 16:33:30 tom 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 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);
+
+ 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;
+
+ snprintf(CS 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 = fopen(CS file_name,"w+");
+ if (*f == NULL) {
+ /* cannot open new dump file, disk full ? -> soft error */
+ snprintf(CS 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;
+ 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) {
+ snprintf(CS 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);
+ 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) {
+ snprintf(CS 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);
+ vsnprintf(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) {
+ snprintf(CS 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) {
+ 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;
+ };
+ 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) {
+ 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) {
+ 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 */
+ snprintf(CS 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);
+ };
+
+ 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);
+ };
+
+ 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 */
+ snprintf(CS 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)
+ fclose(mime_dump_file);
+ if (uu_dump_file != NULL)
+ 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);
+ /* Removed FTTB. We need to decide on TNEF inclusion */
+ /* mime_unpack_tnef(file_name); */
+ };
+
+ return 0;
+}
+
+#endif
--- /dev/null
+/* $Cambridge: exim/src/src/demime.h,v 1.1.2.1 2004/12/02 16:33:30 tom Exp $ */
+
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */
+/* License: GPL */
+
+/* demime defines */
+
+#ifdef WITH_OLD_DEMIME
+
+#define MIME_DEMUX_MODE_SCANNING 0
+#define MIME_DEMUX_MODE_MIME_HEADERS 1
+#define MIME_DEMUX_MODE_BASE64 2
+#define MIME_DEMUX_MODE_QP 3
+#define MIME_DEMUX_MODE_PLAIN 4
+
+#define MIME_UU_MODE_OFF 0
+#define MIME_UU_MODE_UNCONFIRMED 1
+#define MIME_UU_MODE_CONFIRMED 2
+
+#define MIME_MAX_EXTENSION 128
+
+#define MIME_READ_LINE_EOF 0
+#define MIME_READ_LINE_OK 1
+#define MIME_READ_LINE_OVERFLOW 2
+
+#define MIME_SANITY_MAX_LINE_LENGTH 131071
+#define MIME_SANITY_MAX_FILENAME 512
+#define MIME_SANITY_MAX_HEADER_OPTION_VALUE 1024
+#define MIME_SANITY_MAX_B64_LINE_LENGTH 76
+#define MIME_SANITY_MAX_BOUNDARY_LENGTH 1024
+#define MIME_SANITY_MAX_DUMP_FILES 1024
+
+
+
+/* MIME errorlevel settings */
+
+#define MIME_ERRORLEVEL_LONG_LINE 3,US"line length in message or single header size exceeds %u bytes",MIME_SANITY_MAX_LINE_LENGTH
+#define MIME_ERRORLEVEL_TOO_MANY_PARTS 3,US"too many MIME parts (max %u)",MIME_SANITY_MAX_DUMP_FILES
+#define MIME_ERRORLEVEL_MESSAGE_PARTIAL 3,US"'message/partial' MIME type"
+#define MIME_ERRORLEVEL_FILENAME_LENGTH 3,US"proposed filename exceeds %u characters",MIME_SANITY_MAX_FILENAME
+#define MIME_ERRORLEVEL_BOUNDARY_LENGTH 3,US"boundary length exceeds %u characters",MIME_SANITY_MAX_BOUNDARY_LENGTH
+#define MIME_ERRORLEVEL_DOUBLE_HEADERS 2,US"double headers (content-type, content-disposition or content-transfer-encoding)"
+#define MIME_ERRORLEVEL_UU_MISALIGNED 1,US"uuencoded line length is not a multiple of 4 characters"
+#define MIME_ERRORLEVEL_UU_LINE_LENGTH 1,US"uuencoded line length does not match advertised number of bytes"
+#define MIME_ERRORLEVEL_B64_LINE_LENGTH 1,US"base64 line length exceeds %u characters",MIME_SANITY_MAX_B64_LINE_LENGTH
+#define MIME_ERRORLEVEL_B64_ILLEGAL_CHAR 2,US"base64 line contains illegal character"
+#define MIME_ERRORLEVEL_B64_MISALIGNED 1,US"base64 line length is not a multiple of 4 characters"
+#define MIME_ERRORLEVEL_QP_ILLEGAL_CHAR 1,US"quoted-printable encoding contains illegal character"
+
+
+/* demime structures */
+
+typedef struct mime_part {
+ /* true if there was a content-type header */
+ int seen_content_type;
+ /* true if there was a content-transfer-encoding header
+ contains the encoding type */
+ int seen_content_transfer_encoding;
+ /* true if there was a content-disposition header */
+ int seen_content_disposition;
+ /* pointer to a buffer with the proposed file extension */
+ uschar *extension;
+} mime_part;
+
+typedef struct boundary {
+ struct boundary *next;
+ uschar *boundary_string;
+} boundary;
+
+typedef struct file_extension {
+ struct file_extension *next;
+ uschar *file_extension_string;
+} file_extension;
+
+/* demime.c prototypes */
+
+unsigned int mime_hstr_i(uschar *);
+uschar *mime_decode_qp(uschar *, int *);
+int mime_get_dump_file(uschar *, FILE **, uschar *);
+int mime_header_find(uschar *, uschar *, uschar **);
+int mime_read_line(FILE *, int, uschar *, long *);
+int mime_check_boundary(uschar *, struct boundary *);
+int mime_check_uu_start(uschar *, uschar *, int *);
+long uu_decode_line(uschar *, uschar **, long, uschar *);
+long mime_decode_line(int ,uschar *, uschar **, long, uschar *);
+void mime_trigger_error(int, uschar *, ...);
+int mime_demux(FILE *, uschar *);
+
+
+
+/* BASE64 decoder matrix */
+static unsigned char b64[256]={
+/* 0 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+/* 16 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+/* 32 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 62, 128, 128, 128, 63,
+/* 48 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 128, 128, 128, 255, 128, 128,
+/* 64 */ 128, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+/* 80 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 128, 128, 128, 128, 128,
+/* 96 */ 128, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128
+};
+
+
+/* Microsoft-Style uudecode matrix */
+static unsigned char uudec[256]={
+/* 0 */ 0, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+/* 16 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+/* 32 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+/* 48 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+/* 64 */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+/* 80 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+/* 96 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+/* 112 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+/* 128 */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+/* 144 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+/* 160 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+/* 176 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+/* 192 */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+/* 208 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+/* 224 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+/* 240 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
+};
+
+#endif