int mime_acl_check(uschar *acl, FILE *f, struct mime_boundary_context *context, uschar **user_msgptr, uschar **log_msgptr) { int rc = OK; uschar * header = NULL; struct mime_boundary_context nested_context; /* reserve a line buffer to work in */ header = store_get(MIME_MAX_HEADER_SIZE+1); /* Not actually used at the moment, but will be vital to fixing * some RFC 2046 nonconformance later... */ nested_context.parent = context; /* loop through parts */ while(1) { /* reset all per-part mime variables */ mime_vars_reset(); /* If boundary is null, we assume that *f is positioned on the start of headers (for example, at the very beginning of a message. If a boundary is given, we must first advance to it to reach the start of the next header block. */ /* NOTE -- there's an error here -- RFC2046 specifically says to * check for outer boundaries. This code doesn't do that, and * I haven't fixed this. * * (I have moved partway towards adding support, however, by adding * a "parent" field to my new boundary-context structure.) */ if (context) for (;;) { if (!fgets(CS header, MIME_MAX_HEADER_SIZE, f)) { /* Hit EOF or read error. Ugh. */ DEBUG(D_acl) debug_printf("MIME: Hit EOF ...\n"); return rc; } /* boundary line must start with 2 dashes */ if ( Ustrncmp(header, "--", 2) == 0 && Ustrncmp(header+2, context->boundary, Ustrlen(context->boundary)) == 0 ) { /* found boundary */ if (Ustrncmp((header+2+Ustrlen(context->boundary)), "--", 2) == 0) { /* END boundary found */ DEBUG(D_acl) debug_printf("MIME: End boundary found %s\n", context->boundary); return rc; } DEBUG(D_acl) debug_printf("MIME: Next part with boundary %s\n", context->boundary); break; } } /* parse headers, set up expansion variables */ while (mime_get_header(f, header)) { struct mime_header * mh; /* look for interesting headers */ for (mh = mime_header_list; mh < mime_header_list + mime_header_list_size; mh++) if (strncmpic(mh->name, header, mh->namelen) == 0) { uschar * p = header + mh->namelen; uschar * q; /* grab the value (normalize to lower case) and copy to its corresponding expansion variable */ for (q = p; *q != ';' && *q; q++) ; *mh->value = string_copynlc(p, q-p); DEBUG(D_acl) debug_printf("MIME: found %s header, value is '%s'\n", mh->name, *mh->value); if (*(p = q)) p++; /* jump past the ; */ { uschar * mime_fname = NULL; uschar * mime_fname_rfc2231 = NULL; uschar * mime_filename_charset = NULL; BOOL decoding_failed = FALSE; /* grab all param=value tags on the remaining line, check if they are interesting */ while (*p) { mime_parameter * mp; DEBUG(D_acl) debug_printf("MIME: considering paramlist '%s'\n", p); if ( !mime_filename && strncmpic(CUS"content-disposition:", header, 20) == 0 && strncmpic(CUS"filename*", p, 9) == 0 ) { /* RFC 2231 filename */ uschar * q; /* find value of the filename */ p += 9; while(*p != '=' && *p) p++; if (*p) p++; /* p is filename or NUL */ q = mime_param_val(&p); /* p now trailing ; or NUL */ if (q && *q) { uschar * temp_string, * err_msg; int slen; /* build up an un-decoded filename over successive filename*= parameters (for use when 2047 decode fails) */ mime_fname_rfc2231 = string_sprintf("%#s%s", mime_fname_rfc2231, q); if (!decoding_failed) { int size; if (!mime_filename_charset) { uschar * s = q; /* look for a ' in the "filename" */ while(*s != '\'' && *s) s++; /* s is 1st ' or NUL */ if ((size = s-q) > 0) mime_filename_charset = string_copyn(q, size); if (*(p = s)) p++; while(*p == '\'') p++; /* p is after 2nd ' */ } else p = q; DEBUG(D_acl) debug_printf("MIME: charset %s fname '%s'\n", mime_filename_charset ? mime_filename_charset : US"<NULL>", p); temp_string = rfc2231_to_2047(p, mime_filename_charset, &slen); DEBUG(D_acl) debug_printf("MIME: 2047-name %s\n", temp_string); temp_string = rfc2047_decode(temp_string, FALSE, NULL, ' ', NULL, &err_msg); DEBUG(D_acl) debug_printf("MIME: plain-name %s\n", temp_string); size = Ustrlen(temp_string); if (size == slen) decoding_failed = TRUE; else /* build up a decoded filename over successive filename*= parameters */ mime_filename = mime_fname = mime_fname ? string_sprintf("%s%s", mime_fname, temp_string) : temp_string; } } } else /* look for interesting parameters */ for (mp = mime_parameter_list; mp < mime_parameter_list + nelem(mime_parameter_list); mp++ ) if (strncmpic(mp->name, p, mp->namelen) == 0) { uschar * q; uschar * dummy_errstr; /* grab the value and copy to its expansion variable */ p += mp->namelen; q = mime_param_val(&p); /* p now trailing ; or NUL */ *mp->value = q && *q ? rfc2047_decode(q, check_rfc2047_length, NULL, 32, NULL, &dummy_errstr) : NULL; DEBUG(D_acl) debug_printf( "MIME: found %s parameter in %s header, value '%s'\n", mp->name, mh->name, *mp->value); break; /* done matching param names */ } /* There is something, but not one of our interesting parameters. Advance past the next semicolon */ p = mime_next_semicolon(p); if (*p) p++; } /* param scan on line */ if (strncmpic(CUS"content-disposition:", header, 20) == 0) { if (decoding_failed) mime_filename = mime_fname_rfc2231; DEBUG(D_acl) debug_printf( "MIME: found %s parameter in %s header, value is '%s'\n", "filename", mh->name, mime_filename); } } } } /* set additional flag variables (easier access) */ if ( mime_content_type && Ustrncmp(mime_content_type,"multipart",9) == 0 ) mime_is_multipart = 1; /* Make a copy of the boundary pointer. Required since mime_boundary is global and can be overwritten further down in recursion */ nested_context.boundary = mime_boundary; /* raise global counter */ mime_part_count++; /* copy current file handle to global variable */ mime_stream = f; mime_current_boundary = context ? context->boundary : 0; /* Note the context */ mime_is_coverletter = !(context && context->context == MBC_ATTACHMENT); /* call ACL handling function */ rc = acl_check(ACL_WHERE_MIME, NULL, acl, user_msgptr, log_msgptr); mime_stream = NULL; mime_current_boundary = NULL; if (rc != OK) break; /* If we have a multipart entity and a boundary, go recursive */ if ( (mime_content_type != NULL) && (nested_context.boundary != NULL) && (Ustrncmp(mime_content_type,"multipart",9) == 0) ) { DEBUG(D_acl) debug_printf("MIME: Entering multipart recursion, boundary '%s'\n", nested_context.boundary); nested_context.context = context && context->context == MBC_ATTACHMENT ? MBC_ATTACHMENT : Ustrcmp(mime_content_type,"multipart/alternative") == 0 || Ustrcmp(mime_content_type,"multipart/related") == 0 ? MBC_COVERLETTER_ALL : MBC_COVERLETTER_ONESHOT; rc = mime_acl_check(acl, f, &nested_context, user_msgptr, log_msgptr); if (rc != OK) break; } else if ( (mime_content_type != NULL) && (Ustrncmp(mime_content_type,"message/rfc822",14) == 0) ) { const uschar *rfc822name = NULL; uschar filename[2048]; int file_nr = 0; int result = 0; /* must find first free sequential filename */ do { struct stat mystat; (void)string_format(filename, 2048, "%s/scan/%s/__rfc822_%05u", spool_directory, message_id, file_nr++); /* security break */ if (file_nr >= 128) goto NO_RFC822; result = stat(CS filename,&mystat); } while (result != -1); rfc822name = filename; /* decode RFC822 attachment */ mime_decoded_filename = NULL; mime_stream = f; mime_current_boundary = context ? context->boundary : NULL; mime_decode(&rfc822name); mime_stream = NULL; mime_current_boundary = NULL; if (!mime_decoded_filename) /* decoding failed */ { log_write(0, LOG_MAIN, "mime_regex acl condition warning - could not decode RFC822 MIME part to file."); rc = DEFER; goto out; } mime_decoded_filename = NULL; } NO_RFC822: /* If the boundary of this instance is NULL, we are finished here */ if (!context) break; if (context->context == MBC_COVERLETTER_ONESHOT) context->context = MBC_ATTACHMENT; } out: mime_vars_reset(); return rc; }
int mime_regex(uschar **listptr) { int sep = 0; uschar *list = *listptr; uschar *regex_string; uschar regex_string_buffer[1024]; pcre *re; pcre_list *re_list_head = NULL; pcre_list *re_list_item; const char *pcre_error; int pcre_erroffset; FILE *f; uschar *mime_subject = NULL; int mime_subject_len = 0; /* reset expansion variable */ regex_match_string = NULL; /* precompile our regexes */ while ((regex_string = string_nextinlist(&list, &sep, regex_string_buffer, sizeof(regex_string_buffer))) != NULL) { /* parse option */ if ( (strcmpic(regex_string,US"false") == 0) || (Ustrcmp(regex_string,"0") == 0) ) { /* explicitly no matching */ continue; }; /* compile our regular expression */ re = pcre_compile( CS regex_string, 0, &pcre_error, &pcre_erroffset, NULL ); if (re == NULL) { log_write(0, LOG_MAIN, "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", regex_string, pcre_error, pcre_erroffset); continue; } else { re_list_item = store_get(sizeof(pcre_list)); re_list_item->re = re; re_list_item->pcre_text = string_copy(regex_string); re_list_item->next = re_list_head; re_list_head = re_list_item; }; }; /* no regexes -> nothing to do */ if (re_list_head == NULL) { return FAIL; }; /* check if the file is already decoded */ if (mime_decoded_filename == NULL) { uschar *empty = US""; /* no, decode it first */ mime_decode(&empty); if (mime_decoded_filename == NULL) { /* decoding failed */ log_write(0, LOG_MAIN, "mime_regex acl condition warning - could not decode MIME part to file."); return DEFER; }; }; /* open file */ f = fopen(CS mime_decoded_filename, "rb"); if (f == NULL) { /* open failed */ log_write(0, LOG_MAIN, "mime_regex acl condition warning - can't open '%s' for reading.", mime_decoded_filename); return DEFER; }; /* get 32k memory */ mime_subject = (uschar *)store_get(32767); /* read max 32k chars from file */ mime_subject_len = fread(mime_subject, 1, 32766, f); re_list_item = re_list_head; do { /* try matcher on the mmapped file */ debug_printf("Matching '%s'\n", re_list_item->pcre_text); if (pcre_exec(re_list_item->re, NULL, CS mime_subject, mime_subject_len, 0, 0, NULL, 0) >= 0) { Ustrncpy(regex_match_string_buffer, re_list_item->pcre_text, 1023); regex_match_string = regex_match_string_buffer; (void)fclose(f); return OK; }; re_list_item = re_list_item->next; } while (re_list_item != NULL); (void)fclose(f); /* no matches ... */ return FAIL; }
/* * Break out the components of a multipart message * (This function expects to be fed HEADERS + CONTENT) * Note: NULL can be supplied as content_end; in this case, the message is * considered to have ended when the parser encounters a 0x00 byte. */ static void recurseable_mime_parser(char *partnum, char *content_start, char *content_end, MimeParserCallBackType CallBack, MimeParserCallBackType PreMultiPartCallBack, MimeParserCallBackType PostMultiPartCallBack, void *userdata, int dont_decode, interesting_mime_headers *m) { interesting_mime_headers *SubMimeHeaders; char *ptr; char *part_start; char *part_end = NULL; char *evaluate_crlf_ptr = NULL; char *next_boundary; char nested_partnum[256]; int crlf_in_use = 0; int part_seq = 0; CBufStr *chosen_name; /* If this is a multipart message, then recursively process it */ ptr = content_start; part_start = NULL; if (m->is_multipart) { /* Tell the client about this message's multipartedness */ if (PreMultiPartCallBack != NULL) { PreMultiPartCallBack("", "", partnum, "", NULL, m->b[content_type].Key, m->b[charset].Key, 0, m->b[encoding].Key, m->b[id].Key, userdata); } /* Figure out where the boundaries are */ m->b[startary].len = snprintf(m->b[startary].Key, SIZ, "--%s", m->b[boundary].Key); SubMimeHeaders = InitInterestingMimes (); while ((*ptr == '\r') || (*ptr == '\n')) ptr ++; if (strncmp(ptr, m->b[startary].Key, m->b[startary].len) == 0) ptr += m->b[startary].len; while ((*ptr == '\r') || (*ptr == '\n')) ptr ++; part_start = NULL; do { char *optr; optr = ptr; if (parse_MimeHeaders(SubMimeHeaders, &ptr, content_end) != 0) break; if ((ptr - optr > 2) && (*(ptr - 2) == '\r')) crlf_in_use = 1; part_start = ptr; next_boundary = FindNextContent(ptr, content_end, SubMimeHeaders, m); if ((next_boundary != NULL) && (next_boundary - part_start < 3)) { FlushInterestingMimes(SubMimeHeaders); continue; } if ( (part_start != NULL) && (next_boundary != NULL) ) { part_end = next_boundary; --part_end; /* omit the trailing LF */ if (crlf_in_use) { --part_end; /* omit the trailing CR */ } if (!IsEmptyStr(partnum)) { snprintf(nested_partnum, sizeof nested_partnum, "%s.%d", partnum, ++part_seq); } else { snprintf(nested_partnum, sizeof nested_partnum, "%d", ++part_seq); } recurseable_mime_parser(nested_partnum, part_start, part_end, CallBack, PreMultiPartCallBack, PostMultiPartCallBack, userdata, dont_decode, SubMimeHeaders); } if (next_boundary != NULL) { /* If we pass out of scope, don't attempt to * read past the end boundary. */ if ((*(next_boundary + m->b[startary].len) == '-') && (*(next_boundary + m->b[startary].len + 1) == '-') ){ ptr = content_end; } else { /* Set up for the next part. */ part_start = strstr(next_boundary, "\n"); /* Determine whether newlines are LF or CRLF */ evaluate_crlf_ptr = part_start; --evaluate_crlf_ptr; if ((*evaluate_crlf_ptr == '\r') && (*(evaluate_crlf_ptr + 1) == '\n')) { crlf_in_use = 1; } else { crlf_in_use = 0; } /* Advance past the LF ... now we're in the next part */ ++part_start; ptr = part_start; } } else { /* Invalid end of multipart. Bail out! */ ptr = content_end; } FlushInterestingMimes(SubMimeHeaders); } while ( (ptr < content_end) && (next_boundary != NULL) ); free(SubMimeHeaders); if (PostMultiPartCallBack != NULL) { PostMultiPartCallBack("", "", partnum, "", NULL, m->b[content_type].Key, m->b[charset].Key, 0, m->b[encoding].Key, m->b[id].Key, userdata); } } /* If it's not a multipart message, then do something with it */ else { size_t length; part_start = ptr; length = content_end - part_start; ptr = part_end = content_end; /* The following code will truncate the MIME part to the size * specified by the Content-length: header. We have commented it * out because these headers have a tendency to be wrong. * * if ( (content_length > 0) && (length > content_length) ) { * length = content_length; * } */ /* Sometimes the "name" field is tacked on to Content-type, * and sometimes it's tacked on to Content-disposition. Use * whichever one we have. */ if (m->b[content_disposition_name].len > m->b[content_type_name].len) { chosen_name = &m->b[content_disposition_name]; } else { chosen_name = &m->b[content_type_name]; } /* Ok, we've got a non-multipart part here, so do something with it. */ mime_decode(partnum, part_start, length, m->b[content_type].Key, m->b[charset].Key, m->b[encoding].Key, m->b[disposition].Key, m->b[id].Key, chosen_name->Key, m->b[filename].Key, CallBack, NULL, NULL, userdata, dont_decode ); /* * Now if it's an encapsulated message/rfc822 then we have to recurse into it */ if (!strcasecmp(&m->b[content_type].Key[0], "message/rfc822")) { if (PreMultiPartCallBack != NULL) { PreMultiPartCallBack("", "", partnum, "", NULL, m->b[content_type].Key, m->b[charset].Key, 0, m->b[encoding].Key, m->b[id].Key, userdata); } if (CallBack != NULL) { if (strlen(partnum) > 0) { snprintf(nested_partnum, sizeof nested_partnum, "%s.%d", partnum, ++part_seq); } else { snprintf(nested_partnum, sizeof nested_partnum, "%d", ++part_seq); } the_mime_parser(nested_partnum, part_start, part_end, CallBack, PreMultiPartCallBack, PostMultiPartCallBack, userdata, dont_decode ); } if (PostMultiPartCallBack != NULL) { PostMultiPartCallBack("", "", partnum, "", NULL, m->b[content_type].Key, m->b[charset].Key, 0, m->b[encoding].Key, m->b[id].Key, userdata); } } } }