X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/40c90bca9f7e2952bd64faebceb53538f80805a7..1d28cc061677bd07d9bed48dd84bd5c590247043:/src/src/mime.c diff --git a/src/src/mime.c b/src/src/mime.c index c924f2bc3..7c3a33d62 100644 --- a/src/src/mime.c +++ b/src/src/mime.c @@ -2,9 +2,11 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) Tom Kistner 2004, 2015 +/* + * Copyright (c) The Exim Maintainers 2015 - 2022 + * Copyright (c) Tom Kistner 2004 - 2015 * License: GPL - * Copyright (c) The Exim Maintainers 2016 + * SPDX-License-Identifier: GPL-2.0-or-later */ #include "exim.h" @@ -16,6 +18,7 @@ FILE *mime_stream = NULL; uschar *mime_current_boundary = NULL; static mime_header mime_header_list[] = { + /* name namelen value */ { US"content-type:", 13, &mime_content_type }, { US"content-disposition:", 20, &mime_content_disposition }, { US"content-transfer-encoding:", 26, &mime_content_transfer_encoding }, @@ -26,6 +29,7 @@ static mime_header mime_header_list[] = { static int mime_header_list_size = nelem(mime_header_list); static mime_parameter mime_parameter_list[] = { + /* name namelen value */ { US"name=", 5, &mime_filename }, { US"filename=", 9, &mime_filename }, { US"charset=", 8, &mime_charset }, @@ -105,23 +109,23 @@ return initial_pos; static ssize_t mime_decode_asis(FILE* in, FILE* out, uschar* boundary) { - ssize_t len, size = 0; - uschar buffer[MIME_MAX_LINE_LENGTH]; +ssize_t len, size = 0; +uschar buffer[MIME_MAX_LINE_LENGTH]; - while(fgets(CS buffer, MIME_MAX_LINE_LENGTH, mime_stream) != NULL) - { - if (boundary != NULL - && Ustrncmp(buffer, "--", 2) == 0 - && Ustrncmp((buffer+2), boundary, Ustrlen(boundary)) == 0 - ) - break; +while(fgets(CS buffer, MIME_MAX_LINE_LENGTH, mime_stream) != NULL) + { + if (boundary != NULL + && Ustrncmp(buffer, "--", 2) == 0 + && Ustrncmp((buffer+2), boundary, Ustrlen(boundary)) == 0 + ) + break; - len = Ustrlen(buffer); - if (fwrite(buffer, 1, (size_t)len, out) < len) - return -1; - size += len; - } /* while */ - return size; + len = Ustrlen(buffer); + if (fwrite(buffer, 1, (size_t)len, out) < len) + return -1; + size += len; + } /* while */ +return size; } @@ -157,24 +161,16 @@ while (fgets(CS ibuf, MIME_MAX_LINE_LENGTH, in) != NULL) { /* Error from decoder. ipos is unchanged. */ mime_set_anomaly(MIME_ANOMALY_BROKEN_QP); - *opos = '='; - ++opos; + *opos++ = '='; ++ipos; } else if (decode_qp_result == -1) break; else if (decode_qp_result >= 0) - { - *opos = decode_qp_result; - ++opos; - } + *opos++ = decode_qp_result; } else - { - *opos = *ipos; - ++opos; - ++ipos; - } + *opos++ = *ipos++; } /* something to write? */ len = opos - obuf; @@ -188,19 +184,17 @@ return size; } +/* + * Return open filehandle for combo of path and file. + * Side-effect: set mime_decoded_filename, to copy in allocated mem + */ static FILE * mime_get_decode_file(uschar *pname, uschar *fname) { -FILE *f = NULL; -uschar *filename = NULL; - if (pname && fname) - { - filename = string_sprintf("%s/%s", pname, fname); - f = modefopen(filename,"wb+",SPOOL_MODE); - } + mime_decoded_filename = string_sprintf("%s/%s", pname, fname); else if (!pname) - f = modefopen(fname,"wb+",SPOOL_MODE); + mime_decoded_filename = string_copy(fname); else if (!fname) { int file_nr = 0; @@ -210,21 +204,15 @@ else if (!fname) do { struct stat mystat; - filename = string_sprintf("%s/%s-%05u", pname, message_id, file_nr++); + mime_decoded_filename = string_sprintf("%s/%s-%05u", pname, message_id, file_nr++); /* security break */ if (file_nr >= 1024) break; - result = stat(CS filename, &mystat); + result = stat(CS mime_decoded_filename, &mystat); } while(result != -1); - - f = modefopen(filename, "wb+", SPOOL_MODE); } -/* set expansion variable */ -/*XXX ? not set if !pname ? */ -mime_decoded_filename = filename; - -return f; +return modefopen(mime_decoded_filename, "wb+", SPOOL_MODE); } @@ -233,29 +221,24 @@ mime_decode(const uschar **listptr) { int sep = 0; const uschar *list = *listptr; -uschar *option; -uschar option_buffer[1024]; -uschar decode_path[1024]; +uschar * option; +uschar * decode_path; FILE *decode_file = NULL; long f_pos = 0; ssize_t size_counter = 0; ssize_t (*decode_function)(FILE*, FILE*, uschar*); -if (mime_stream == NULL) +if (!mime_stream || (f_pos = ftell(mime_stream)) < 0) return FAIL; -f_pos = ftell(mime_stream); - /* build default decode path (will exist since MBOX must be spooled up) */ -(void)string_format(decode_path,1024,"%s/scan/%s",spool_directory,message_id); +decode_path = string_sprintf("%s/scan/%s", spool_directory, message_id); /* try to find 1st option */ -if ((option = string_nextinlist(&list, &sep, - option_buffer, - sizeof(option_buffer))) != NULL) +if ((option = string_nextinlist(&list, &sep, NULL, 0))) { /* parse 1st option */ - if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) ) + if ((Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0)) /* explicitly no decoding */ return FAIL; @@ -357,17 +340,16 @@ while(!done) if ( ((c == '\t') || (c == ' ')) && (header_value_mode == 1) ) continue; - /* we have hit a non-whitespace char, start copying value data */ - header_value_mode = 2; + /* we have hit a non-whitespace char, start copying value data */ + header_value_mode = 2; - if (c == '"') /* flip "quoted" mode */ - header_value_mode = header_value_mode==2 ? 3 : 2; + if (c == '"') /* flip "quoted" mode */ + header_value_mode = header_value_mode==2 ? 3 : 2; - /* leave value mode on unquoted ';' */ - if (header_value_mode == 2 && c == ';') { - header_value_mode = 0; - }; - /* -------------------------------- */ + /* leave value mode on unquoted ';' */ + if (header_value_mode == 2 && c == ';') + header_value_mode = 0; + /* -------------------------------- */ } else { @@ -415,13 +397,11 @@ if ((num_copied > 0) && (header[num_copied-1] != ';')) header[num_copied] = '\0'; /* return 0 for EOF or empty line */ -if ((c == EOF) || (num_copied == 1)) - return 0; -else - return 1; +return c == EOF || num_copied == 1 ? 0 : 1; } +/* reset all per-part mime variables */ static void mime_vars_reset(void) { @@ -453,24 +433,22 @@ static uschar * mime_param_val(uschar ** sp) { uschar * s = *sp; -uschar * val = NULL; -int size = 0, ptr = 0; +gstring * val = NULL; -/* debug_printf(" considering paramval '%s'\n", s); */ +/* debug_printf_indent(" considering paramval '%s'\n", s); */ while (*s && *s != ';') /* ; terminates */ if (*s == '"') { s++; /* skip opening " */ while (*s && *s != '"') /* " protects ; */ - val = string_catn(val, &size, &ptr, s++, 1); + val = string_catn(val, s++, 1); if (*s) s++; /* skip closing " */ } else - val = string_catn(val, &size, &ptr, s++, 1); -if (val) val[ptr] = '\0'; + val = string_catn(val, s++, 1); *sp = s; -return val; +return string_from_gstring(val); } static uschar * @@ -493,27 +471,26 @@ return s; static uschar * rfc2231_to_2047(const uschar * fname, const uschar * charset, int * len) { -int size = 0, ptr = 0; -uschar * val = string_catn(NULL, &size, &ptr, US"=?", 2); +gstring * val = string_catn(NULL, US"=?", 2); uschar c; if (charset) - val = string_cat(val, &size, &ptr, charset); -val = string_catn(val, &size, &ptr, US"?Q?", 3); + val = string_cat(val, charset); +val = string_catn(val, US"?Q?", 3); while ((c = *fname)) if (c == '%' && isxdigit(fname[1]) && isxdigit(fname[2])) { - val = string_catn(val, &size, &ptr, US"=", 1); - val = string_catn(val, &size, &ptr, ++fname, 2); + val = string_catn(val, US"=", 1); + val = string_catn(val, ++fname, 2); fname += 2; } else - val = string_catn(val, &size, &ptr, fname++, 1); + val = string_catn(val, fname++, 1); -val = string_catn(val, &size, &ptr, US"?=", 2); -val[*len = ptr] = '\0'; -return val; +val = string_catn(val, US"?=", 2); +*len = val->ptr; +return string_from_gstring(val); } @@ -525,8 +502,8 @@ 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); +/* reserve a line buffer to work in. Assume tainted data. */ +header = store_get(MIME_MAX_HEADER_SIZE+1, GET_TAINTED); /* Not actually used at the moment, but will be vital to fixing * some RFC 2046 nonconformance later... */ @@ -555,7 +532,7 @@ while(1) if (!fgets(CS header, MIME_MAX_HEADER_SIZE, f)) { /* Hit EOF or read error. Ugh. */ - DEBUG(D_acl) debug_printf("MIME: Hit EOF ...\n"); + DEBUG(D_acl) debug_printf_indent("MIME: Hit EOF ...\n"); return rc; } @@ -567,12 +544,12 @@ while(1) if (Ustrncmp((header+2+Ustrlen(context->boundary)), "--", 2) == 0) { /* END boundary found */ - DEBUG(D_acl) debug_printf("MIME: End boundary found %s\n", + DEBUG(D_acl) debug_printf_indent("MIME: End boundary found %s\n", context->boundary); return rc; } - DEBUG(D_acl) debug_printf("MIME: Next part with boundary %s\n", + DEBUG(D_acl) debug_printf_indent("MIME: Next part with boundary %s\n", context->boundary); break; } @@ -580,11 +557,9 @@ while(1) /* 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; + for (struct mime_header * mh = mime_header_list; mh < mime_header_list + mime_header_list_size; mh++) if (strncmpic(mh->name, header, mh->namelen) == 0) { @@ -596,7 +571,7 @@ while(1) 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", + DEBUG(D_acl) debug_printf_indent("MIME: found %s header, value is '%s'\n", mh->name, *mh->value); if (*(p = q)) p++; /* jump past the ; */ @@ -612,9 +587,7 @@ while(1) while (*p) { - mime_parameter * mp; - - DEBUG(D_acl) debug_printf("MIME: considering paramlist '%s'\n", p); + DEBUG(D_acl) debug_printf_indent("MIME: considering paramlist '%s'\n", p); if ( !mime_filename && strncmpic(CUS"content-disposition:", header, 20) == 0 @@ -659,19 +632,17 @@ while(1) else p = q; - DEBUG(D_acl) debug_printf("MIME: charset %s fname '%s'\n", + DEBUG(D_acl) debug_printf_indent("MIME: charset %s fname '%s'\n", mime_filename_charset ? mime_filename_charset : US"", p); temp_string = rfc2231_to_2047(p, mime_filename_charset, &slen); - DEBUG(D_acl) debug_printf("MIME: 2047-name %s\n", temp_string); + DEBUG(D_acl) debug_printf_indent("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); + DEBUG(D_acl) debug_printf_indent("MIME: plain-name %s\n", temp_string); - size = Ustrlen(temp_string); - - if (size == slen) + if (!temp_string || (size = Ustrlen(temp_string)) == slen) decoding_failed = TRUE; else /* build up a decoded filename over successive @@ -686,7 +657,7 @@ while(1) else /* look for interesting parameters */ - for (mp = mime_parameter_list; + for (mime_parameter * mp = mime_parameter_list; mp < mime_parameter_list + nelem(mime_parameter_list); mp++ ) if (strncmpic(mp->name, p, mp->namelen) == 0) @@ -702,7 +673,7 @@ while(1) ? rfc2047_decode(q, check_rfc2047_length, NULL, 32, NULL, &dummy_errstr) : NULL; - DEBUG(D_acl) debug_printf( + DEBUG(D_acl) debug_printf_indent( "MIME: found %s parameter in %s header, value '%s'\n", mp->name, mh->name, *mp->value); @@ -720,13 +691,12 @@ while(1) { if (decoding_failed) mime_filename = mime_fname_rfc2231; - DEBUG(D_acl) debug_printf( + DEBUG(D_acl) debug_printf_indent( "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 @@ -758,12 +728,11 @@ while(1) 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) ) + if ( mime_content_type && nested_context.boundary + && Ustrncmp(mime_content_type,"multipart",9) == 0) { DEBUG(D_acl) - debug_printf("MIME: Entering multipart recursion, boundary '%s'\n", + debug_printf_indent("MIME: Entering multipart recursion, boundary '%s'\n", nested_context.boundary); nested_context.context = @@ -777,25 +746,25 @@ while(1) 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) ) + else if ( mime_content_type + && Ustrncmp(mime_content_type,"message/rfc822",14) == 0) { - const uschar *rfc822name = NULL; - uschar filename[2048]; + const uschar * rfc822name = NULL; + uschar * filename; int file_nr = 0; int result = 0; /* must find first free sequential filename */ - do + for (gstring * g = string_get(64); result != -1; g->ptr = 0) { struct stat mystat; - (void)string_format(filename, 2048, + g = string_fmt_append(g, "%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); + result = stat(CS (filename = string_from_gstring(g)), &mystat); + } rfc822name = filename; @@ -809,7 +778,7 @@ while(1) if (!mime_decoded_filename) /* decoding failed */ { log_write(0, LOG_MAIN, - "mime_regex acl condition warning - could not decode RFC822 MIME part to file."); + "MIME acl condition warning - could not decode RFC822 MIME part to file."); rc = DEFER; goto out; }