1 /*
2  * http_client Module
3  * Copyright (C) 2015-2016 Edvina AB, Olle E. Johansson
4  *
5  * Based on part of the utils module and part
6  * of the json-rpc-c module
7  *
8  * Copyright (C) 2008 Juha Heinanen
9  * Copyright (C) 2009 1&1 Internet AG
10  * Copyright (C) 2013 Carsten Bock, ng-voice GmbH
11  *
12  * This file is part of Kamailio, a free SIP server.
13  *
14  * Kamailio is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version
18  *
19  * Kamailio is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
27  *
28  */
29 
30 /*! \file
31  * \brief  Kamailio http_client :: The module interface file
32  * \ingroup http_client
33  */
34 
35 /*! \defgroup http_client Kamailio :: Module interface to Curl
36  *
37  * http://curl.haxx.se
38  * A generic library for many protocols
39  *
40  *  http_connect(connection, url, $avp)
41  *  http_connect(connection, url, content-type, data, $avp)
42  *
43  * 	$var(res) = http_connect("anders", "/postlåda", "application/json", "{ ok, {200, ok}}", "$avp(gurka)");
44  * 	$var(res) = http_connect("anders", "/postlåda", "application/json", "$var(tomat)", "$avp(gurka)");
45  *
46  */
47 
48 
49 #include <curl/curl.h>
50 
51 #include "../../core/mod_fix.h"
52 #include "../../core/sr_module.h"
53 #include "../../core/ut.h"
54 #include "../../core/resolve.h"
55 #include "../../core/locking.h"
56 #include "../../core/script_cb.h"
57 #include "../../core/mem/shm_mem.h"
58 #include "../../lib/srdb1/db.h"
59 #include "../../core/rpc.h"
60 #include "../../core/rpc_lookup.h"
61 #include "../../core/config.h"
62 #include "../../core/lvalue.h"
63 #include "../../core/pt.h" /* Process table */
64 #include "../../core/kemi.h"
65 
66 #include "functions.h"
67 #include "curlcon.h"
68 #include "curlrpc.h"
69 #include "curl_api.h"
70 
71 MODULE_VERSION
72 
73 #define CURL_USER_AGENT NAME " (" VERSION " (" ARCH "/" OS_QUOTED "))"
74 #define CURL_USER_AGENT_LEN (sizeof(CURL_USER_AGENT) - 1)
75 
76 /* Module parameter variables */
77 unsigned int default_connection_timeout = 4;
78 char *default_tls_cacert =
79 		NULL; /*!< File name: Default CA cert to use for curl TLS connection */
80 str default_tls_clientcert =
81 		STR_NULL; /*!< File name: Default client certificate to use for curl TLS connection */
82 str default_tls_clientkey =
83 		STR_NULL; /*!< File name: Key in PEM format that belongs to client cert */
84 str default_cipher_suite_list = STR_NULL; /*!< List of allowed cipher suites */
85 unsigned int default_tls_version = 0;	 /*!< 0 = Use libcurl default */
86 unsigned int default_tls_verify_peer =
87 		1; /*!< 0 = Do not verify TLS server cert. 1 = Verify TLS cert (default) */
88 unsigned int default_tls_verify_host =
89 		2;								  /*!< 0 = Do not verify TLS server CN/SAN  2 = Verify TLS server CN/SAN (default) */
90 str default_http_proxy = STR_NULL;		  /*!< Default HTTP proxy to use */
91 unsigned int default_http_proxy_port = 0; /*!< Default HTTP proxy port to use */
92 unsigned int default_http_follow_redirect =
93 		0; /*!< Follow HTTP redirects CURLOPT_FOLLOWLOCATION */
94 unsigned int default_keep_connections =
95 		0; /*!< Keep http connections open for reuse */
96 str default_useragent = {CURL_USER_AGENT,
97 		CURL_USER_AGENT_LEN}; /*!< Default CURL useragent. Default "Kamailio Curl " */
98 unsigned int default_maxdatasize = 0; /*!< Default download size. 0=disabled */
99 unsigned int default_authmethod =
100 		CURLAUTH_BASIC
101 		| CURLAUTH_DIGEST; /*!< authentication method - Basic, Digest or both */
102 
103 char *default_netinterface = 0; /*!< local network interface */
104 
105 /*!< Default http query result mode
106  * - 0: return full result
107  * - 1: return first line only */
108 unsigned int default_query_result = 1;
109 /*!< Default download size for result of query function. 0=disabled (no limit) */
110 unsigned int default_query_maxdatasize = 0;
111 
112 str http_client_config_file = STR_NULL;
113 
114 static curl_version_info_data *curl_info;
115 
116 /* Module management function prototypes */
117 static int mod_init(void);
118 static int child_init(int);
119 static void destroy(void);
120 
121 /* Fixup functions to be defined later */
122 static int fixup_http_query_get(void **param, int param_no);
123 static int fixup_free_http_query_get(void **param, int param_no);
124 static int fixup_http_query_post(void **param, int param_no);
125 static int fixup_free_http_query_post(void **param, int param_no);
126 static int fixup_http_query_post_hdr(void **param, int param_no);
127 static int fixup_free_http_query_post_hdr(void **param, int param_no);
128 
129 static int fixup_curl_connect(void **param, int param_no);
130 static int fixup_free_curl_connect(void **param, int param_no);
131 static int fixup_curl_connect_post(void **param, int param_no);
132 static int fixup_curl_connect_post_raw(void **param, int param_no);
133 static int fixup_free_curl_connect_post(void **param, int param_no);
134 static int fixup_free_curl_connect_post_raw(void **param, int param_no);
135 static int w_curl_connect_post(struct sip_msg *_m, char *_con, char *_url,
136 		char *_result, char *_ctype, char *_data);
137 static int w_curl_connect_post_raw(struct sip_msg *_m, char *_con, char *_url,
138 		char *_result, char *_ctype, char *_data);
139 
140 static int fixup_curl_get_redirect(void **param, int param_no);
141 static int fixup_free_curl_get_redirect(void **param, int param_no);
142 static int w_curl_get_redirect(struct sip_msg *_m, char *_con, char *_result);
143 
144 /* Wrappers for http_query to be defined later */
145 static int w_http_query(struct sip_msg *_m, char *_url, char *_result);
146 static int w_http_query_post(
147 		struct sip_msg *_m, char *_url, char *_post, char *_result);
148 static int w_http_query_post_hdr(struct sip_msg *_m, char *_url, char *_post,
149 		char *_hdrs, char *_result);
150 static int w_curl_connect(
151 		struct sip_msg *_m, char *_con, char *_url, char *_result);
152 
153 /* forward function */
154 static int curl_con_param(modparam_t type, void *val);
155 static int pv_parse_curlerror(pv_spec_p sp, str *in);
156 static int pv_get_curlerror(
157 		struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
158 
159 /* clang-format off */
160 /* Exported functions */
161 static cmd_export_t cmds[] = {
162 	{"http_client_query", (cmd_function)w_http_query, 2, fixup_http_query_get,
163 	 	fixup_free_http_query_get,
164 		REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
165 	{"http_client_query", (cmd_function)w_http_query_post, 3, fixup_http_query_post,
166 		fixup_free_http_query_post,
167 		REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
168 	{"http_client_query", (cmd_function)w_http_query_post_hdr, 4, fixup_http_query_post_hdr,
169 		fixup_free_http_query_post_hdr,
170 		REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
171 	{"http_connect", (cmd_function)w_curl_connect, 3, fixup_curl_connect,
172 		fixup_free_curl_connect,
173 		REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
174 	{"http_connect", (cmd_function)w_curl_connect_post, 5, fixup_curl_connect_post,
175 		fixup_free_curl_connect_post,
176 		REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
177 	{"http_connect_raw", (cmd_function)w_curl_connect_post_raw, 5, fixup_curl_connect_post_raw,
178 		fixup_free_curl_connect_post_raw,
179 		REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
180 	{"http_get_redirect", (cmd_function)w_curl_get_redirect, 2, fixup_curl_get_redirect,
181 		fixup_free_curl_get_redirect,
182 		REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
183 	{"bind_http_client",  (cmd_function)bind_httpc_api,  0, 0, 0, 0},
184 	{0,0,0,0,0,0}
185 };
186 
187 
188 /* Exported parameters */
189 static param_export_t params[] = {
190 	{"connection_timeout", PARAM_INT, &default_connection_timeout},
191 	{"cacert", PARAM_STRING,  &default_tls_cacert },
192 	{"client_cert", PARAM_STR, &default_tls_clientcert },
193 	{"client_key", PARAM_STR, &default_tls_clientkey },
194 	{"cipher_suites", PARAM_STR, &default_cipher_suite_list },
195 	{"tlsversion", PARAM_INT, &default_tls_version },
196 	{"verify_peer", PARAM_INT, &default_tls_verify_peer },
197 	{"verify_host", PARAM_INT, &default_tls_verify_host },
198 	{"httpproxyport", PARAM_INT, &default_http_proxy_port },
199 	{"httpproxy", PARAM_STR, &default_http_proxy},
200 	{"httpredirect", PARAM_INT, &default_http_follow_redirect },
201 	{"useragent", PARAM_STR,  &default_useragent },
202 	{"maxdatasize", PARAM_INT,  &default_maxdatasize },
203 	{"config_file", PARAM_STR,  &http_client_config_file },
204 	{"httpcon",  PARAM_STRING|USE_FUNC_PARAM, (void*)curl_con_param},
205 	{"authmethod", PARAM_INT, &default_authmethod },
206 	{"keep_connections", PARAM_INT, &default_keep_connections },
207 	{"query_result", PARAM_INT, &default_query_result },
208 	{"query_maxdatasize", PARAM_INT, &default_query_maxdatasize },
209 	{"netinterface", PARAM_STRING,  &default_netinterface },
210 	{0, 0, 0}
211 };
212 
213 
214 /*!
215  * \brief Exported Pseudo variables
216  */
217 static pv_export_t mod_pvs[] = {
218 	{{"curlerror", (sizeof("curlerror")-1)}, /* Curl error codes */
219 		PVT_OTHER, pv_get_curlerror, 0, pv_parse_curlerror, 0, 0, 0},
220 
221 	{{0, 0}, 0, 0, 0, 0, 0, 0, 0}
222 };
223 
224 /* Module interface */
225 struct module_exports exports = {
226 	"http_client",		/* module name */
227 	DEFAULT_DLFLAGS,	/* dlopen flags */
228 	cmds,				/* exported functions */
229 	params,				/* exported parameters */
230 	0,					/* RPC method exports */
231 	mod_pvs,					/* exported pseudo-variables */
232 	0,					/* response handling function */
233 	mod_init,			/* module initialization function */
234 	child_init,			/* per-child init function */
235 	destroy				/* module destroy function */
236 };
237 /* clang-format on */
238 
239 counter_handle_t connections; /* Number of connection definitions */
240 counter_handle_t connok;	  /* Successful Connection attempts */
241 counter_handle_t connfail;	/* Failed Connection attempts */
242 
243 
init_shmlock(void)244 static int init_shmlock(void)
245 {
246 	return 0;
247 }
248 
249 
destroy_shmlock(void)250 static void destroy_shmlock(void)
251 {
252 	;
253 }
254 
255 /* Init counters */
curl_counter_init()256 static void curl_counter_init()
257 {
258 	counter_register(&connections, "httpclient", "connections", 0, 0, 0,
259 			"Counter of connection definitions (httpcon)", 0);
260 	counter_register(&connok, "httpclient", "connok", 0, 0, 0,
261 			"Counter of successful connections (200 OK)", 0);
262 	counter_register(&connfail, "httpclient", "connfail", 0, 0, 0,
263 			"Counter of failed connections (not 200 OK)", 0);
264 }
265 
266 
267 /* Module initialization function */
mod_init(void)268 static int mod_init(void)
269 {
270 
271 	LM_DBG("init curl module\n");
272 
273 	/* Initialize curl */
274 	if(curl_global_init(CURL_GLOBAL_ALL)) {
275 		LM_ERR("curl_global_init failed\n");
276 		return -1;
277 	}
278 	curl_info = curl_version_info(CURLVERSION_NOW);
279 
280 	if(curl_init_rpc() < 0) {
281 		LM_ERR("failed to register RPC commands\n");
282 		return -1;
283 	}
284 
285 	if(init_shmlock() != 0) {
286 		LM_CRIT("cannot initialize shared memory lock.\n");
287 		return -1;
288 	}
289 
290 	curl_counter_init();
291 	counter_add(connections, curl_connection_count());
292 
293 	if(default_tls_version >= CURL_SSLVERSION_LAST) {
294 		LM_WARN("tlsversion %d unsupported value. Using libcurl default\n",
295 				default_tls_version);
296 		default_tls_version = CURL_SSLVERSION_DEFAULT;
297 	}
298 	if(http_client_config_file.s != NULL) {
299 		if(http_client_load_config(&http_client_config_file) < 0) {
300 			LM_ERR("Failed to load http_client connections from [%.*s]\n",
301 					http_client_config_file.len, http_client_config_file.s);
302 			return -1;
303 		}
304 	}
305 
306 	if(default_connection_timeout == 0) {
307 		LM_ERR("CURL connection timeout set to zero. Using default 4 secs\n");
308 		default_connection_timeout = 4;
309 	}
310 	if(default_http_proxy_port == 0) {
311 		LM_INFO("HTTP proxy port set to 0. Disabling HTTP proxy\n");
312 	}
313 
314 	LM_DBG("**** init http_client module done. Curl version: %s SSL %s\n",
315 			curl_info->version, curl_info->ssl_version);
316 	LM_DBG("**** init http_client: Number of connection objects: %d \n",
317 			curl_connection_count());
318 	LM_DBG("**** init http_client: User Agent: %.*s \n", default_useragent.len,
319 			default_useragent.s);
320 	LM_DBG("**** init http_client: HTTPredirect: %d \n",
321 			default_http_follow_redirect);
322 	LM_DBG("**** init http_client: Client Cert: %.*s Key %.*s\n",
323 			default_tls_clientcert.len, default_tls_clientcert.s,
324 			default_tls_clientkey.len, default_tls_clientkey.s);
325 	LM_DBG("**** init http_client: CA Cert: %s \n", default_tls_cacert);
326 	LM_DBG("**** init http_client: Cipher Suites: %.*s \n",
327 			default_cipher_suite_list.len, default_cipher_suite_list.s);
328 	LM_DBG("**** init http_client: SSL Version: %d \n", default_tls_version);
329 	LM_DBG("**** init http_client: verifypeer: %d verifyhost: %d\n",
330 			default_tls_verify_peer, default_tls_verify_host);
331 	LM_DBG("**** init http_client: HTTP Proxy: %.*s Port %d\n",
332 			default_http_proxy.len, default_http_proxy.s,
333 			default_http_proxy_port);
334 	LM_DBG("**** init http_client: Auth method: %d \n", default_authmethod);
335 	LM_DBG("**** init http_client: Keep Connections open: %d \n",
336 			default_keep_connections);
337 
338 	LM_DBG("**** Extra: Curl supports %s %s %s \n",
339 			(curl_info->features & CURL_VERSION_SSL ? "TLS" : ""),
340 			(curl_info->features & CURL_VERSION_IPV6 ? "IPv6" : ""),
341 			(curl_info->features & CURL_VERSION_IDN ? "IDN" : ""));
342 	return 0;
343 }
344 
345 /*! Returns TRUE if curl supports TLS */
curl_support_tls()346 int curl_support_tls()
347 {
348 	return (curl_info->features & CURL_VERSION_SSL);
349 }
350 
351 /*! Returns TRUE if curl supports IPv6 */
curl_support_ipv6()352 int curl_support_ipv6()
353 {
354 	return (curl_info->features & CURL_VERSION_IPV6);
355 }
356 
357 
358 /* Child initialization function */
child_init(int rank)359 static int child_init(int rank)
360 {
361 	int i = my_pid();
362 
363 	if(rank == PROC_INIT || rank == PROC_MAIN || rank == PROC_TCP_MAIN) {
364 		return 0; /* do nothing for the main process */
365 	}
366 	LM_DBG("*** http_client module initializing process %d\n", i);
367 
368 	return 0;
369 }
370 
371 
destroy(void)372 static void destroy(void)
373 {
374 	/* Cleanup curl */
375 	curl_global_cleanup();
376 	destroy_shmlock();
377 }
378 
379 
380 /**
381  * parse httpcon module parameter
382  */
curl_con_param(modparam_t type,void * val)383 int curl_con_param(modparam_t type, void *val)
384 {
385 	if(val == NULL) {
386 		goto error;
387 	}
388 
389 	LM_DBG("**** HTTP_CLIENT got modparam httpcon \n");
390 	return curl_parse_param((char *)val);
391 error:
392 	return -1;
393 }
394 
395 /* Fixup functions */
396 
397 /*
398  * Fix http_query params: url (string that may contain pvars) and
399  * result (writable pvar).
400  */
fixup_http_query_get(void ** param,int param_no)401 static int fixup_http_query_get(void **param, int param_no)
402 {
403 	if(param_no == 1) {
404 		return fixup_spve_null(param, 1);
405 	}
406 
407 	if(param_no == 2) {
408 		if(fixup_pvar_null(param, 1) != 0) {
409 			LM_ERR("http_query: failed to fixup result pvar\n");
410 			return -1;
411 		}
412 		if(((pv_spec_t *)(*param))->setf == NULL) {
413 			LM_ERR("http_query: result pvar is not writeble\n");
414 			return -1;
415 		}
416 		return 0;
417 	}
418 
419 	LM_ERR("invalid parameter number <%d>\n", param_no);
420 	return -1;
421 }
422 
423 /*
424  * Free http_query params.
425  */
fixup_free_http_query_get(void ** param,int param_no)426 static int fixup_free_http_query_get(void **param, int param_no)
427 {
428 	if(param_no == 1) {
429 		return fixup_free_spve_null(param, 1);
430 	}
431 
432 	if(param_no == 2) {
433 		return fixup_free_pvar_null(param, 1);
434 	}
435 
436 	LM_ERR("http_query: invalid parameter number <%d>\n", param_no);
437 	return -1;
438 }
439 
440 
441 /*
442  * Fix curl_connect params: connection(string/pvar) url (string that may contain pvars) and
443  * result (writable pvar).
444  */
fixup_curl_connect(void ** param,int param_no)445 static int fixup_curl_connect(void **param, int param_no)
446 {
447 
448 	if(param_no == 1) {
449 		/* We want char * strings */
450 		return 0;
451 	}
452 	/* URL and data may contain pvar */
453 	if(param_no == 2) {
454 		return fixup_spve_null(param, 1);
455 	}
456 	if(param_no == 3) {
457 		if(fixup_pvar_null(param, 1) != 0) {
458 			LM_ERR("failed to fixup result pvar\n");
459 			return -1;
460 		}
461 		if(((pv_spec_t *)(*param))->setf == NULL) {
462 			LM_ERR("result pvar is not writeble\n");
463 			return -1;
464 		}
465 		return 0;
466 	}
467 
468 	LM_ERR("invalid parameter number <%d>\n", param_no);
469 	return -1;
470 }
471 
472 /*
473  * Fix curl_connect params when posting (5 parameters):
474  *	connection (string/pvar), url (string with pvars), content-type,
475  *      data (string/pvar, pvar)
476  */
fixup_curl_connect_post(void ** param,int param_no)477 static int fixup_curl_connect_post(void **param, int param_no)
478 {
479 
480 	if(param_no == 1 || param_no == 3) {
481 		/* We want char * strings */
482 		return 0;
483 	}
484 	/* URL and data may contain pvar */
485 	if(param_no == 2 || param_no == 4) {
486 		return fixup_spve_null(param, 1);
487 	}
488 	if(param_no == 5) {
489 		if(fixup_pvar_null(param, 1) != 0) {
490 			LM_ERR("failed to fixup result pseudo variable\n");
491 			return -1;
492 		}
493 		if(((pv_spec_t *)(*param))->setf == NULL) {
494 			LM_ERR("result pvar is not writeable\n");
495 			return -1;
496 		}
497 		return 0;
498 	}
499 
500 	LM_ERR("invalid parameter number <%d>\n", param_no);
501 	return -1;
502 }
503 
504 /*
505  * Fix curl_connect params when posting (5 parameters):
506  *	connection (string/pvar), url (string with pvars), content-type,
507  *      data (string(with no pvar parsing), pvar)
508  */
fixup_curl_connect_post_raw(void ** param,int param_no)509 static int fixup_curl_connect_post_raw(void **param, int param_no)
510 {
511 
512 	if(param_no == 1 || param_no == 3 || param_no == 4) {
513 		/* We want char * strings */
514 		return 0;
515 	}
516 	/* URL and data may contain pvar */
517 	if(param_no == 2) {
518 		return fixup_spve_null(param, 1);
519 	}
520 	if(param_no == 5) {
521 		if(fixup_pvar_null(param, 1) != 0) {
522 			LM_ERR("failed to fixup result pseudo variable\n");
523 			return -1;
524 		}
525 		if(((pv_spec_t *)(*param))->setf == NULL) {
526 			LM_ERR("result pvar is not writeable\n");
527 			return -1;
528 		}
529 		return 0;
530 	}
531 
532 	LM_ERR("invalid parameter number <%d>\n", param_no);
533 	return -1;
534 }
535 
536 /*
537  * Free curl_connect params.
538  */
fixup_free_curl_connect_post(void ** param,int param_no)539 static int fixup_free_curl_connect_post(void **param, int param_no)
540 {
541 	if(param_no == 1 || param_no == 3) {
542 		/* Char strings don't need freeing */
543 		return 0;
544 	}
545 	if(param_no == 2 || param_no == 4) {
546 		return fixup_free_spve_null(param, 1);
547 	}
548 
549 	if(param_no == 5) {
550 		return fixup_free_pvar_null(param, 1);
551 	}
552 
553 	LM_ERR("invalid parameter number <%d>\n", param_no);
554 	return -1;
555 }
556 
557 /*
558  * Free curl_connect params.
559  */
fixup_free_curl_connect_post_raw(void ** param,int param_no)560 static int fixup_free_curl_connect_post_raw(void **param, int param_no)
561 {
562 	if(param_no == 1 || param_no == 3 || param_no == 4) {
563 		/* Char strings don't need freeing */
564 		return 0;
565 	}
566 	if(param_no == 2) {
567 		return fixup_free_spve_null(param, 1);
568 	}
569 
570 	if(param_no == 5) {
571 		return fixup_free_pvar_null(param, 1);
572 	}
573 
574 	LM_ERR("invalid parameter number <%d>\n", param_no);
575 	return -1;
576 }
577 
578 /*
579  * Free curl_connect params.
580  */
fixup_free_curl_connect(void ** param,int param_no)581 static int fixup_free_curl_connect(void **param, int param_no)
582 {
583 	if(param_no == 1) {
584 		/* Char strings don't need freeing */
585 		return 0;
586 	}
587 	if(param_no == 2) {
588 		return fixup_free_spve_null(param, 1);
589 	}
590 
591 	if(param_no == 3) {
592 		return fixup_free_pvar_null(param, 1);
593 	}
594 
595 	LM_ERR("invalid parameter number <%d>\n", param_no);
596 	return -1;
597 }
598 
599 /*
600  * Wrapper for Curl_connect (GET)
601  */
ki_curl_connect_helper(sip_msg_t * _m,str * con,str * url,pv_spec_t * dst)602 static int ki_curl_connect_helper(sip_msg_t *_m, str *con, str *url,
603 		pv_spec_t *dst)
604 {
605 	str result = {NULL, 0};
606 	pv_value_t val;
607 	int ret = 0;
608 
609 	ret = curl_con_query_url(_m, con, url, &result, NULL, NULL);
610 
611 	val.rs = result;
612 	val.flags = PV_VAL_STR;
613 	if(dst->setf) {
614 		dst->setf(_m, &dst->pvp, (int)EQ_T, &val);
615 	} else {
616 		LM_WARN("target pv is not writable\n");
617 	}
618 
619 	if(result.s != NULL)
620 		pkg_free(result.s);
621 
622 	return (ret == 0) ? -1 : ret;
623 }
624 
625 /*
626  * Kemi wrapper for Curl_connect (GET)
627  */
ki_curl_connect(sip_msg_t * _m,str * con,str * url,str * dpv)628 static int ki_curl_connect(sip_msg_t *_m, str *con, str *url, str *dpv)
629 {
630 	pv_spec_t *dst;
631 
632 	dst = pv_cache_get(dpv);
633 	if(dst==NULL) {
634 		LM_ERR("failed to get pv spec for: %.*s\n", dpv->len, dpv->s);
635 		return -1;
636 	}
637 	if(dst->setf==NULL) {
638 		LM_ERR("target pv is not writable: %.*s\n", dpv->len, dpv->s);
639 		return -1;
640 	}
641 
642 	return ki_curl_connect_helper(_m, con, url, dst);
643 }
644 
645 /*
646  * Cfg wrapper for Curl_connect (GET)
647  */
w_curl_connect(sip_msg_t * _m,char * _con,char * _url,char * _result)648 static int w_curl_connect(sip_msg_t *_m, char *_con, char *_url, char *_result)
649 {
650 	str con = {NULL, 0};
651 	str url = {NULL, 0};
652 	pv_spec_t *dst;
653 
654 	if(_con == NULL || _url == NULL || _result == NULL) {
655 		LM_ERR("http_connect: Invalid parameter\n");
656 		return -1;
657 	}
658 	con.s = _con;
659 	con.len = strlen(con.s);
660 	if(get_str_fparam(&url, _m, (gparam_p)_url) != 0) {
661 		LM_ERR("http_connect: url has no value\n");
662 		return -1;
663 	}
664 
665 	LM_DBG("**** HTTP_CONNECT Connection %s URL %s Result var %s\n", _con, _url,
666 			_result);
667 	dst = (pv_spec_t *)_result;
668 
669 	return ki_curl_connect_helper(_m, &con, &url, dst);
670 }
671 
672 /*
673  * Wrapper for Curl_connect (POST)
674  */
ki_curl_connect_post_helper(sip_msg_t * _m,str * con,str * url,str * ctype,str * data,pv_spec_t * dst)675 static int ki_curl_connect_post_helper(sip_msg_t *_m, str *con, str *url,
676 		str *ctype, str *data, pv_spec_t *dst)
677 {
678 	str result = {NULL, 0};
679 	pv_value_t val;
680 	int ret = 0;
681 
682 	ret = curl_con_query_url(_m, con, url, &result, ctype->s, data);
683 
684 	val.rs = result;
685 	val.flags = PV_VAL_STR;
686 	if(dst->setf) {
687 		dst->setf(_m, &dst->pvp, (int)EQ_T, &val);
688 	} else {
689 		LM_WARN("target pv is not writtable\n");
690 	}
691 
692 	if(result.s != NULL)
693 		pkg_free(result.s);
694 
695 	return (ret == 0) ? -1 : ret;
696 }
697 
698 /*
699  * Kemi wrapper for Curl_connect (POST)
700  */
ki_curl_connect_post(sip_msg_t * _m,str * con,str * url,str * ctype,str * data,str * dpv)701 static int ki_curl_connect_post(sip_msg_t *_m, str *con, str *url,
702 		str *ctype, str *data, str *dpv)
703 {
704 	pv_spec_t *dst;
705 
706 	dst = pv_cache_get(dpv);
707 	if(dst==NULL) {
708 		LM_ERR("failed to get pv spec for: %.*s\n", dpv->len, dpv->s);
709 		return -1;
710 	}
711 	if(dst->setf==NULL) {
712 		LM_ERR("target pv is not writable: %.*s\n", dpv->len, dpv->s);
713 		return -1;
714 	}
715 
716 	return ki_curl_connect_post_helper(_m, con, url, ctype, data, dst);
717 }
718 
719 /*
720  * Wrapper for Curl_connect (POST) raw data (no pvar parsing inside the data)
721  */
w_curl_connect_post_raw(struct sip_msg * _m,char * _con,char * _url,char * _ctype,char * _data,char * _result)722 static int w_curl_connect_post_raw(struct sip_msg *_m, char *_con, char *_url,
723 		char *_ctype, char *_data, char *_result)
724 {
725 	str con = {NULL, 0};
726 	str url = {NULL, 0};
727 	str ctype = {NULL, 0};
728 	str data = {NULL, 0};
729 	pv_spec_t *dst;
730 
731 	if(_con == NULL || _url == NULL || _ctype==NULL || _data == NULL
732 			|| _result == NULL) {
733 		LM_ERR("http_connect: Invalid parameters\n");
734 		return -1;
735 	}
736 	con.s = _con;
737 	con.len = strlen(con.s);
738 
739 	if(get_str_fparam(&url, _m, (gparam_p)_url) != 0) {
740 		LM_ERR("http_connect: URL has no value\n");
741 		return -1;
742 	}
743 
744 	ctype.s = _ctype;
745 	ctype.len = strlen(ctype.s);
746 
747 	data.s = _data;
748 	data.len = strlen(data.s);
749 
750 	LM_DBG("**** HTTP_CONNECT: Connection %s URL %s Result var %s\n", _con,
751 			_url, _result);
752 	dst = (pv_spec_t *)_result;
753 
754 	return ki_curl_connect_post_helper(_m, &con, &url, &ctype, &data, dst);
755 }
756 
757 /*
758  * Wrapper for Curl_connect (POST)
759  */
w_curl_connect_post(struct sip_msg * _m,char * _con,char * _url,char * _ctype,char * _data,char * _result)760 static int w_curl_connect_post(struct sip_msg *_m, char *_con, char *_url,
761 		char *_ctype, char *_data, char *_result)
762 {
763 	str con = {NULL, 0};
764 	str url = {NULL, 0};
765 	str ctype = {NULL, 0};
766 	str data = {NULL, 0};
767 	pv_spec_t *dst;
768 
769 	if(_con == NULL || _url == NULL || _ctype==NULL || _data == NULL
770 			|| _result == NULL) {
771 		LM_ERR("http_connect: Invalid parameters\n");
772 		return -1;
773 	}
774 	con.s = _con;
775 	con.len = strlen(con.s);
776 
777 	if(get_str_fparam(&url, _m, (gparam_p)_url) != 0) {
778 		LM_ERR("http_connect: URL has no value\n");
779 		return -1;
780 	}
781 
782 	ctype.s = _ctype;
783 	ctype.len = strlen(ctype.s);
784 
785 	if(get_str_fparam(&data, _m, (gparam_p)_data) != 0) {
786 		LM_ERR("http_connect: No post data given\n");
787 		return -1;
788 	}
789 
790 	LM_DBG("**** HTTP_CONNECT: Connection %s URL %s Result var %s\n", _con,
791 			_url, _result);
792 	dst = (pv_spec_t *)_result;
793 
794 	return ki_curl_connect_post_helper(_m, &con, &url, &ctype, &data, dst);
795 }
796 
797 /*!
798  * Fix http_query params: url (string that may contain pvars) and
799  * result (writable pvar).
800  */
fixup_http_query_post(void ** param,int param_no)801 static int fixup_http_query_post(void **param, int param_no)
802 {
803 	if((param_no == 1) || (param_no == 2)) {
804 		return fixup_spve_null(param, 1);
805 	}
806 
807 	if(param_no == 3) {
808 		if(fixup_pvar_null(param, 1) != 0) {
809 			LM_ERR("failed to fixup result pvar\n");
810 			return -1;
811 		}
812 		if(((pv_spec_t *)(*param))->setf == NULL) {
813 			LM_ERR("result pvar is not writeble\n");
814 			return -1;
815 		}
816 		return 0;
817 	}
818 
819 	LM_ERR("invalid parameter number <%d>\n", param_no);
820 	return -1;
821 }
822 
823 /*!
824  * Free http_query params.
825  */
fixup_free_http_query_post(void ** param,int param_no)826 static int fixup_free_http_query_post(void **param, int param_no)
827 {
828 	if((param_no == 1) || (param_no == 2)) {
829 		return fixup_free_spve_null(param, 1);
830 	}
831 
832 	if(param_no == 3) {
833 		return fixup_free_pvar_null(param, 1);
834 	}
835 
836 	LM_ERR("invalid parameter number <%d>\n", param_no);
837 	return -1;
838 }
839 
840 /*
841  * Fix http_query params: url (string that may contain pvars) and
842  * result (writable pvar).
843  */
fixup_http_query_post_hdr(void ** param,int param_no)844 static int fixup_http_query_post_hdr(void **param, int param_no)
845 {
846 	if((param_no >= 1) && (param_no <= 3)) {
847 		return fixup_spve_null(param, 1);
848 	}
849 
850 	if(param_no == 4) {
851 		if(fixup_pvar_null(param, 1) != 0) {
852 			LM_ERR("failed to fixup result pvar\n");
853 			return -1;
854 		}
855 		if(((pv_spec_t *)(*param))->setf == NULL) {
856 			LM_ERR("result pvar is not writeble\n");
857 			return -1;
858 		}
859 		return 0;
860 	}
861 
862 	LM_ERR("invalid parameter number <%d>\n", param_no);
863 	return -1;
864 }
865 
866 /*
867  * Free http_query params.
868  */
fixup_free_http_query_post_hdr(void ** param,int param_no)869 static int fixup_free_http_query_post_hdr(void **param, int param_no)
870 {
871 	if((param_no >= 1) && (param_no <= 3)) {
872 		return fixup_free_spve_null(param, 1);
873 	}
874 
875 	if(param_no == 4) {
876 		return fixup_free_pvar_null(param, 1);
877 	}
878 
879 	LM_ERR("invalid parameter number <%d>\n", param_no);
880 	return -1;
881 }
882 
883 /*!
884  * helper for HTTP-Query function
885  */
ki_http_query_helper(sip_msg_t * _m,str * url,str * post,str * hdrs,pv_spec_t * dst)886 static int ki_http_query_helper(sip_msg_t *_m, str *url, str *post, str *hdrs,
887 		pv_spec_t *dst)
888 {
889 	int ret = 0;
890 	str result = {NULL, 0};
891 	pv_value_t val;
892 
893 	if(url==NULL || url->s==NULL) {
894 		LM_ERR("invalid url parameter\n");
895 		return -1;
896 	}
897 	ret = http_client_query(_m, url->s, &result,
898 			(post && post->s && post->len>0)?post->s:NULL,
899 			(hdrs && hdrs->s && hdrs->len>0)?hdrs->s:NULL);
900 
901 	val.rs = result;
902 	val.flags = PV_VAL_STR;
903 	if(dst->setf) {
904 		dst->setf(_m, &dst->pvp, (int)EQ_T, &val);
905 	} else {
906 		LM_WARN("target pv is not writable\n");
907 	}
908 
909 	if(result.s != NULL)
910 		pkg_free(result.s);
911 
912 	return (ret == 0) ? -1 : ret;
913 }
914 
ki_http_query_post_hdrs(sip_msg_t * _m,str * url,str * post,str * hdrs,str * dpv)915 static int ki_http_query_post_hdrs(sip_msg_t *_m, str *url, str *post, str *hdrs,
916 		str *dpv)
917 {
918 	pv_spec_t *dst;
919 
920 	dst = pv_cache_get(dpv);
921 	if(dst==NULL) {
922 		LM_ERR("failed to get pv spec for: %.*s\n", dpv->len, dpv->s);
923 		return -1;
924 	}
925 	if(dst->setf==NULL) {
926 		LM_ERR("target pv is not writable: %.*s\n", dpv->len, dpv->s);
927 		return -1;
928 	}
929 
930 	return ki_http_query_helper(_m, url, post, hdrs, dst);
931 }
932 
ki_http_query_post(sip_msg_t * _m,str * url,str * post,str * dpv)933 static int ki_http_query_post(sip_msg_t *_m, str *url, str *post, str *dpv)
934 {
935 	return ki_http_query_post_hdrs(_m, url, post, NULL, dpv);
936 }
937 
ki_http_query(sip_msg_t * _m,str * url,str * dpv)938 static int ki_http_query(sip_msg_t *_m, str *url, str *dpv)
939 {
940 	return ki_http_query_post_hdrs(_m, url, NULL, NULL, dpv);
941 }
942 
943 /*!
944  * Wrapper for HTTP-Query function for cfg script
945  */
w_http_query_script(sip_msg_t * _m,char * _url,char * _post,char * _hdrs,char * _result)946 static int w_http_query_script(sip_msg_t *_m, char *_url, char *_post,
947 		char *_hdrs, char *_result)
948 {
949 	str url = {NULL, 0};
950 	str post = {NULL, 0};
951 	str hdrs = {NULL, 0};
952 	pv_spec_t *dst;
953 
954 	if(get_str_fparam(&url, _m, (gparam_p)_url) != 0 || url.len <= 0) {
955 		LM_ERR("URL has no value\n");
956 		return -1;
957 	}
958 	if(_post && get_str_fparam(&post, _m, (gparam_p)_post) != 0) {
959 		LM_ERR("DATA has no value\n");
960 		return -1;
961 	} else {
962 		if(post.len == 0) {
963 			post.s = NULL;
964 		}
965 	}
966 	if(_hdrs && get_str_fparam(&hdrs, _m, (gparam_p)_hdrs) != 0) {
967 		LM_ERR("HDRS has no value\n");
968 		return -1;
969 	} else {
970 		if(hdrs.len == 0) {
971 			hdrs.s = NULL;
972 		}
973 	}
974 	dst = (pv_spec_t *)_result;
975 
976 	return ki_http_query_helper(_m, &url, &post, &hdrs, dst);
977 }
978 
979 /*!
980  * Wrapper for HTTP-Query (GET)
981  */
w_http_query(struct sip_msg * _m,char * _url,char * _result)982 static int w_http_query(struct sip_msg *_m, char *_url, char *_result)
983 {
984 	return w_http_query_script(_m, _url, NULL, NULL, _result);
985 }
986 
987 /*!
988  * Wrapper for HTTP-Query (POST-Variant)
989  */
w_http_query_post(struct sip_msg * _m,char * _url,char * _post,char * _result)990 static int w_http_query_post(
991 		struct sip_msg *_m, char *_url, char *_post, char *_result)
992 {
993 	return w_http_query_script(_m, _url, _post, NULL, _result);
994 }
995 
996 /*!
997  * Wrapper for HTTP-Query (HDRS-Variant)
998  */
w_http_query_post_hdr(struct sip_msg * _m,char * _url,char * _post,char * _hdrs,char * _result)999 static int w_http_query_post_hdr(
1000 		struct sip_msg *_m, char *_url, char *_post, char *_hdrs, char *_result)
1001 {
1002 	return w_http_query_script(_m, _url, _post, _hdrs, _result);
1003 }
1004 
1005 /*!
1006  * Parse arguments to  pv $curlerror
1007  */
pv_parse_curlerror(pv_spec_p sp,str * in)1008 static int pv_parse_curlerror(pv_spec_p sp, str *in)
1009 {
1010 	int cerr = 0;
1011 	if(sp == NULL || in == NULL || in->len <= 0)
1012 		return -1;
1013 
1014 	cerr = atoi(in->s);
1015 	LM_DBG(" =====> CURL ERROR %d \n", cerr);
1016 	sp->pvp.pvn.u.isname.name.n = cerr;
1017 
1018 	sp->pvp.pvn.type = PV_NAME_INTSTR;
1019 	sp->pvp.pvn.u.isname.type = 0;
1020 
1021 	return 0;
1022 }
1023 
1024 /*
1025  * PV - return curl error explanation as string
1026  */
pv_get_curlerror(struct sip_msg * msg,pv_param_t * param,pv_value_t * res)1027 static int pv_get_curlerror(
1028 		struct sip_msg *msg, pv_param_t *param, pv_value_t *res)
1029 {
1030 	str curlerr;
1031 	char *err = NULL;
1032 
1033 	if(param == NULL) {
1034 		return -1;
1035 	}
1036 
1037 	/* cURL error codes does not collide with HTTP codes */
1038 	if(param->pvn.u.isname.name.n < 0 || param->pvn.u.isname.name.n > 999) {
1039 		err = "Bad CURL error code";
1040 	}
1041 	if(param->pvn.u.isname.name.n > 99) {
1042 		err = "HTTP result code";
1043 	}
1044 	if(err == NULL) {
1045 		err = (char *)curl_easy_strerror(param->pvn.u.isname.name.n);
1046 	}
1047 	curlerr.s = err;
1048 	curlerr.len = strlen(err);
1049 
1050 	return pv_get_strval(msg, param, res, &curlerr);
1051 }
1052 
1053 
1054 /*
1055  * Fix curl_get_redirect params: connection(string/pvar) url (string that may contain pvars) and
1056  * result (writable pvar).
1057  */
fixup_curl_get_redirect(void ** param,int param_no)1058 static int fixup_curl_get_redirect(void **param, int param_no)
1059 {
1060 	if(param_no == 1) { /* Connection name */
1061 		/* We want char * strings */
1062 		return 0;
1063 	}
1064 	if(param_no == 2) { /* PVAR to store result in */
1065 		if(fixup_pvar_null(param, 1) != 0) {
1066 			LM_ERR("failed to fixup result pseudo variable\n");
1067 			return -1;
1068 		}
1069 		if(((pv_spec_t *)(*param))->setf == NULL) {
1070 			LM_ERR("result pseudovariable is not writeable\n");
1071 			return -1;
1072 		}
1073 		return 0;
1074 	}
1075 
1076 	LM_ERR("invalid parameter number <%d>\n", param_no);
1077 	return -1;
1078 }
1079 
1080 /*
1081  * Free curl_get_redirect params.
1082  */
fixup_free_curl_get_redirect(void ** param,int param_no)1083 static int fixup_free_curl_get_redirect(void **param, int param_no)
1084 {
1085 	if(param_no == 1) {
1086 		/* Char strings don't need freeing */
1087 		return 0;
1088 	}
1089 	if(param_no == 2) {
1090 		return fixup_free_spve_null(param, 1);
1091 	}
1092 
1093 	LM_ERR("invalid parameter number <%d>\n", param_no);
1094 	return -1;
1095 }
1096 
1097 /*
1098  * Wrapper for Curl_redirect
1099  */
w_curl_get_redirect(struct sip_msg * _m,char * _con,char * _result)1100 static int w_curl_get_redirect(struct sip_msg *_m, char *_con, char *_result)
1101 {
1102 
1103 	str con = {NULL, 0};
1104 	str result = {NULL, 0};
1105 	pv_spec_t *dst;
1106 	pv_value_t val;
1107 	int ret = 0;
1108 
1109 	if(_con == NULL || _result == NULL) {
1110 		LM_ERR("Invalid or missing parameter\n");
1111 		return -1;
1112 	}
1113 	con.s = _con;
1114 	con.len = strlen(con.s);
1115 
1116 	LM_DBG("**** http_client get_redirect Connection %s Result var %s\n", _con,
1117 			_result);
1118 
1119 	ret = curl_get_redirect(_m, &con, &result);
1120 
1121 	val.rs = result;
1122 	val.flags = PV_VAL_STR;
1123 	dst = (pv_spec_t *)_result;
1124 	dst->setf(_m, &dst->pvp, (int)EQ_T, &val);
1125 
1126 	if(result.s != NULL)
1127 		pkg_free(result.s);
1128 
1129 	return ret;
1130 }
1131 
1132 /**
1133  *
1134  */
1135 /* clang-format off */
1136 static sr_kemi_t sr_kemi_http_client_exports[] = {
1137 	{ str_init("http_client"), str_init("query"),
1138 		SR_KEMIP_INT, ki_http_query,
1139 		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
1140 			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
1141 	},
1142 	{ str_init("http_client"), str_init("query_post"),
1143 		SR_KEMIP_INT, ki_http_query_post,
1144 		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
1145 			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
1146 	},
1147 	{ str_init("http_client"), str_init("query_post_hdrs"),
1148 		SR_KEMIP_INT, ki_http_query_post_hdrs,
1149 		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
1150 			SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE }
1151 	},
1152 	{ str_init("http_client"), str_init("curl_connect"),
1153 		SR_KEMIP_INT, ki_curl_connect,
1154 		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
1155 			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
1156 	},
1157 	{ str_init("http_client"), str_init("curl_connect_post"),
1158 		SR_KEMIP_INT, ki_curl_connect_post,
1159 		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
1160 			SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE }
1161 	},
1162 
1163 	{ {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
1164 };
1165 /* clang-format on */
1166 
mod_register(char * path,int * dlflags,void * p1,void * p2)1167 int mod_register(char *path, int *dlflags, void *p1, void *p2)
1168 {
1169 	sr_kemi_modules_add(sr_kemi_http_client_exports);
1170 	return 0;
1171 }
1172