1 /*
2 Copyright (C) 2002-2011 Thomas Ries <tries@gmx.net>
3
4 This file is part of Siproxd.
5
6 Siproxd is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 Siproxd is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Siproxd; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 /*
22 * This plugin adds a regular expression rewrite support
23 * for SIP targets.
24 */
25
26
27 /* must be defined before including <plugin.h> */
28 #define PLUGIN_NAME plugin_regex
29
30 #include "config.h"
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <regex.h>
36
37 #include <sys/types.h>
38 #include <netinet/in.h>
39 #include <arpa/inet.h>
40
41 #include <osipparser2/osip_parser.h>
42
43 #include "siproxd.h"
44 #include "plugins.h"
45 #include "redirect_cache.h"
46 #include "log.h"
47
48 static char const ident[]="$Id: plugin_regex.c 526 2015-09-19 14:54:42Z hb9xar $";
49
50 /* Plug-in identification */
51 static char name[]="plugin_regex";
52 static char desc[]="Use regular expressions to rewrite SIP targets";
53
54 /* global configuration storage - required for config file location */
55 extern struct siproxd_config configuration;
56
57 /* constants */
58 #define REDIRECTED_TAG "redirected"
59 #define REDIRECTED_VAL "regex"
60
61 /* plugin configuration storage */
62 static struct plugin_config {
63 stringa_t regex_desc;
64 stringa_t regex_pattern;
65 stringa_t regex_replace;
66 } plugin_cfg;
67
68 /* Instructions for config parser */
69 static cfgopts_t plugin_cfg_opts[] = {
70 { "plugin_regex_desc", TYP_STRINGA,&plugin_cfg.regex_desc, {0, NULL} },
71 { "plugin_regex_pattern", TYP_STRINGA,&plugin_cfg.regex_pattern, {0, NULL} },
72 { "plugin_regex_replace", TYP_STRINGA,&plugin_cfg.regex_replace, {0, NULL} },
73 {0, 0, 0}
74 };
75
76 /* local storage needed for regular expression handling */
77 static regex_t *re;
78 /* Redirect Cache: Queue Head is static */
79 static redirected_cache_element_t redirected_cache;
80
81
82 /* local prototypes */
83 static int plugin_regex_init(void);
84 static int plugin_regex_process(sip_ticket_t *ticket);
85 static int plugin_regex_redirect(sip_ticket_t *ticket);
86 static regmatch_t * rmatch (char *buf, int size, regex_t *re);
87 static int rreplace (char *buf, int size, regex_t *re, regmatch_t pmatch[], char *rp);
88
89
90 /*
91 * Plugin API functions code
92 */
93 /* Initialization */
PLUGIN_INIT(plugin_def_t * plugin_def)94 int PLUGIN_INIT(plugin_def_t *plugin_def) {
95 plugin_def->api_version=SIPROXD_API_VERSION;
96 plugin_def->name=name;
97 plugin_def->desc=desc;
98 plugin_def->exe_mask=PLUGIN_DETERMINE_TARGET;
99
100 /* read the config file */
101 if (read_config(configuration.configfile,
102 configuration.config_search,
103 plugin_cfg_opts, name) == STS_FAILURE) {
104 ERROR("Plugin '%s': could not load config file", name);
105 return STS_FAILURE;
106 }
107
108 return plugin_regex_init();
109 }
110
111 /* Processing */
PLUGIN_PROCESS(int stage,sip_ticket_t * ticket)112 int PLUGIN_PROCESS(int stage, sip_ticket_t *ticket){
113 int sts;
114 sts=plugin_regex_process(ticket);
115 return sts;
116 }
117
118 /* De-Initialization */
PLUGIN_END(plugin_def_t * plugin_def)119 int PLUGIN_END(plugin_def_t *plugin_def){
120 return STS_SUCCESS;
121 }
122
123
124 /*
125 * Workload code
126 */
plugin_regex_init(void)127 static int plugin_regex_init(void) {
128 int i;
129 int sts, retsts;
130 int num_entries;
131 char errbuf[256];
132
133 retsts = STS_SUCCESS;
134
135 /* check for equal entries of patterns and replacements */
136 if (plugin_cfg.regex_pattern.used != plugin_cfg.regex_replace.used) {
137 ERROR("Plugin '%s': number of search patterns (%i) and number of "
138 "replacement patterns (%i) differ!", name,
139 plugin_cfg.regex_pattern.used, plugin_cfg.regex_replace.used);
140 return STS_FAILURE;
141 }
142
143 if (plugin_cfg.regex_pattern.used != plugin_cfg.regex_desc.used) {
144 ERROR("Plugin '%s': number of search patterns (%i) and number of "
145 "descriptions (%i) differ!", name,
146 plugin_cfg.regex_pattern.used, plugin_cfg.regex_desc.used);
147 return STS_FAILURE;
148 }
149
150 /* allocate space for regexes and compile them */
151 num_entries = plugin_cfg.regex_pattern.used;
152 re = malloc(num_entries*sizeof(re[0]));
153 for (i=0; i < num_entries; i++) {
154 sts = regcomp (&re[i], plugin_cfg.regex_pattern.string[i],
155 REG_ICASE|REG_EXTENDED);
156 if (sts != 0) {
157 regerror(sts, &re[i], errbuf, sizeof(errbuf));
158 ERROR("Regular expression [%s] failed to compile: %s",
159 plugin_cfg.regex_pattern.string[i], errbuf);
160 retsts = STS_FAILURE;
161 }
162 }
163
164 return retsts;
165 }
166 /* returns STS_SIP_SENT if processing is to be terminated,
167 * otherwise STS_SUCCESS (go on with processing) */
168 /* code (entry point) */
plugin_regex_process(sip_ticket_t * ticket)169 static int plugin_regex_process(sip_ticket_t *ticket) {
170 int sts=STS_SUCCESS;
171 osip_uri_t *req_url;
172 osip_uri_t *to_url;
173 osip_generic_param_t *r=NULL;
174
175 /* plugin loaded and not configured, return with success */
176 if (plugin_cfg.regex_pattern.used==0) return STS_SUCCESS;
177 if (plugin_cfg.regex_replace.used==0) return STS_SUCCESS;
178
179 DEBUGC(DBCLASS_PLUGIN,"plugin entered");
180 req_url=osip_message_get_uri(ticket->sipmsg);
181 to_url=osip_to_get_url(ticket->sipmsg);
182
183 /* only outgoing direction is handled */
184 sip_find_direction(ticket, NULL);
185 if (ticket->direction != DIR_OUTGOING)
186 return STS_SUCCESS;
187
188 /* only INVITE and ACK are handled */
189 if (!MSG_IS_INVITE(ticket->sipmsg) && !MSG_IS_ACK(ticket->sipmsg))
190 return STS_SUCCESS;
191
192 /* expire old cache entries */
193 expire_redirected_cache(&redirected_cache);
194
195 /* REQ URI with username must exist, prefix string must exist */
196 if (!req_url || !req_url->username)
197 return STS_SUCCESS; /* ignore */
198
199 /* Loop avoidance:
200 * If this INVITE has already been redirected by a prior 302
201 * moved response a "REDIRECTED_TAG" parameter should be present in the
202 * URI.
203 * Hopefully all UAs (Clients) do honor RFC3261 and copy the
204 * *full* URI form the contact header into the new request header
205 * upon a 3xx response.
206 */
207 if (req_url) {
208 osip_uri_param_get_byname(&(req_url->url_params), REDIRECTED_TAG, &r);
209 if (r && r->gvalue && strcmp(r->gvalue,REDIRECTED_VAL)== 0) {
210 DEBUGC(DBCLASS_PLUGIN,"Packet has already been processed (ReqURI)");
211 return STS_SUCCESS;
212 }
213 }
214 if (to_url) {
215 osip_uri_param_get_byname(&(to_url->url_params), REDIRECTED_TAG, &r);
216 if (r && r->gvalue && strcmp(r->gvalue,REDIRECTED_VAL)== 0) {
217 DEBUGC(DBCLASS_PLUGIN,"Packet has already been processed (ToURI)");
218 return STS_SUCCESS;
219 }
220 }
221
222 /*
223 * The SIP message is to be processed
224 */
225
226 /* outgoing INVITE request */
227 if (MSG_IS_INVITE(ticket->sipmsg)) {
228 DEBUGC(DBCLASS_PLUGIN,"processing INVITE");
229 sts=plugin_regex_redirect(ticket);
230 }
231 /* outgoing ACK request: is result of a local 3xx answer (moved...)
232 *
233 * Only consume that particular ACK that belongs to a sent 302 answer,
234 * nothing else. Otherwise the ACK from the redirected call will get
235 * consumed as well and causes the call to be aborted (timeout).
236 * We keep a cache with Call-Ids of such "302 moved" dialogs.
237 * Only consume such ACKs that are part of such a dialog.
238 */
239 else if (MSG_IS_ACK(ticket->sipmsg)) {
240 if (is_in_redirected_cache(&redirected_cache, ticket) == STS_TRUE) {
241 DEBUGC(DBCLASS_PLUGIN,"processing ACK (consume it)");
242 sts=STS_SIP_SENT; /* eat up the ACK that was directed to myself */
243 }
244 }
245
246 return sts;
247 }
248
249
250 /* private plugin code */
plugin_regex_redirect(sip_ticket_t * ticket)251 static int plugin_regex_redirect(sip_ticket_t *ticket) {
252 osip_uri_t *to_url=ticket->sipmsg->to->url;
253 char *url_string=NULL;
254 osip_uri_t *new_to_url;
255 int i, sts;
256 osip_contact_t *contact = NULL;
257 /* character workspaces for regex */
258 #define WORKSPACE_SIZE 128
259 static char in[WORKSPACE_SIZE+1], rp[WORKSPACE_SIZE+1];
260
261 /* do apply to full To URI... */
262 sts = osip_uri_to_str(to_url, &url_string);
263 if (sts != 0) {
264 ERROR("osip_uri_to_str() failed");
265 return STS_FAILURE;
266 }
267 DEBUGC(DBCLASS_BABBLE, "To URI string: [%s]", url_string);
268
269 /* perform search and replace of the regexes, first match hits */
270 for (i = 0; i < plugin_cfg.regex_pattern.used; i++) {
271 regmatch_t *pmatch = NULL;
272 pmatch = rmatch(url_string, WORKSPACE_SIZE, &re[i]);
273 if (pmatch == NULL) continue; /* no match, next */
274
275 /* have a match, do the replacement */
276 INFO("Matched rexec rule: %s",plugin_cfg.regex_desc.string[i] );
277 strncpy (in, url_string, WORKSPACE_SIZE);
278 in[WORKSPACE_SIZE]='\0';
279 strncpy (rp, plugin_cfg.regex_replace.string[i], WORKSPACE_SIZE);
280 rp[WORKSPACE_SIZE]='\0';
281
282 sts = rreplace(in, WORKSPACE_SIZE, &re[i], pmatch, rp);
283 if (sts != STS_SUCCESS) {
284 ERROR("regex replace failed: pattern:[%s] replace:[%s]",
285 plugin_cfg.regex_pattern.string[i],
286 plugin_cfg.regex_replace.string[i]);
287 osip_free(url_string);
288 return STS_FAILURE;
289 }
290 /* only do first match */
291 break;
292 }
293 if (i >= plugin_cfg.regex_pattern.used) {
294 /* no match */
295 osip_free(url_string);
296 return STS_SUCCESS;
297 }
298 /* in: contains the new string */
299
300 sts = osip_uri_init(&new_to_url);
301 if (sts != 0) {
302 ERROR("Unable to initialize URI");
303 osip_free(url_string);
304 return STS_FAILURE;
305 }
306
307 sts = osip_uri_parse(new_to_url, in);
308 if (sts != 0) {
309 ERROR("Unable to parse To URI: %s", in);
310 osip_uri_free(new_to_url);
311 osip_free(url_string);
312 return STS_FAILURE;
313 }
314
315 /* use a "302 Moved temporarily" response back to the client */
316 /* new target is within the Contact Header */
317
318 /* remove all Contact headers in message */
319 for (i=0; (contact != NULL) || (i == 0); i++) {
320 osip_message_get_contact(ticket->sipmsg, 0, &contact);
321 if (contact) {
322 osip_list_remove(&(ticket->sipmsg->contacts),0);
323 osip_contact_free(contact);
324 }
325 } /* for i */
326
327 /* insert one new Contact header containing the new target address */
328 osip_contact_init(&contact);
329 osip_list_add(&(ticket->sipmsg->contacts),contact,0);
330
331 /* link the new_to_url into the Contact list */
332 contact->url = new_to_url;
333 new_to_url = NULL;
334
335 /*
336 * Add the 'REDIRECTED_TAG=REDIRECTED_VAL' parameter to URI. Required to figure out
337 * if this INVITE has already been processed (redirected) and
338 * does not need further attention by this plugin.
339 * THIS IS REQUIRED TO AVOID A LOOP
340 */
341 osip_uri_param_add(&(contact->url->url_params), osip_strdup(REDIRECTED_TAG),
342 osip_strdup(REDIRECTED_VAL));
343
344 INFO("redirecting %s -> %s", url_string, in);
345
346 /* sent redirect message back to local client */
347 add_to_redirected_cache(&redirected_cache, ticket);
348 sip_gen_response(ticket, 302 /*Moved temporarily*/);
349
350 /* release resources and return */
351 osip_free(url_string);
352 return STS_SIP_SENT;
353 }
354
355 /*
356 * This regex replacement code has been proudly borrowed from
357 * http://www.daniweb.com/software-development/c/code/216955#
358 *
359 * buf: input string + output result
360 * rp: replacement string, will be destroyed during processing!
361 * size: size of buf and rp
362 * re: regex to process
363 *
364 * rmatch() performs the initial regexec match, and if a match is found
365 * it returns a pointer to the regmatch array which contains the result
366 * of the match.
367 * Afterwards rreplace() is to be called, providing this regmatch array.
368 *
369 * This eliminates the need to copy the 'rp' string before knowing
370 * if a match is actually there.
371 */
372 #define NMATCHES 10
rmatch(char * buf,int size,regex_t * re)373 static regmatch_t * rmatch (char *buf, int size, regex_t *re) {
374 static regmatch_t pm[NMATCHES]; /* regoff_t is int so size is int */
375
376 /* perform the match */
377 if (regexec (re, buf, NMATCHES, pm, 0)) {
378 return NULL;
379 }
380 return &pm[0];
381 }
382
rreplace(char * buf,int size,regex_t * re,regmatch_t pmatch[],char * rp)383 static int rreplace (char *buf, int size, regex_t *re, regmatch_t pmatch[], char *rp) {
384 char *pos;
385 int sub, so, n;
386
387 /* match(es) found: */
388 for (pos = rp; *pos; pos++) {
389 /* back references \1 ... \9: expand them in 'rp' */
390 if (*pos == '\\' && *(pos + 1) > '0' && *(pos + 1) <= '9') {
391 so = pmatch[*(pos + 1) - 48].rm_so; /* pmatch[1..9] */
392 n = pmatch[*(pos + 1) - 48].rm_eo - so;
393 if (so < 0 || strlen (rp) + n - 1 > size) return STS_FAILURE;
394 memmove (pos + n, pos + 2, strlen (pos) - 1);
395 memmove (pos, buf + so, n);
396 pos = pos + n - 2;
397 }
398 }
399
400 sub = pmatch[1].rm_so; /* no repeated replace when sub >= 0 */
401 /* and replace rp in the input buffer */
402 for (pos = buf; !regexec (re, pos, 1, pmatch, 0); ) {
403 n = pmatch[0].rm_eo - pmatch[0].rm_so;
404 pos += pmatch[0].rm_so;
405 if (strlen (buf) - n + strlen (rp) > size) {
406 return STS_FAILURE;
407 }
408 memmove (pos + strlen (rp), pos + n, strlen (pos) - n + 1);
409 memmove (pos, rp, strlen (rp));
410 pos += strlen (rp);
411 if (sub >= 0) break;
412 }
413 return STS_SUCCESS;
414 }
415