| 1 | /************************************************* |
| 2 | * Exim - an Internet mail transport agent * |
| 3 | *************************************************/ |
| 4 | |
| 5 | /* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-2015 |
| 6 | * License: GPL |
| 7 | * Copyright (c) The Exim Maintainers 2016 - 2018 |
| 8 | */ |
| 9 | |
| 10 | /* Code for matching regular expressions against headers and body. |
| 11 | Called from acl.c. */ |
| 12 | |
| 13 | #include "exim.h" |
| 14 | #ifdef WITH_CONTENT_SCAN |
| 15 | #include <unistd.h> |
| 16 | #include <sys/mman.h> |
| 17 | |
| 18 | /* Structure to hold a list of Regular expressions */ |
| 19 | typedef struct pcre_list { |
| 20 | pcre *re; |
| 21 | uschar *pcre_text; |
| 22 | struct pcre_list *next; |
| 23 | } pcre_list; |
| 24 | |
| 25 | uschar regex_match_string_buffer[1024]; |
| 26 | |
| 27 | extern FILE *mime_stream; |
| 28 | extern uschar *mime_current_boundary; |
| 29 | |
| 30 | static pcre_list * |
| 31 | compile(const uschar * list) |
| 32 | { |
| 33 | int sep = 0; |
| 34 | uschar *regex_string; |
| 35 | const char *pcre_error; |
| 36 | int pcre_erroffset; |
| 37 | pcre_list *re_list_head = NULL; |
| 38 | pcre_list *ri; |
| 39 | |
| 40 | /* precompile our regexes */ |
| 41 | while ((regex_string = string_nextinlist(&list, &sep, NULL, 0))) |
| 42 | if (strcmpic(regex_string, US"false") != 0 && Ustrcmp(regex_string, "0") != 0) |
| 43 | { |
| 44 | pcre *re; |
| 45 | |
| 46 | /* compile our regular expression */ |
| 47 | if (!(re = pcre_compile( CS regex_string, |
| 48 | 0, &pcre_error, &pcre_erroffset, NULL ))) |
| 49 | { |
| 50 | log_write(0, LOG_MAIN, |
| 51 | "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", |
| 52 | regex_string, pcre_error, pcre_erroffset); |
| 53 | continue; |
| 54 | } |
| 55 | |
| 56 | ri = store_get(sizeof(pcre_list), FALSE); |
| 57 | ri->re = re; |
| 58 | ri->pcre_text = regex_string; |
| 59 | ri->next = re_list_head; |
| 60 | re_list_head = ri; |
| 61 | } |
| 62 | return re_list_head; |
| 63 | } |
| 64 | |
| 65 | static int |
| 66 | matcher(pcre_list * re_list_head, uschar * linebuffer, int len) |
| 67 | { |
| 68 | for(pcre_list * ri = re_list_head; ri; ri = ri->next) |
| 69 | { |
| 70 | int ovec[3*(REGEX_VARS+1)]; |
| 71 | int n; |
| 72 | |
| 73 | /* try matcher on the line */ |
| 74 | if ((n = pcre_exec(ri->re, NULL, CS linebuffer, len, 0, 0, ovec, nelem(ovec))) > 0) |
| 75 | { |
| 76 | Ustrncpy(regex_match_string_buffer, ri->pcre_text, |
| 77 | sizeof(regex_match_string_buffer)-1); |
| 78 | regex_match_string = regex_match_string_buffer; |
| 79 | |
| 80 | for (int nn = 1; nn < n; nn++) |
| 81 | regex_vars[nn-1] = |
| 82 | string_copyn(linebuffer + ovec[nn*2], ovec[nn*2+1] - ovec[nn*2]); |
| 83 | |
| 84 | return OK; |
| 85 | } |
| 86 | } |
| 87 | return FAIL; |
| 88 | } |
| 89 | |
| 90 | int |
| 91 | regex(const uschar **listptr) |
| 92 | { |
| 93 | unsigned long mbox_size; |
| 94 | FILE *mbox_file; |
| 95 | pcre_list *re_list_head; |
| 96 | uschar *linebuffer; |
| 97 | long f_pos = 0; |
| 98 | int ret = FAIL; |
| 99 | |
| 100 | /* reset expansion variable */ |
| 101 | regex_match_string = NULL; |
| 102 | |
| 103 | if (!mime_stream) /* We are in the DATA ACL */ |
| 104 | { |
| 105 | if (!(mbox_file = spool_mbox(&mbox_size, NULL, NULL))) |
| 106 | { /* error while spooling */ |
| 107 | log_write(0, LOG_MAIN|LOG_PANIC, |
| 108 | "regex acl condition: error while creating mbox spool file"); |
| 109 | return DEFER; |
| 110 | } |
| 111 | } |
| 112 | else |
| 113 | { |
| 114 | if ((f_pos = ftell(mime_stream)) < 0) |
| 115 | { |
| 116 | log_write(0, LOG_MAIN|LOG_PANIC, |
| 117 | "regex acl condition: mime_stream: %s", strerror(errno)); |
| 118 | return DEFER; |
| 119 | } |
| 120 | mbox_file = mime_stream; |
| 121 | } |
| 122 | |
| 123 | /* precompile our regexes */ |
| 124 | if (!(re_list_head = compile(*listptr))) |
| 125 | return FAIL; /* no regexes -> nothing to do */ |
| 126 | |
| 127 | /* match each line against all regexes */ |
| 128 | linebuffer = store_get(32767, TRUE); /* tainted */ |
| 129 | while (fgets(CS linebuffer, 32767, mbox_file)) |
| 130 | { |
| 131 | if ( mime_stream && mime_current_boundary /* check boundary */ |
| 132 | && Ustrncmp(linebuffer, "--", 2) == 0 |
| 133 | && Ustrncmp((linebuffer+2), mime_current_boundary, |
| 134 | Ustrlen(mime_current_boundary)) == 0) |
| 135 | break; /* found boundary */ |
| 136 | |
| 137 | if ((ret = matcher(re_list_head, linebuffer, (int)Ustrlen(linebuffer))) == OK) |
| 138 | goto done; |
| 139 | } |
| 140 | /* no matches ... */ |
| 141 | |
| 142 | done: |
| 143 | if (!mime_stream) |
| 144 | (void)fclose(mbox_file); |
| 145 | else |
| 146 | { |
| 147 | clearerr(mime_stream); |
| 148 | if (fseek(mime_stream, f_pos, SEEK_SET) == -1) |
| 149 | { |
| 150 | log_write(0, LOG_MAIN|LOG_PANIC, |
| 151 | "regex acl condition: mime_stream: %s", strerror(errno)); |
| 152 | clearerr(mime_stream); |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | return ret; |
| 157 | } |
| 158 | |
| 159 | |
| 160 | int |
| 161 | mime_regex(const uschar **listptr) |
| 162 | { |
| 163 | pcre_list *re_list_head = NULL; |
| 164 | FILE *f; |
| 165 | uschar *mime_subject = NULL; |
| 166 | int mime_subject_len = 0; |
| 167 | int ret; |
| 168 | |
| 169 | /* reset expansion variable */ |
| 170 | regex_match_string = NULL; |
| 171 | |
| 172 | /* precompile our regexes */ |
| 173 | if (!(re_list_head = compile(*listptr))) |
| 174 | return FAIL; /* no regexes -> nothing to do */ |
| 175 | |
| 176 | /* check if the file is already decoded */ |
| 177 | if (!mime_decoded_filename) |
| 178 | { /* no, decode it first */ |
| 179 | const uschar *empty = US""; |
| 180 | mime_decode(&empty); |
| 181 | if (!mime_decoded_filename) |
| 182 | { /* decoding failed */ |
| 183 | log_write(0, LOG_MAIN, |
| 184 | "mime_regex acl condition warning - could not decode MIME part to file"); |
| 185 | return DEFER; |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | /* open file */ |
| 190 | if (!(f = fopen(CS mime_decoded_filename, "rb"))) |
| 191 | { |
| 192 | log_write(0, LOG_MAIN, |
| 193 | "mime_regex acl condition warning - can't open '%s' for reading", |
| 194 | mime_decoded_filename); |
| 195 | return DEFER; |
| 196 | } |
| 197 | |
| 198 | /* get 32k memory, tainted */ |
| 199 | mime_subject = store_get(32767, TRUE); |
| 200 | |
| 201 | mime_subject_len = fread(mime_subject, 1, 32766, f); |
| 202 | |
| 203 | ret = matcher(re_list_head, mime_subject, mime_subject_len); |
| 204 | (void)fclose(f); |
| 205 | return ret; |
| 206 | } |
| 207 | |
| 208 | #endif /* WITH_CONTENT_SCAN */ |