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