2 * Script functions of http_client module
4 * Copyright (C) 2015 Olle E. Johansson, Edvina AB
6 * Based on functions from siputil
7 * Copyright (C) 2008 Juha Heinanen
8 * Copyright (C) 2013 Carsten Bock, ng-voice GmbH
10 * This file is part of Kamailio, a free SIP server.
12 * Kamailio is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version
17 * Kamailio is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
30 * \brief Kamailio http_client :: script functions
31 * \ingroup http_client
32 * Module: \ref http_client
36 #include "../../core/mod_fix.h"
37 #include "../../core/pvar.h"
38 #include "../../core/route_struct.h"
39 #include "../../core/ut.h"
40 #include "../../core/mem/mem.h"
41 #include "../../core/parser/msg_parser.h"
43 #include "http_client.h"
59 unsigned int authmethod;
60 unsigned int http_proxy_port;
61 unsigned int tlsversion;
62 unsigned int verify_peer;
63 unsigned int verify_host;
65 unsigned int http_follow_redirect;
67 unsigned int maxdatasize;
68 unsigned int keep_connections;
69 curl_con_pkg_t *pconn;
74 * curl write function that saves received data as zero terminated
75 * to stream. Returns the amount of data taken care of.
77 * This function may be called multiple times for larger responses,
78 * so it reallocs + concatenates the buffer as needed.
80 size_t write_function( void *ptr, size_t size, size_t nmemb, void *stream_ptr)
82 curl_res_stream_t *stream = (curl_res_stream_t *) stream_ptr;
85 if (stream->max_size == 0 || stream->curr_size < stream->max_size) {
86 char *tmp = (char *) pkg_realloc(stream->buf, stream->curr_size + (size * nmemb));
89 LM_ERR("cannot allocate memory for stream\n");
90 return CURLE_WRITE_ERROR;
94 memcpy(&stream->buf[stream->pos], (char *) ptr, (size * nmemb));
96 stream->curr_size += ((size * nmemb));
97 stream->pos += (size * nmemb);
100 LM_DBG("****** ##### CURL Max datasize exceeded: max %u current %u\n", (unsigned int) stream->max_size, (unsigned int)stream->curr_size);
108 /*! Send query to server, optionally post data.
110 static int curL_query_url(struct sip_msg* _m, const char* _url, str* _dst,
111 const curl_query_t * const params)
116 curl_res_stream_t stream;
119 double download_size = 0;
120 struct curl_slist *headerlist = NULL;
122 memset(&stream, 0, sizeof(curl_res_stream_t));
123 stream.max_size = (size_t) params->maxdatasize;
126 LM_DBG("****** ##### We have a pconn - keep_connections: %d!\n",
127 params->keep_connections);
128 params->pconn->result_content_type[0] = '\0';
129 params->pconn->redirecturl[0] = '\0';
130 if (params->pconn->curl != NULL) {
131 LM_DBG(" ****** ##### Reusing existing connection if possible\n");
132 curl = params->pconn->curl; /* Reuse existing handle */
133 curl_easy_reset(curl); /* Reset handle */
139 curl = curl_easy_init();
142 LM_ERR("Failed to initialize curl connection\n");
146 LM_DBG("****** ##### CURL URL [%s] \n", _url);
147 res = curl_easy_setopt(curl, CURLOPT_URL, _url);
149 /* Limit to HTTP and HTTPS protocols */
150 res = curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
151 res = curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
154 /* Now specify we want to POST data */
155 res |= curl_easy_setopt(curl, CURLOPT_POST, 1L);
157 if(params->contenttype) {
161 snprintf(ctype, sizeof(ctype), "Content-Type: %s", params->contenttype);
163 /* Set the content-type of the DATA */
164 headerlist = curl_slist_append(headerlist, ctype);
167 /* Tell CURL we want to upload using POST */
168 res |= curl_easy_setopt(curl, CURLOPT_POSTFIELDS, params->post);
171 /* Reset post option */
172 res |= curl_easy_setopt(curl, CURLOPT_POST, 0L);
176 headerlist = curl_slist_append(headerlist, params->hdrs);
179 res |= curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
182 if (params->maxdatasize) {
183 /* Maximum data size to download - we always download full response,
184 * but cut it off before moving to pvar */
185 LM_DBG("****** ##### CURL Max datasize %u\n", params->maxdatasize);
188 if (params->username) {
189 res |= curl_easy_setopt(curl, CURLOPT_USERNAME, params->username);
190 res |= curl_easy_setopt(curl, CURLOPT_HTTPAUTH, params->authmethod);
192 if (params->secret) {
193 res |= curl_easy_setopt(curl, CURLOPT_PASSWORD, params->secret);
196 /* Client certificate */
197 if (params->clientcert != NULL && params->clientkey != NULL) {
198 res |= curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM");
199 res |= curl_easy_setopt(curl, CURLOPT_SSLCERT, params->clientcert);
201 res |= curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
202 res |= curl_easy_setopt(curl, CURLOPT_SSLKEY, params->clientkey);
205 if (params->cacert != NULL) {
206 res |= curl_easy_setopt(curl, CURLOPT_CAINFO, params->cacert);
209 if (params->tlsversion != CURL_SSLVERSION_DEFAULT) {
210 res |= curl_easy_setopt(curl, CURLOPT_SSLVERSION,
211 (long) params->tlsversion);
214 if (params->ciphersuites != NULL) {
215 res |= curl_easy_setopt(curl, CURLOPT_SSL_CIPHER_LIST,
216 params->ciphersuites);
219 if (params->http_proxy != NULL) {
220 LM_DBG("****** ##### CURL proxy [%s] \n", params->http_proxy);
221 res |= curl_easy_setopt(curl, CURLOPT_PROXY, params->http_proxy);
223 LM_DBG("****** ##### CURL proxy NOT SET \n");
226 if (params->http_proxy_port > 0) {
227 res |= curl_easy_setopt(curl, CURLOPT_PROXYPORT,
228 params->http_proxy_port);
232 res |= curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER,
233 (long) params->verify_peer);
234 res |= curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST,
235 (long) params->verify_host?2:0);
237 res |= curl_easy_setopt(curl, CURLOPT_NOSIGNAL, (long) 1);
238 res |= curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long) params->timeout);
239 res |= curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION,
240 (long) params->http_follow_redirect);
241 if (params->http_follow_redirect) {
242 LM_DBG("Following redirects for this request! \n");
246 res |= curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_function);
247 res |= curl_easy_setopt(curl, CURLOPT_WRITEDATA, &stream);
249 if(params->useragent)
250 res |= curl_easy_setopt(curl, CURLOPT_USERAGENT, params->useragent);
252 if (res != CURLE_OK) {
254 LM_ERR("Could not set CURL options. Library error \n");
256 double totaltime, connecttime;
258 res = curl_easy_perform(curl);
260 curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &totaltime);
261 curl_easy_getinfo(curl, CURLINFO_APPCONNECT_TIME, &connecttime);
262 LM_DBG("HTTP Call performed in %f s (connect time %f) \n",
263 totaltime, connecttime);
265 params->pconn->querytime = totaltime;
266 params->pconn->connecttime = connecttime;
273 curl_slist_free_all(headerlist);
276 if (res != CURLE_OK) {
277 /* http://curl.haxx.se/libcurl/c/libcurl-errors.html */
278 if (res == CURLE_COULDNT_CONNECT) {
279 LM_WARN("failed to connect() to host\n");
280 } else if ( res == CURLE_COULDNT_RESOLVE_HOST ) {
281 LM_WARN("Couldn't resolve host\n");
282 } else if ( res == CURLE_COULDNT_RESOLVE_PROXY ) {
283 LM_WARN("Couldn't resolve http_proxy host\n");
284 } else if ( res == CURLE_UNSUPPORTED_PROTOCOL ) {
285 LM_WARN("URL Schema not supported by curl\n");
286 } else if ( res == CURLE_URL_MALFORMAT ) {
287 LM_WARN("Malformed URL used in http_client\n");
288 } else if ( res == CURLE_OUT_OF_MEMORY ) {
289 LM_WARN("Curl library out of memory\n");
290 } else if ( res == CURLE_OPERATION_TIMEDOUT ) {
291 LM_WARN("Curl library timed out on request\n");
292 } else if ( res == CURLE_SSL_CONNECT_ERROR ) {
293 LM_WARN("TLS error in curl connection\n");
294 } else if ( res == CURLE_SSL_CERTPROBLEM ) {
295 LM_WARN("TLS local certificate error\n");
296 } else if ( res == CURLE_SSL_CIPHER ) {
297 LM_WARN("TLS cipher error\n");
298 } else if ( res == CURLE_SSL_CACERT ) {
299 LM_WARN("TLS server certificate validation error (No valid CA cert)\n");
300 } else if ( res == CURLE_SSL_CACERT_BADFILE ) {
301 LM_WARN("TLS CA certificate read error \n");
302 } else if ( res == CURLE_SSL_ISSUER_ERROR ) {
303 LM_WARN("TLS issuer certificate check error \n");
304 } else if ( res == CURLE_PEER_FAILED_VERIFICATION ) {
305 LM_WARN("TLS verification error\n");
306 } else if ( res == CURLE_TOO_MANY_REDIRECTS ) {
307 LM_WARN("Too many redirects\n");
309 LM_ERR("failed to perform curl (%d)\n", res);
313 params->pconn->last_result = res;
315 if (params->pconn && params->keep_connections) {
316 params->pconn->curl = curl; /* Save connection, don't close */
318 /* Cleanup and close - bye bye and thank you for all the bytes */
319 curl_easy_cleanup(curl);
322 pkg_free(stream.buf);
324 counter_inc(connfail);
325 if (params->failovercon != NULL) {
326 LM_ERR("FATAL FAILURE: Trying failover to curl con (%s)\n",
327 params->failovercon);
333 /* HTTP_CODE CHANGED TO CURLINFO_RESPONSE_CODE in curl > 7.10.7 */
334 curl_easy_getinfo(curl, CURLINFO_HTTP_CODE, &stat);
335 if(res == CURLE_OK) {
339 /* ask for the content-type of the response */
340 res = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
341 res |= curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
344 LM_DBG("We received Content-Type: %s\n", ct);
346 strlen(ct)<sizeof(params->pconn->result_content_type)-1) {
347 strncpy(params->pconn->result_content_type, ct,
348 sizeof(params->pconn->result_content_type));
352 LM_DBG("We visited URL: %s\n", url);
354 && strlen(url)<sizeof(params->pconn->redirecturl)-1) {
355 strncpy(params->pconn->redirecturl, url ,
356 sizeof(params->pconn->redirecturl));
361 params->pconn->last_result = stat;
364 if ((stat >= 200) && (stat < 500)) {
367 curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &download_size);
368 LM_DBG(" -- curl download size: %u \n", (unsigned int)download_size);
369 datasize = download_size;
371 if (download_size > 0) {
373 if (params->oneline) {
374 /* search for line feed */
375 at = memchr(stream.buf, (char)10, download_size);
376 datasize = (double) (at - stream.buf);
377 LM_DBG(" -- curl download size cut to first line: %d \n",
381 if (params->maxdatasize
382 && ((unsigned int) download_size) > params->maxdatasize) {
383 /* Limit at maximum data size */
384 datasize = (double) params->maxdatasize;
385 LM_DBG(" -- curl download size cut to maxdatasize : %d \n",
388 /* Limit at actual downloaded data size */
389 datasize = (double) download_size;
390 LM_DBG(" -- curl download size cut to download_size : %d \n",
392 // at = stream.buf + (unsigned int) download_size;
395 /* Create a STR object */
398 /* Duplicate string to return */
399 pkg_str_dup(_dst, &rval);
400 LM_DBG("curl query result - length: %d data: [%.*s]\n", rval.len,
410 counter_inc(connfail);
412 if (params->failovercon != NULL) {
413 LM_ERR("FAILURE: Trying failover to curl con (%s)\n",
414 params->failovercon);
415 return (1000 + stat);
420 /* CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ... ); */
421 if (params->pconn && params->keep_connections) {
422 params->pconn->curl = curl; /* Save connection, don't close */
424 curl_easy_cleanup(curl);
426 if (stream.buf != NULL) {
427 pkg_free(stream.buf);
432 /*! Run a query based on a connection definition */
433 int curl_get_redirect(struct sip_msg* _m, const str *connection, str* result)
435 curl_con_t *conn = NULL;
436 curl_con_pkg_t *pconn = NULL;
441 /* Find connection if it exists */
443 LM_ERR("No cURL connection specified\n");
446 LM_DBG("******** CURL Connection %.*s\n", connection->len, connection->s);
447 conn = curl_get_connection((str*)connection);
449 LM_ERR("No cURL connection found: %.*s\n", connection->len, connection->s);
452 pconn = curl_get_pkg_connection(conn);
454 LM_ERR("No cURL connection data found: %.*s\n", connection->len, connection->s);
457 /* Create a STR object */
458 rval.s = pconn->redirecturl;
459 rval.len = strlen(pconn->redirecturl);
460 /* Duplicate string to return */
461 pkg_str_dup(result, &rval);
462 LM_DBG("curl last redirect URL: Length %d %.*s \n", rval.len, rval.len, rval.s);
468 /*! Run a query based on a connection definition */
469 int curl_con_query_url_f(struct sip_msg* _m, const str *connection, const str* url, str* result, const char *contenttype, const str* post, int failover)
471 curl_con_t *conn = NULL;
472 curl_con_pkg_t *pconn = NULL;
474 char *postdata = NULL;
475 curl_query_t query_params;
477 unsigned int maxdatasize = default_maxdatasize;
480 /* Find connection if it exists */
482 LM_ERR("No cURL connection specified\n");
485 LM_DBG("******** CURL Connection %.*s\n", connection->len, connection->s);
486 conn = curl_get_connection((str*)connection);
488 LM_ERR("No cURL connection found: %.*s\n", connection->len, connection->s);
491 pconn = curl_get_pkg_connection(conn);
493 LM_ERR("No cURL connection data found: %.*s\n", connection->len, connection->s);
497 LM_DBG("******** CURL Connection found %.*s\n", connection->len, connection->s);
498 maxdatasize = conn->maxdatasize;
501 if (url && (url->len > 0) && (url->s != NULL)) {
502 int url_len = conn->schema.len + 3 + conn->url.len + 1 + url->len + 1;
503 urlbuf = pkg_malloc(url_len);
504 if (urlbuf == NULL) {
508 snprintf(urlbuf, url_len, "%.*s://%.*s%s%.*s",
509 conn->schema.len, conn->schema.s,
510 conn->url.len, conn->url.s,
511 (url->s[0] == '/')? "" : "/",
514 int url_len = conn->schema.len + 3 + conn->url.len + 1;
515 urlbuf = pkg_malloc(url_len);
516 if (urlbuf == NULL) {
520 snprintf(urlbuf, url_len, "%.*s://%.*s",
521 conn->schema.len, conn->schema.s,
522 conn->url.len, conn->url.s);
524 LM_DBG("***** #### ***** CURL URL: %s \n", urlbuf);
526 if (post && (post->len > 0) && (post->s != NULL)) {
528 /* Allocated using pkg_memory */
529 postdata = as_asciiz((str*)post);
530 if (postdata == NULL) {
531 ERR("Curl: No memory left\n");
535 LM_DBG("***** #### ***** CURL POST data: %s Content-type %s\n", postdata, contenttype);
538 memset(&query_params, 0, sizeof(curl_query_t));
539 query_params.username = conn->username;
540 query_params.secret = conn->password;
541 query_params.authmethod = conn->authmethod;
542 query_params.contenttype = contenttype ? (char*)contenttype : "text/plain";
543 query_params.post = postdata;
544 query_params.clientcert = conn->clientcert;
545 query_params.clientkey = conn->clientkey;
546 query_params.cacert = default_tls_cacert;
547 query_params.ciphersuites = conn->ciphersuites;
548 query_params.tlsversion = conn->tlsversion;
549 query_params.verify_peer = conn->verify_peer;
550 query_params.verify_host = conn->verify_host;
551 query_params.timeout = conn->timeout;
552 query_params.http_follow_redirect = conn->http_follow_redirect;
553 query_params.keep_connections = conn->keep_connections;
554 query_params.oneline = 0;
555 query_params.maxdatasize = maxdatasize;
556 query_params.http_proxy_port = conn->http_proxy_port;
557 query_params.failovercon = conn->failover.s ? as_asciiz(&conn->failover) : NULL;
558 query_params.pconn = pconn;
559 if (conn->http_proxy) {
560 query_params.http_proxy = conn->http_proxy;
561 LM_DBG("****** ##### CURL proxy [%s] \n", query_params.http_proxy);
563 LM_DBG("**** Curl HTTP_proxy not set \n");
566 res = curL_query_url(_m, urlbuf, result, &query_params);
568 if (res > 1000 && conn->failover.s) {
569 int counter = failover + 1;
571 LM_DBG("**** No more failovers - returning failure\n");
574 /* Time for failover */
575 return curl_con_query_url_f(_m, &conn->failover, url, result, contenttype, post, counter);
578 LM_DBG("***** #### ***** CURL DONE : %s \n", urlbuf);
580 if (urlbuf != NULL) {
583 if (postdata != NULL) {
590 int curl_con_query_url(struct sip_msg* _m, const str *connection,
591 const str* url, str* result, const char *contenttype, const str* post)
593 return curl_con_query_url_f(_m, connection, url, result, contenttype, post, 0);
597 * Performs http_query and saves possible result (first body line of reply)
599 * This is the same http_query as used to be in the utils module.
601 int http_client_query(struct sip_msg* _m, char* _url, str* _dst, char* _post,
605 curl_query_t query_params;
607 memset(&query_params, 0, sizeof(curl_query_t));
608 query_params.username = NULL;
609 query_params.secret = NULL;
610 query_params.authmethod = default_authmethod;
611 query_params.contenttype = NULL;
612 query_params.hdrs = _hdrs;
613 query_params.post = _post;
614 query_params.clientcert = NULL;
615 query_params.clientkey = NULL;
616 query_params.cacert = NULL;
617 query_params.ciphersuites = NULL;
618 query_params.tlsversion = default_tls_version;
619 query_params.verify_peer = default_tls_verify_peer;
620 query_params.verify_host = default_tls_verify_host;
621 query_params.timeout = default_connection_timeout;
622 query_params.http_follow_redirect = default_http_follow_redirect;
623 query_params.oneline = 1;
624 query_params.maxdatasize = 0;
625 if(default_useragent.s!=NULL && default_useragent.len>0) {
626 query_params.useragent = default_useragent.s;
628 if(default_http_proxy.s!=NULL && default_http_proxy.len>0) {
629 query_params.http_proxy = default_http_proxy.s;
630 if(default_http_proxy_port>0) {
631 query_params.http_proxy_port = default_http_proxy_port;
635 res = curL_query_url(_m, _url, _dst, &query_params);
641 char *http_get_content_type(const str *connection)
643 curl_con_t *conn = NULL;
644 curl_con_pkg_t *pconn = NULL;
646 /* Find connection if it exists */
648 LM_ERR("No cURL connection specified\n");
651 LM_DBG("******** CURL Connection %.*s\n", connection->len, connection->s);
652 conn = curl_get_connection((str*)connection);
654 LM_ERR("No cURL connection found: %.*s\n", connection->len, connection->s);
657 pconn = curl_get_pkg_connection(conn);
659 LM_ERR("No cURL connection data found: %.*s\n", connection->len, connection->s);
663 return pconn->result_content_type;