1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 **/
19
20 #include "common.h"
21 #include "sysinfo.h"
22 #include "zbxregexp.h"
23 #include "zbxhttp.h"
24
25 #include "comms.h"
26 #include "cfg.h"
27
28 #include "http.h"
29
30 #define HTTP_SCHEME_STR "http://"
31
32 #ifndef HAVE_LIBCURL
33
34 #define ZBX_MAX_WEBPAGE_SIZE (1 * 1024 * 1024)
35
36 #else
37
38 #define HTTPS_SCHEME_STR "https://"
39
40 typedef struct
41 {
42 char *data;
43 size_t allocated;
44 size_t offset;
45 }
46 zbx_http_response_t;
47
48 #endif
49
detect_url(const char * host)50 static int detect_url(const char *host)
51 {
52 char *p;
53 int ret = FAIL;
54
55 if (NULL != strpbrk(host, "/@#?[]"))
56 return SUCCEED;
57
58 if (NULL != (p = strchr(host, ':')) && NULL == strchr(++p, ':'))
59 ret = SUCCEED;
60
61 return ret;
62 }
63
process_url(const char * host,const char * port,const char * path,char ** url,char ** error)64 static int process_url(const char *host, const char *port, const char *path, char **url, char **error)
65 {
66 char *p, *delim;
67 int scheme_found = 0;
68
69 /* port and path parameters must be empty */
70 if ((NULL != port && '\0' != *port) || (NULL != path && '\0' != *path))
71 {
72 *error = zbx_strdup(*error,
73 "Parameters \"path\" and \"port\" must be empty if URL is specified in \"host\".");
74 return FAIL;
75 }
76
77 /* allow HTTP(S) scheme only */
78 #ifdef HAVE_LIBCURL
79 if (0 == zbx_strncasecmp(host, HTTP_SCHEME_STR, ZBX_CONST_STRLEN(HTTP_SCHEME_STR)) ||
80 0 == zbx_strncasecmp(host, HTTPS_SCHEME_STR, ZBX_CONST_STRLEN(HTTPS_SCHEME_STR)))
81 #else
82 if (0 == zbx_strncasecmp(host, HTTP_SCHEME_STR, ZBX_CONST_STRLEN(HTTP_SCHEME_STR)))
83 #endif
84 {
85 scheme_found = 1;
86 }
87 else if (NULL != (p = strstr(host, "://")) && (NULL == (delim = strpbrk(host, "/?#")) || delim > p))
88 {
89 *error = zbx_dsprintf(*error, "Unsupported scheme: %.*s.", (int)(p - host), host);
90 return FAIL;
91 }
92
93 if (NULL != (p = strchr(host, '#')))
94 *url = zbx_dsprintf(*url, "%s%.*s", (0 == scheme_found ? HTTP_SCHEME_STR : ""), (int)(p - host), host);
95 else
96 *url = zbx_dsprintf(*url, "%s%s", (0 == scheme_found ? HTTP_SCHEME_STR : ""), host);
97
98 return SUCCEED;
99 }
100
check_common_params(const char * host,const char * path,char ** error)101 static int check_common_params(const char *host, const char *path, char **error)
102 {
103 const char *wrong_chr, URI_PROHIBIT_CHARS[] = {0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,\
104 0xF,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x7F,0};
105
106 if (NULL == host || '\0' == *host)
107 {
108 *error = zbx_strdup(*error, "Invalid first parameter.");
109 return FAIL;
110 }
111
112 if (NULL != (wrong_chr = strpbrk(host, URI_PROHIBIT_CHARS)))
113 {
114 *error = zbx_dsprintf(NULL, "Incorrect hostname expression. Check hostname part after: %.*s.",
115 (int)(wrong_chr - host), host);
116 return FAIL;
117 }
118
119 if (NULL != path && NULL != (wrong_chr = strpbrk(path, URI_PROHIBIT_CHARS)))
120 {
121 *error = zbx_dsprintf(NULL, "Incorrect path expression. Check path part after: %.*s.",
122 (int)(wrong_chr - path), path);
123 return FAIL;
124 }
125
126 return SUCCEED;
127 }
128
129 #ifdef HAVE_LIBCURL
curl_write_cb(void * ptr,size_t size,size_t nmemb,void * userdata)130 static size_t curl_write_cb(void *ptr, size_t size, size_t nmemb, void *userdata)
131 {
132 size_t r_size = size * nmemb;
133 zbx_http_response_t *response;
134
135 response = (zbx_http_response_t*)userdata;
136 zbx_str_memcpy_alloc(&response->data, &response->allocated, &response->offset, (const char *)ptr, r_size);
137
138 return r_size;
139 }
140
curl_ignore_cb(void * ptr,size_t size,size_t nmemb,void * userdata)141 static size_t curl_ignore_cb(void *ptr, size_t size, size_t nmemb, void *userdata)
142 {
143 ZBX_UNUSED(ptr);
144 ZBX_UNUSED(userdata);
145
146 return size * nmemb;
147 }
148
curl_page_get(char * url,char ** buffer,char ** error)149 static int curl_page_get(char *url, char **buffer, char **error)
150 {
151 CURLcode err;
152 zbx_http_response_t page = {0};
153 CURL *easyhandle;
154 int ret = SYSINFO_RET_FAIL;
155
156 if (NULL == (easyhandle = curl_easy_init()))
157 {
158 *error = zbx_strdup(*error, "Cannot initialize cURL library.");
159 return SYSINFO_RET_FAIL;
160 }
161
162 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_USERAGENT, "Zabbix " ZABBIX_VERSION)) ||
163 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_SSL_VERIFYPEER, 0L)) ||
164 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_SSL_VERIFYHOST, 0L)) ||
165 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_FOLLOWLOCATION, 0L)) ||
166 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_URL, url)) ||
167 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION,
168 NULL != buffer ? curl_write_cb : curl_ignore_cb)) ||
169 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_WRITEDATA, &page)) ||
170 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_HEADER, 1L)) ||
171 (NULL != CONFIG_SOURCE_IP &&
172 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_INTERFACE, CONFIG_SOURCE_IP))) ||
173 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_TIMEOUT, (long)CONFIG_TIMEOUT)))
174 {
175 *error = zbx_dsprintf(*error, "Cannot set cURL option: %s.", curl_easy_strerror(err));
176 goto out;
177 }
178
179 if (CURLE_OK == (err = curl_easy_perform(easyhandle)))
180 {
181 if (NULL != buffer)
182 *buffer = page.data;
183
184 ret = SYSINFO_RET_OK;
185 }
186 else
187 {
188 zbx_free(page.data);
189 *error = zbx_dsprintf(*error, "Cannot perform cURL request: %s.", curl_easy_strerror(err));
190 }
191
192 out:
193 curl_easy_cleanup(easyhandle);
194
195 return ret;
196 }
197
get_http_page(const char * host,const char * path,const char * port,char ** buffer,char ** error)198 static int get_http_page(const char *host, const char *path, const char *port, char **buffer, char **error)
199 {
200 char *url = NULL;
201 int ret;
202
203 if (SUCCEED != check_common_params(host, path, error))
204 return SYSINFO_RET_FAIL;
205
206 if (SUCCEED == detect_url(host))
207 {
208 /* URL detected */
209 if (SUCCEED != process_url(host, port, path, &url, error))
210 return SYSINFO_RET_FAIL;
211 }
212 else
213 {
214 /* URL is not detected - compose URL using host, port and path */
215
216 unsigned short port_n = ZBX_DEFAULT_HTTP_PORT;
217
218 if (NULL != port && '\0' != *port)
219 {
220 if (SUCCEED != is_ushort(port, &port_n))
221 {
222 *error = zbx_strdup(*error, "Invalid third parameter.");
223 return SYSINFO_RET_FAIL;
224 }
225 }
226
227 if (NULL != strchr(host, ':'))
228 url = zbx_dsprintf(url, HTTP_SCHEME_STR "[%s]:%u/", host, port_n);
229 else
230 url = zbx_dsprintf(url, HTTP_SCHEME_STR "%s:%u/", host, port_n);
231
232 if (NULL != path)
233 url = zbx_strdcat(url, path + ('/' == *path ? 1 : 0));
234 }
235
236 if (SUCCEED != zbx_http_punycode_encode_url(&url))
237 {
238 *error = zbx_strdup(*error, "Cannot encode domain name into punycode.");
239 ret = SYSINFO_RET_FAIL;
240 goto out;
241 }
242
243 ret = curl_page_get(url, buffer, error);
244 out:
245 zbx_free(url);
246
247 return ret;
248 }
249 #else
find_port_sep(char * host,size_t len)250 static char *find_port_sep(char *host, size_t len)
251 {
252 int in_ipv6 = 0;
253
254 for (; 0 < len--; host++)
255 {
256 if (0 == in_ipv6)
257 {
258 if (':' == *host)
259 return host;
260 else if ('[' == *host)
261 in_ipv6 = 1;
262 }
263 else if (']' == *host)
264 in_ipv6 = 0;
265 }
266
267 return NULL;
268 }
269
get_http_page(const char * host,const char * path,const char * port,char ** buffer,char ** error)270 static int get_http_page(const char *host, const char *path, const char *port, char **buffer, char **error)
271 {
272 char *url = NULL, *hostname = NULL, *path_loc = NULL;
273 int ret = SYSINFO_RET_OK, ipv6_host_found = 0;
274 unsigned short port_num;
275 zbx_socket_t s;
276
277 if (SUCCEED != check_common_params(host, path, error))
278 return SYSINFO_RET_FAIL;
279
280 if (SUCCEED == detect_url(host))
281 {
282 /* URL detected */
283
284 char *p, *p_host, *au_end;
285 size_t authority_len;
286
287 if (SUCCEED != process_url(host, port, path, &url, error))
288 return SYSINFO_RET_FAIL;
289
290 p_host = url + ZBX_CONST_STRLEN(HTTP_SCHEME_STR);
291
292 if (0 == (authority_len = strcspn(p_host, "/?")))
293 {
294 *error = zbx_dsprintf(*error, "Invalid or missing host in URL.");
295 ret = SYSINFO_RET_FAIL;
296 goto out;
297 }
298
299 if (NULL != memchr(p_host, '@', authority_len))
300 {
301 *error = zbx_strdup(*error, "Unsupported URL format.");
302 ret = SYSINFO_RET_FAIL;
303 goto out;
304 }
305
306 au_end = &p_host[authority_len - 1];
307
308 if (NULL != (p = find_port_sep(p_host, authority_len)))
309 {
310 char *port_str;
311 int port_len = (int)(au_end - p);
312
313 if (0 < port_len)
314 {
315 port_str = zbx_dsprintf(NULL, "%.*s", port_len, p + 1);
316
317 if (SUCCEED != is_ushort(port_str, &port_num))
318 ret = SYSINFO_RET_FAIL;
319 else
320 hostname = zbx_dsprintf(hostname, "%.*s", (int)(p - p_host), p_host);
321
322 zbx_free(port_str);
323 }
324 else
325 ret = SYSINFO_RET_FAIL;
326 }
327 else
328 {
329 port_num = ZBX_DEFAULT_HTTP_PORT;
330 hostname = zbx_dsprintf(hostname, "%.*s", (int)(au_end - p_host + 1), p_host);
331 }
332
333 if (SYSINFO_RET_OK != ret)
334 {
335 *error = zbx_dsprintf(*error, "URL using bad/illegal format.");
336 goto out;
337 }
338
339 if ('[' == *hostname)
340 {
341 zbx_ltrim(hostname, "[");
342 zbx_rtrim(hostname, "]");
343 ipv6_host_found = 1;
344 }
345
346 if ('\0' == *hostname)
347 {
348 *error = zbx_dsprintf(*error, "Invalid or missing host in URL.");
349 ret = SYSINFO_RET_FAIL;
350 goto out;
351 }
352
353 path_loc = zbx_strdup(path_loc, '\0' != p_host[authority_len] ? &p_host[authority_len] : "/");
354 }
355 else
356 {
357 /* URL is not detected */
358
359 if (NULL == port || '\0' == *port)
360 {
361 port_num = ZBX_DEFAULT_HTTP_PORT;
362 }
363 else if (FAIL == is_ushort(port, &port_num))
364 {
365 *error = zbx_strdup(*error, "Invalid third parameter.");
366 ret = SYSINFO_RET_FAIL;
367 goto out;
368 }
369
370 path_loc = zbx_strdup(path_loc, (NULL != path ? path : "/"));
371 hostname = zbx_strdup(hostname, host);
372
373 if (NULL != strchr(hostname, ':'))
374 ipv6_host_found = 1;
375 }
376
377 if (SUCCEED != zbx_http_punycode_encode_url(&hostname))
378 {
379 *error = zbx_strdup(*error, "Cannot encode domain name into punycode.");
380 ret = SYSINFO_RET_FAIL;
381 goto out;
382 }
383
384 if (SUCCEED == (ret = zbx_tcp_connect(&s, CONFIG_SOURCE_IP, hostname, port_num, CONFIG_TIMEOUT,
385 ZBX_TCP_SEC_UNENCRYPTED, NULL, NULL)))
386 {
387 char *request = NULL;
388
389 request = zbx_dsprintf(request,
390 "GET %s%s HTTP/1.1\r\n"
391 "Host: %s%s%s\r\n"
392 "Connection: close\r\n"
393 "\r\n",
394 ('/' != *path_loc ? "/" : ""), path_loc, (1 == ipv6_host_found ? "[" : ""), hostname,
395 (1 == ipv6_host_found ? "]" : ""));
396
397 if (SUCCEED == (ret = zbx_tcp_send_raw(&s, request)))
398 {
399 if (SUCCEED == (ret = zbx_tcp_recv_raw(&s)))
400 {
401 if (NULL != buffer)
402 {
403 *buffer = (char*)zbx_malloc(*buffer, ZBX_MAX_WEBPAGE_SIZE);
404 zbx_strlcpy(*buffer, s.buffer, ZBX_MAX_WEBPAGE_SIZE);
405 }
406 }
407 }
408
409 zbx_free(request);
410 zbx_tcp_close(&s);
411 }
412
413 if (SUCCEED != ret)
414 {
415 *error = zbx_dsprintf(NULL, "HTTP get error: %s", zbx_socket_strerror());
416 ret = SYSINFO_RET_FAIL;
417 }
418 else
419 ret = SYSINFO_RET_OK;
420
421 out:
422 zbx_free(url);
423 zbx_free(path_loc);
424 zbx_free(hostname);
425
426 return ret;
427 }
428 #endif
429
WEB_PAGE_GET(AGENT_REQUEST * request,AGENT_RESULT * result)430 int WEB_PAGE_GET(AGENT_REQUEST *request, AGENT_RESULT *result)
431 {
432 char *hostname, *path_str, *port_str, *buffer = NULL, *error = NULL;
433 int ret;
434
435 if (3 < request->nparam)
436 {
437 SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
438 return SYSINFO_RET_FAIL;
439 }
440
441 hostname = get_rparam(request, 0);
442 path_str = get_rparam(request, 1);
443 port_str = get_rparam(request, 2);
444
445 if (SYSINFO_RET_OK == (ret = get_http_page(hostname, path_str, port_str, &buffer, &error)))
446 {
447 zbx_rtrim(buffer, "\r\n");
448 SET_TEXT_RESULT(result, buffer);
449 }
450 else
451 SET_MSG_RESULT(result, error);
452
453 return ret;
454 }
455
WEB_PAGE_PERF(AGENT_REQUEST * request,AGENT_RESULT * result)456 int WEB_PAGE_PERF(AGENT_REQUEST *request, AGENT_RESULT *result)
457 {
458 char *hostname, *path_str, *port_str, *error = NULL;
459 double start_time;
460 int ret;
461
462 if (3 < request->nparam)
463 {
464 SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
465 return SYSINFO_RET_FAIL;
466 }
467
468 hostname = get_rparam(request, 0);
469 path_str = get_rparam(request, 1);
470 port_str = get_rparam(request, 2);
471
472 start_time = zbx_time();
473
474 if (SYSINFO_RET_OK == (ret = get_http_page(hostname, path_str, port_str, NULL, &error)))
475 SET_DBL_RESULT(result, zbx_time() - start_time);
476 else
477 SET_MSG_RESULT(result, error);
478
479 return ret;
480 }
481
WEB_PAGE_REGEXP(AGENT_REQUEST * request,AGENT_RESULT * result)482 int WEB_PAGE_REGEXP(AGENT_REQUEST *request, AGENT_RESULT *result)
483 {
484 char *hostname, *path_str, *port_str, *buffer = NULL, *error = NULL,
485 *ptr = NULL, *str, *newline, *regexp, *length_str;
486 const char *output;
487 int length, ret;
488
489 if (6 < request->nparam)
490 {
491 SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
492 return SYSINFO_RET_FAIL;
493 }
494
495 if (4 > request->nparam)
496 {
497 SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid number of parameters."));
498 return SYSINFO_RET_FAIL;
499 }
500
501 hostname = get_rparam(request, 0);
502 path_str = get_rparam(request, 1);
503 port_str = get_rparam(request, 2);
504 regexp = get_rparam(request, 3);
505 length_str = get_rparam(request, 4);
506 output = get_rparam(request, 5);
507
508 if (NULL == length_str || '\0' == *length_str)
509 length = MAX_BUFFER_LEN - 1;
510 else if (FAIL == is_uint31_1(length_str, &length))
511 {
512 SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fifth parameter."));
513 return SYSINFO_RET_FAIL;
514 }
515
516 /* by default return the matched part of web page */
517 if (NULL == output || '\0' == *output)
518 output = "\\0";
519
520 if (SYSINFO_RET_OK == (ret = get_http_page(hostname, path_str, port_str, &buffer, &error)))
521 {
522 for (str = buffer; ;)
523 {
524 if (NULL != (newline = strchr(str, '\n')))
525 {
526 if (str != newline && '\r' == newline[-1])
527 newline[-1] = '\0';
528 else
529 *newline = '\0';
530 }
531
532 if (SUCCEED == zbx_regexp_sub(str, regexp, output, &ptr) && NULL != ptr)
533 break;
534
535 if (NULL != newline)
536 str = newline + 1;
537 else
538 break;
539 }
540
541 if (NULL != ptr)
542 SET_STR_RESULT(result, ptr);
543 else
544 SET_STR_RESULT(result, zbx_strdup(NULL, ""));
545
546 zbx_free(buffer);
547 }
548 else
549 SET_MSG_RESULT(result, error);
550
551 return ret;
552 }
553