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