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 			CURLE_OK != (err = curl_easy_setopt(easyhandle, ZBX_CURLOPT_ACCEPT_ENCODING, "")))
175 	{
176 		*error = zbx_dsprintf(*error, "Cannot set cURL option: %s.", curl_easy_strerror(err));
177 		goto out;
178 	}
179 
180 	if (CURLE_OK == (err = curl_easy_perform(easyhandle)))
181 	{
182 		if (NULL != buffer)
183 			*buffer = page.data;
184 
185 		ret = SYSINFO_RET_OK;
186 	}
187 	else
188 	{
189 		zbx_free(page.data);
190 		*error = zbx_dsprintf(*error, "Cannot perform cURL request: %s.", curl_easy_strerror(err));
191 	}
192 
193 out:
194 	curl_easy_cleanup(easyhandle);
195 
196 	return ret;
197 }
198 
get_http_page(const char * host,const char * path,const char * port,char ** buffer,char ** error)199 static int	get_http_page(const char *host, const char *path, const char *port, char **buffer, char **error)
200 {
201 	char	*url = NULL;
202 	int	ret;
203 
204 	if (SUCCEED != check_common_params(host, path, error))
205 		return SYSINFO_RET_FAIL;
206 
207 	if (SUCCEED == detect_url(host))
208 	{
209 		/* URL detected */
210 		if (SUCCEED != process_url(host, port, path, &url, error))
211 			return SYSINFO_RET_FAIL;
212 	}
213 	else
214 	{
215 		/* URL is not detected - compose URL using host, port and path */
216 
217 		unsigned short	port_n = ZBX_DEFAULT_HTTP_PORT;
218 
219 		if (NULL != port && '\0' != *port)
220 		{
221 			if (SUCCEED != is_ushort(port, &port_n))
222 			{
223 				*error = zbx_strdup(*error, "Invalid third parameter.");
224 				return SYSINFO_RET_FAIL;
225 			}
226 		}
227 
228 		if (NULL != strchr(host, ':'))
229 			url = zbx_dsprintf(url, HTTP_SCHEME_STR "[%s]:%u/", host, port_n);
230 		else
231 			url = zbx_dsprintf(url, HTTP_SCHEME_STR "%s:%u/", host, port_n);
232 
233 		if (NULL != path)
234 			url = zbx_strdcat(url, path + ('/' == *path ? 1 : 0));
235 	}
236 
237 	if (SUCCEED != zbx_http_punycode_encode_url(&url))
238 	{
239 		*error = zbx_strdup(*error, "Cannot encode domain name into punycode.");
240 		ret = SYSINFO_RET_FAIL;
241 		goto out;
242 	}
243 
244 	ret = curl_page_get(url, buffer, error);
245 out:
246 	zbx_free(url);
247 
248 	return ret;
249 }
250 #else
find_port_sep(char * host,size_t len)251 static char	*find_port_sep(char *host, size_t len)
252 {
253 	int	in_ipv6 = 0;
254 
255 	for (; 0 < len--; host++)
256 	{
257 		if (0 == in_ipv6)
258 		{
259 			if (':' == *host)
260 				return host;
261 			else if ('[' == *host)
262 				in_ipv6 = 1;
263 		}
264 		else if (']' == *host)
265 			in_ipv6 = 0;
266 	}
267 
268 	return NULL;
269 }
270 
get_http_page(const char * host,const char * path,const char * port,char ** buffer,char ** error)271 static int	get_http_page(const char *host, const char *path, const char *port, char **buffer, char **error)
272 {
273 	char		*url = NULL, *hostname = NULL, *path_loc = NULL;
274 	int		ret = SYSINFO_RET_OK, ipv6_host_found = 0;
275 	unsigned short	port_num;
276 	zbx_socket_t	s;
277 
278 	if (SUCCEED != check_common_params(host, path, error))
279 		return SYSINFO_RET_FAIL;
280 
281 	if (SUCCEED == detect_url(host))
282 	{
283 		/* URL detected */
284 
285 		char	*p, *p_host, *au_end;
286 		size_t	authority_len;
287 
288 		if (SUCCEED != process_url(host, port, path, &url, error))
289 			return SYSINFO_RET_FAIL;
290 
291 		p_host = url + ZBX_CONST_STRLEN(HTTP_SCHEME_STR);
292 
293 		if (0 == (authority_len = strcspn(p_host, "/?")))
294 		{
295 			*error = zbx_dsprintf(*error, "Invalid or missing host in URL.");
296 			ret = SYSINFO_RET_FAIL;
297 			goto out;
298 		}
299 
300 		if (NULL != memchr(p_host, '@', authority_len))
301 		{
302 			*error = zbx_strdup(*error, "Unsupported URL format.");
303 			ret = SYSINFO_RET_FAIL;
304 			goto out;
305 		}
306 
307 		au_end = &p_host[authority_len - 1];
308 
309 		if (NULL != (p = find_port_sep(p_host, authority_len)))
310 		{
311 			char	*port_str;
312 			int	port_len = (int)(au_end - p);
313 
314 			if (0 < port_len)
315 			{
316 				port_str = zbx_dsprintf(NULL, "%.*s", port_len, p + 1);
317 
318 				if (SUCCEED != is_ushort(port_str, &port_num))
319 					ret = SYSINFO_RET_FAIL;
320 				else
321 					hostname = zbx_dsprintf(hostname, "%.*s", (int)(p - p_host), p_host);
322 
323 				zbx_free(port_str);
324 			}
325 			else
326 				ret = SYSINFO_RET_FAIL;
327 		}
328 		else
329 		{
330 			port_num = ZBX_DEFAULT_HTTP_PORT;
331 			hostname = zbx_dsprintf(hostname, "%.*s", (int)(au_end - p_host + 1), p_host);
332 		}
333 
334 		if (SYSINFO_RET_OK != ret)
335 		{
336 			*error = zbx_dsprintf(*error, "URL using bad/illegal format.");
337 			goto out;
338 		}
339 
340 		if ('[' == *hostname)
341 		{
342 			zbx_ltrim(hostname, "[");
343 			zbx_rtrim(hostname, "]");
344 			ipv6_host_found = 1;
345 		}
346 
347 		if ('\0' == *hostname)
348 		{
349 			*error = zbx_dsprintf(*error, "Invalid or missing host in URL.");
350 			ret = SYSINFO_RET_FAIL;
351 			goto out;
352 		}
353 
354 		path_loc = zbx_strdup(path_loc, '\0' != p_host[authority_len] ? &p_host[authority_len] : "/");
355 	}
356 	else
357 	{
358 		/* URL is not detected */
359 
360 		if (NULL == port || '\0' == *port)
361 		{
362 			port_num = ZBX_DEFAULT_HTTP_PORT;
363 		}
364 		else if (FAIL == is_ushort(port, &port_num))
365 		{
366 			*error = zbx_strdup(*error, "Invalid third parameter.");
367 			ret = SYSINFO_RET_FAIL;
368 			goto out;
369 		}
370 
371 		path_loc = zbx_strdup(path_loc, (NULL != path ? path : "/"));
372 		hostname = zbx_strdup(hostname, host);
373 
374 		if (NULL != strchr(hostname, ':'))
375 			ipv6_host_found = 1;
376 	}
377 
378 	if (SUCCEED != zbx_http_punycode_encode_url(&hostname))
379 	{
380 		*error = zbx_strdup(*error, "Cannot encode domain name into punycode.");
381 		ret = SYSINFO_RET_FAIL;
382 		goto out;
383 	}
384 
385 	if (SUCCEED == (ret = zbx_tcp_connect(&s, CONFIG_SOURCE_IP, hostname, port_num, CONFIG_TIMEOUT,
386 			ZBX_TCP_SEC_UNENCRYPTED, NULL, NULL)))
387 	{
388 		char	*request = NULL;
389 
390 		request = zbx_dsprintf(request,
391 				"GET %s%s HTTP/1.1\r\n"
392 				"Host: %s%s%s\r\n"
393 				"Connection: close\r\n"
394 				"\r\n",
395 				('/' != *path_loc ? "/" : ""), path_loc, (1 == ipv6_host_found ? "[" : ""), hostname,
396 				(1 == ipv6_host_found ? "]" : ""));
397 
398 		if (SUCCEED == (ret = zbx_tcp_send_raw(&s, request)))
399 		{
400 			if (SUCCEED == (ret = zbx_tcp_recv_raw(&s)))
401 			{
402 				if (NULL != buffer)
403 				{
404 					*buffer = (char*)zbx_malloc(*buffer, ZBX_MAX_WEBPAGE_SIZE);
405 					zbx_strlcpy(*buffer, s.buffer, ZBX_MAX_WEBPAGE_SIZE);
406 				}
407 			}
408 		}
409 
410 		zbx_free(request);
411 		zbx_tcp_close(&s);
412 	}
413 
414 	if (SUCCEED != ret)
415 	{
416 		*error = zbx_dsprintf(NULL, "HTTP get error: %s", zbx_socket_strerror());
417 		ret = SYSINFO_RET_FAIL;
418 	}
419 	else
420 		ret = SYSINFO_RET_OK;
421 
422 out:
423 	zbx_free(url);
424 	zbx_free(path_loc);
425 	zbx_free(hostname);
426 
427 	return ret;
428 }
429 #endif
430 
WEB_PAGE_GET(AGENT_REQUEST * request,AGENT_RESULT * result)431 int	WEB_PAGE_GET(AGENT_REQUEST *request, AGENT_RESULT *result)
432 {
433 	char	*hostname, *path_str, *port_str, *buffer = NULL, *error = NULL;
434 	int	ret;
435 
436 	if (3 < request->nparam)
437 	{
438 		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
439 		return SYSINFO_RET_FAIL;
440 	}
441 
442 	hostname = get_rparam(request, 0);
443 	path_str = get_rparam(request, 1);
444 	port_str = get_rparam(request, 2);
445 
446 	if (SYSINFO_RET_OK == (ret = get_http_page(hostname, path_str, port_str, &buffer, &error)))
447 	{
448 		zbx_rtrim(buffer, "\r\n");
449 		SET_TEXT_RESULT(result, buffer);
450 	}
451 	else
452 		SET_MSG_RESULT(result, error);
453 
454 	return ret;
455 }
456 
WEB_PAGE_PERF(AGENT_REQUEST * request,AGENT_RESULT * result)457 int	WEB_PAGE_PERF(AGENT_REQUEST *request, AGENT_RESULT *result)
458 {
459 	char	*hostname, *path_str, *port_str, *error = NULL;
460 	double	start_time;
461 	int	ret;
462 
463 	if (3 < request->nparam)
464 	{
465 		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
466 		return SYSINFO_RET_FAIL;
467 	}
468 
469 	hostname = get_rparam(request, 0);
470 	path_str = get_rparam(request, 1);
471 	port_str = get_rparam(request, 2);
472 
473 	start_time = zbx_time();
474 
475 	if (SYSINFO_RET_OK == (ret = get_http_page(hostname, path_str, port_str, NULL, &error)))
476 		SET_DBL_RESULT(result, zbx_time() - start_time);
477 	else
478 		SET_MSG_RESULT(result, error);
479 
480 	return ret;
481 }
482 
WEB_PAGE_REGEXP(AGENT_REQUEST * request,AGENT_RESULT * result)483 int	WEB_PAGE_REGEXP(AGENT_REQUEST *request, AGENT_RESULT *result)
484 {
485 	char		*hostname, *path_str, *port_str, *buffer = NULL, *error = NULL, *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 = ZBX_MAX_UINT31_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 		char	*ptr = NULL, *str;
523 
524 		for (str = buffer; ;)
525 		{
526 			char	*newline;
527 
528 			if (NULL != (newline = strchr(str, '\n')))
529 			{
530 				if (str != newline && '\r' == newline[-1])
531 					newline[-1] = '\0';
532 				else
533 					*newline = '\0';
534 			}
535 
536 			if (SUCCEED == zbx_regexp_sub(str, regexp, output, &ptr) && NULL != ptr)
537 				break;
538 
539 			if (NULL != newline)
540 				str = newline + 1;
541 			else
542 				break;
543 		}
544 
545 		if (NULL != ptr)
546 		{
547 			if ((size_t)length < zbx_strlen_utf8(ptr))
548 				ptr[zbx_strlen_utf8_nchars(ptr, length)] = '\0';
549 
550 			SET_STR_RESULT(result, ptr);
551 		}
552 		else
553 			SET_STR_RESULT(result, zbx_strdup(NULL, ""));
554 
555 		zbx_free(buffer);
556 	}
557 	else
558 		SET_MSG_RESULT(result, error);
559 
560 	return ret;
561 }
562