Merge branch '4.next'
[exim.git] / src / src / regex_cache.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /*
6  * Copyright (c) The Exim Maintainers 2022
7  * License: GPL
8  */
9
10 /* Caching layers for compiled REs.  There is a local layer in the process,
11 implemented as a tree for inserts and lookup.  This cache is inherited from
12 the daemon, for the process tree deriving from there - but not by re-exec'd
13 proceses or commandline submission processes.
14
15 If the process has to compile, and is not the daemon or a re-exec'd exim,
16 it notifies the use of the RE to the daemon via a unix-domain socket.
17 This is a fire-and-forget send with no response, hence cheap from the point-of
18 view of the sender.  I have not measured the overall comms costs.  The
19 daemon also compiles the RE, and caches the result.
20
21 A second layer would be possible by asking the daemon via the notifier socket
22 (for a result from its cache, or a compile if it must).  The comms overhead
23 is significant, not only for the channel but also for de/serialisation of
24 the compiled object.  This makes it untenable for the primary use-case, the
25 transport process which has been re-exec'd to gain privs - and therefore does not
26 have the daemon-maintained cache.  Using shared-memory might reduce that cost
27 (the attach time for the memory segment will matter); the implimentation
28 would require suitable R/W locks.
29 */
30
31 #include "exim.h"
32
33 typedef struct re_req {
34   uschar        notifier_reqtype;
35   BOOL          caseless;
36   uschar        re[1];          /* extensible */
37 } re_req;
38
39 static tree_node * regex_cache = NULL;
40 static tree_node * regex_caseless_cache = NULL;
41
42 #define REGEX_CACHESIZE_LIMIT 1000
43
44 /******************************************************************************/
45
46 static void
47 regex_to_daemon(const uschar * key, BOOL caseless)
48 {
49 int klen = Ustrlen(key) + 1;
50 int rlen = sizeof(re_req) + klen;
51 re_req * req;
52 int fd, old_pool = store_pool;
53
54 DEBUG(D_expand|D_lists)
55   debug_printf_indent("sending RE '%s' to daemon\n", key);
56
57 store_pool = POOL_MAIN;
58   req = store_get(rlen, key);   /* maybe need a size limit */
59 store_pool = old_pool;;
60 req->notifier_reqtype = NOTIFY_REGEX;
61 req->caseless = caseless;
62 memcpy(req->re, key, klen);
63
64 if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) >= 0)
65   {
66   struct sockaddr_un sa_un = {.sun_family = AF_UNIX};
67   ssize_t len = daemon_notifier_sockname(&sa_un);
68
69   if (sendto(fd, req, rlen, 0, (struct sockaddr *)&sa_un, (socklen_t)len) < 0)
70     DEBUG(D_queue_run)
71       debug_printf("%s: sendto %s\n", __FUNCTION__, strerror(errno));
72   close(fd);
73   }
74 else DEBUG(D_queue_run) debug_printf(" socket: %s\n", strerror(errno));
75 }
76
77
78 static const pcre2_code *
79 regex_from_cache(const uschar * key, BOOL caseless)
80 {
81 tree_node * node  =
82   tree_search(caseless ? regex_caseless_cache : regex_cache, key);
83 DEBUG(D_expand|D_lists)
84   debug_printf_indent("compiled %sRE '%s' %sfound in local cache\n",
85                       caseless ? "caseless " : "", key, node ? "" : "not ");
86
87 return node ? node->data.ptr : NULL;
88 }
89
90
91 static void
92 regex_to_cache(const uschar * key, BOOL caseless, const pcre2_code * cre)
93 {
94 PCRE2_SIZE srelen;
95 uschar * sre;
96 tree_node * node;
97
98 node = store_get(sizeof(tree_node) + Ustrlen(key) + 1, key);    /* we are called with STORE_PERM */
99 Ustrcpy(node->name, key);
100 node->data.ptr = (void *)cre;
101
102 if (!tree_insertnode(caseless ? &regex_caseless_cache : &regex_cache, node))
103   { DEBUG(D_expand|D_lists) debug_printf_indent("duplicate key!\n"); }
104 else DEBUG(D_expand|D_lists)
105   debug_printf_indent("compiled RE '%s' saved in local cache\n", key);
106
107 /* Additionally, if not re-execed and not the daemon, tell the daemon of the RE
108 so it can add to the cache */
109
110 if (f.daemon_scion && !f.daemon_listen)
111   regex_to_daemon(key, caseless);
112
113 return;
114 }
115
116 /******************************************************************************/
117
118 /*************************************************
119 *  Compile regular expression and panic on fail  *
120 *************************************************/
121
122 /* This function is called when failure to compile a regular expression leads
123 to a panic exit. In other cases, pcre_compile() is called directly. In many
124 cases where this function is used, the results of the compilation are to be
125 placed in long-lived store, so we temporarily reset the store management
126 functions that PCRE uses if the use_malloc flag is set.
127
128 Argument:
129   pattern     the pattern to compile
130   flags
131    caseless    caseless matching is required
132    cacheable   use (writeback) cache
133   use_malloc  TRUE if compile into malloc store
134
135 Returns:      pointer to the compiled pattern
136 */
137
138 const pcre2_code *
139 regex_must_compile(const uschar * pattern, mcs_flags flags, BOOL use_malloc)
140 {
141 BOOL caseless = !!(flags & MCS_CASELESS);
142 size_t offset;
143 const pcre2_code * yield;
144 int old_pool = store_pool, err;
145
146 /* Optionall, check the cache and return if found */
147
148 if (  flags & MCS_CACHEABLE
149    && (yield = regex_from_cache(pattern, caseless)))
150   return yield;
151
152 store_pool = POOL_PERM;
153
154 if (!(yield = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED,
155   caseless ? PCRE_COPT|PCRE2_CASELESS : PCRE_COPT,
156   &err, &offset, use_malloc ? pcre_mlc_cmp_ctx : pcre_gen_cmp_ctx)))
157   {
158   uschar errbuf[128];
159   pcre2_get_error_message(err, errbuf, sizeof(errbuf));
160   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "regular expression error: "
161     "%s at offset %ld while compiling %s", errbuf, (long)offset, pattern);
162   }
163
164 if (use_malloc)
165   {
166   /*pcre2_general_context_free(gctx);*/
167   }
168
169 if (flags & MCS_CACHEABLE)
170   regex_to_cache(pattern, caseless, yield);
171
172 store_pool = old_pool;
173 return yield;
174 }
175
176
177
178
179 /* Wrapper for pcre2_compile() and error-message handling.
180
181 Arguments:      pattern         regex to compile
182                 flags
183                  caseless       flag for match variant
184                  cacheable      use (writeback) cache
185                 errstr          on error, filled in with error message
186                 cctx            compile-context for pcre2
187
188 Return:         NULL on error, with errstr set. Otherwise, the compiled RE object
189 */
190
191 const pcre2_code *
192 regex_compile(const uschar * pattern, mcs_flags flags, uschar ** errstr,
193   pcre2_compile_context * cctx)
194 {
195 const uschar * key = pattern;
196 BOOL caseless = !!(flags & MCS_CASELESS);
197 int err;
198 PCRE2_SIZE offset;
199 const pcre2_code * yield;
200 int old_pool = store_pool;
201
202 /* Optionally, check the cache and return if found */
203
204 if (  flags & MCS_CACHEABLE
205    && (yield = regex_from_cache(key, caseless)))
206   return yield;
207
208 DEBUG(D_expand|D_lists) debug_printf_indent("compiling %sRE '%s'\n",
209                                 caseless ? "caseless " : "", pattern);
210
211 store_pool = POOL_PERM;
212 if (!(yield = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED,
213                 caseless ? PCRE_COPT|PCRE2_CASELESS : PCRE_COPT,
214                 &err, &offset, cctx)))
215   {
216   uschar errbuf[128];
217   pcre2_get_error_message(err, errbuf, sizeof(errbuf));
218   store_pool = old_pool;
219   *errstr = string_sprintf("regular expression error in "
220             "\"%s\": %s at offset %ld", pattern, errbuf, (long)offset);
221   }
222 else if (flags & MCS_CACHEABLE)
223   regex_to_cache(key, caseless, yield);
224 store_pool = old_pool;
225
226 return yield;
227 }
228
229
230
231 /* Handle a regex notify arriving at the daemon.  We get sent the original RE;
232 compile it (again) and write to the cache.  Later forked procs will be able to
233 read from the cache, unless they re-execed.  Therefore, those latter never bother
234 sending us a notification. */
235
236 void
237 regex_at_daemon(const uschar * reqbuf)
238 {
239 const re_req * req = (const re_req *)reqbuf;
240 uschar * errstr;
241 const pcre2_code * cre;
242
243 if (regex_cachesize >= REGEX_CACHESIZE_LIMIT)
244   errstr = US"regex cache size limit reached";
245 else if ((cre = regex_compile(req->re,
246             req->caseless ? MCS_CASELESS | MCS_CACHEABLE : MCS_CACHEABLE,
247             &errstr, pcre_gen_cmp_ctx)))
248   regex_cachesize++;
249
250 DEBUG(D_any) if (!cre) debug_printf("%s\n", errstr);
251 return;
252 }