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