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 "checks_http.h"
21 #include "zbxhttp.h"
22 #include "zbxjson.h"
23 #include "log.h"
24 #ifdef HAVE_LIBCURL
25 
26 #define HTTP_REQUEST_GET	0
27 #define HTTP_REQUEST_POST	1
28 #define HTTP_REQUEST_PUT	2
29 #define HTTP_REQUEST_HEAD	3
30 
31 #define HTTP_STORE_RAW		0
32 #define HTTP_STORE_JSON		1
33 
zbx_request_string(int result)34 static const char	*zbx_request_string(int result)
35 {
36 	switch (result)
37 	{
38 		case HTTP_REQUEST_GET:
39 			return "GET";
40 		case HTTP_REQUEST_POST:
41 			return "POST";
42 		case HTTP_REQUEST_PUT:
43 			return "PUT";
44 		case HTTP_REQUEST_HEAD:
45 			return "HEAD";
46 		default:
47 			return "unknown";
48 	}
49 }
50 
http_prepare_request(CURL * easyhandle,const char * posts,unsigned char request_method,char ** error)51 static int	http_prepare_request(CURL *easyhandle, const char *posts, unsigned char request_method, char **error)
52 {
53 	CURLcode	err;
54 
55 	switch (request_method)
56 	{
57 		case HTTP_REQUEST_POST:
58 			if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDS, posts)))
59 			{
60 				*error = zbx_dsprintf(*error, "Cannot specify data to POST: %s", curl_easy_strerror(err));
61 				return FAIL;
62 			}
63 			break;
64 		case HTTP_REQUEST_GET:
65 			if ('\0' == *posts)
66 				return SUCCEED;
67 
68 			if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDS, posts)))
69 			{
70 				*error = zbx_dsprintf(*error, "Cannot specify data to POST: %s", curl_easy_strerror(err));
71 				return FAIL;
72 			}
73 
74 			if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_CUSTOMREQUEST, "GET")))
75 			{
76 				*error = zbx_dsprintf(*error, "Cannot specify custom GET request: %s",
77 						curl_easy_strerror(err));
78 				return FAIL;
79 			}
80 			break;
81 		case HTTP_REQUEST_HEAD:
82 			if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_NOBODY, 1L)))
83 			{
84 				*error = zbx_dsprintf(*error, "Cannot specify HEAD request: %s", curl_easy_strerror(err));
85 				return FAIL;
86 			}
87 			break;
88 		case HTTP_REQUEST_PUT:
89 			if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDS, posts)))
90 			{
91 				*error = zbx_dsprintf(*error, "Cannot specify data to POST: %s", curl_easy_strerror(err));
92 				return FAIL;
93 			}
94 
95 			if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_CUSTOMREQUEST, "PUT")))
96 			{
97 				*error = zbx_dsprintf(*error, "Cannot specify custom GET request: %s",
98 						curl_easy_strerror(err));
99 				return FAIL;
100 			}
101 			break;
102 		default:
103 			THIS_SHOULD_NEVER_HAPPEN;
104 			*error = zbx_strdup(*error, "Unsupported request method");
105 			return FAIL;
106 	}
107 
108 	return SUCCEED;
109 }
110 
http_add_json_header(struct zbx_json * json,char * line)111 static void	http_add_json_header(struct zbx_json *json, char *line)
112 {
113 	char	*colon;
114 
115 	if (NULL != (colon = strchr(line, ':')))
116 	{
117 		zbx_ltrim(colon + 1, " \t");
118 
119 		*colon = '\0';
120 		zbx_json_addstring(json, line, colon + 1, ZBX_JSON_TYPE_STRING);
121 		*colon = ':';
122 	}
123 	else
124 		zbx_json_addstring(json, line, "", ZBX_JSON_TYPE_STRING);
125 }
126 
http_output_json(unsigned char retrieve_mode,char ** buffer,zbx_http_response_t * header,zbx_http_response_t * body)127 static void	http_output_json(unsigned char retrieve_mode, char **buffer, zbx_http_response_t *header,
128 		zbx_http_response_t *body)
129 {
130 	struct zbx_json		json;
131 	struct zbx_json_parse	jp;
132 	char			*headers, *line;
133 	unsigned char		json_content = 0;
134 
135 	zbx_json_init(&json, ZBX_JSON_STAT_BUF_LEN);
136 
137 	headers = header->data;
138 
139 	if (retrieve_mode != ZBX_RETRIEVE_MODE_CONTENT)
140 		zbx_json_addobject(&json, "header");
141 
142 	while (NULL != (line = zbx_http_parse_header(&headers)))
143 	{
144 		if (0 == json_content &&
145 				0 == zbx_strncasecmp(line, "Content-Type:", ZBX_CONST_STRLEN("Content-Type:")) &&
146 				NULL != strstr(line, "application/json"))
147 		{
148 			json_content = 1;
149 		}
150 
151 		if (retrieve_mode != ZBX_RETRIEVE_MODE_CONTENT)
152 			http_add_json_header(&json, line);
153 
154 		zbx_free(line);
155 	}
156 
157 	if (retrieve_mode != ZBX_RETRIEVE_MODE_CONTENT)
158 		zbx_json_close(&json);
159 
160 	if (NULL != body->data)
161 	{
162 		if (0 == json_content)
163 		{
164 			zbx_json_addstring(&json, "body", body->data, ZBX_JSON_TYPE_STRING);
165 		}
166 		else if (FAIL == zbx_json_open(body->data, &jp))
167 		{
168 			zbx_json_addstring(&json, "body", body->data, ZBX_JSON_TYPE_STRING);
169 			zabbix_log(LOG_LEVEL_DEBUG, "received invalid JSON object %s", zbx_json_strerror());
170 		}
171 		else
172 		{
173 			zbx_lrtrim(body->data, ZBX_WHITESPACE);
174 			zbx_json_addraw(&json, "body", body->data);
175 		}
176 	}
177 
178 	*buffer = zbx_strdup(NULL, json.buffer);
179 	zbx_json_free(&json);
180 }
181 
get_value_http(const DC_ITEM * item,AGENT_RESULT * result)182 int	get_value_http(const DC_ITEM *item, AGENT_RESULT *result)
183 {
184 	CURL			*easyhandle;
185 	CURLcode		err;
186 	char			url[ITEM_URL_LEN_MAX], errbuf[CURL_ERROR_SIZE], *error = NULL, *headers, *line, *buffer;
187 	int			ret = NOTSUPPORTED, timeout_seconds, found = FAIL;
188 	long			response_code;
189 	struct curl_slist	*headers_slist = NULL;
190 	struct zbx_json		json;
191 	zbx_http_response_t	body = {0}, header = {0};
192 	size_t			(*curl_body_cb)(void *ptr, size_t size, size_t nmemb, void *userdata);
193 	char			application_json[] = {"Content-Type: application/json"};
194 	char			application_xml[] = {"Content-Type: application/xml"};
195 
196 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() request method '%s' URL '%s%s' headers '%s' message body '%s'",
197 			__func__, zbx_request_string(item->request_method), item->url, item->query_fields,
198 			item->headers, item->posts);
199 
200 	if (NULL == (easyhandle = curl_easy_init()))
201 	{
202 		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot initialize cURL library"));
203 		goto clean;
204 	}
205 
206 	switch (item->retrieve_mode)
207 	{
208 		case ZBX_RETRIEVE_MODE_CONTENT:
209 		case ZBX_RETRIEVE_MODE_BOTH:
210 			curl_body_cb = zbx_curl_write_cb;
211 			break;
212 		case ZBX_RETRIEVE_MODE_HEADERS:
213 			curl_body_cb = zbx_curl_ignore_cb;
214 			break;
215 		default:
216 			THIS_SHOULD_NEVER_HAPPEN;
217 			SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid retrieve mode"));
218 			goto clean;
219 	}
220 
221 	if (SUCCEED != zbx_http_prepare_callbacks(easyhandle, &header, &body, zbx_curl_write_cb, curl_body_cb, errbuf,
222 			&error))
223 	{
224 		SET_MSG_RESULT(result, error);
225 		goto clean;
226 	}
227 
228 	if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_PROXY, item->http_proxy)))
229 	{
230 		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot set proxy: %s", curl_easy_strerror(err)));
231 		goto clean;
232 	}
233 
234 	if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_FOLLOWLOCATION,
235 			0 == item->follow_redirects ? 0L : 1L)))
236 	{
237 		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot set follow redirects: %s", curl_easy_strerror(err)));
238 		goto clean;
239 	}
240 
241 	if (0 != item->follow_redirects &&
242 			CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_MAXREDIRS, ZBX_CURLOPT_MAXREDIRS)))
243 	{
244 		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot set number of redirects allowed: %s",
245 				curl_easy_strerror(err)));
246 		goto clean;
247 	}
248 
249 	if (FAIL == is_time_suffix(item->timeout, &timeout_seconds, strlen(item->timeout)))
250 	{
251 		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid timeout: %s", item->timeout));
252 		goto clean;
253 	}
254 
255 	if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_TIMEOUT, (long)timeout_seconds)))
256 	{
257 		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot specify timeout: %s", curl_easy_strerror(err)));
258 		goto clean;
259 	}
260 
261 	if (SUCCEED != zbx_http_prepare_ssl(easyhandle, item->ssl_cert_file, item->ssl_key_file, item->ssl_key_password,
262 			item->verify_peer, item->verify_host, &error))
263 	{
264 		SET_MSG_RESULT(result, error);
265 		goto clean;
266 	}
267 
268 	if (SUCCEED != zbx_http_prepare_auth(easyhandle, item->authtype, item->username, item->password, &error))
269 	{
270 		SET_MSG_RESULT(result, error);
271 		goto clean;
272 	}
273 
274 	if (SUCCEED != http_prepare_request(easyhandle, item->posts, item->request_method, &error))
275 	{
276 		SET_MSG_RESULT(result, error);
277 		goto clean;
278 	}
279 
280 	headers = item->headers;
281 	while (NULL != (line = zbx_http_parse_header(&headers)))
282 	{
283 		headers_slist = curl_slist_append(headers_slist, line);
284 
285 		if (FAIL == found && 0 == strncmp(line, "Content-Type:", ZBX_CONST_STRLEN("Content-Type:")))
286 			found = SUCCEED;
287 
288 		zbx_free(line);
289 	}
290 
291 	if (FAIL == found)
292 	{
293 		if (ZBX_POSTTYPE_JSON == item->post_type)
294 			headers_slist = curl_slist_append(headers_slist, application_json);
295 		else if (ZBX_POSTTYPE_XML == item->post_type)
296 			headers_slist = curl_slist_append(headers_slist, application_xml);
297 	}
298 
299 	if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_HTTPHEADER, headers_slist)))
300 	{
301 		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot specify headers: %s", curl_easy_strerror(err)));
302 		goto clean;
303 	}
304 
305 #if LIBCURL_VERSION_NUM >= 0x071304
306 	/* CURLOPT_PROTOCOLS is supported starting with version 7.19.4 (0x071304) */
307 	if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS)))
308 	{
309 		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot set allowed protocols: %s", curl_easy_strerror(err)));
310 		goto clean;
311 	}
312 #endif
313 
314 	zbx_snprintf(url, sizeof(url),"%s%s", item->url, item->query_fields);
315 	if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_URL, url)))
316 	{
317 		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot specify URL: %s", curl_easy_strerror(err)));
318 		goto clean;
319 	}
320 
321 	*errbuf = '\0';
322 
323 	if (CURLE_OK != (err = curl_easy_perform(easyhandle)))
324 	{
325 		if (CURLE_WRITE_ERROR == err)
326 		{
327 			SET_MSG_RESULT(result, zbx_strdup(NULL, "The requested value is too large"));
328 		}
329 		else
330 		{
331 			SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot perform request: %s",
332 					'\0' == *errbuf ? curl_easy_strerror(err) : errbuf));
333 		}
334 		goto clean;
335 	}
336 
337 	if (CURLE_OK != (err = curl_easy_getinfo(easyhandle, CURLINFO_RESPONSE_CODE, &response_code)))
338 	{
339 		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot get the response code: %s", curl_easy_strerror(err)));
340 		goto clean;
341 	}
342 
343 	if ('\0' != *item->status_codes && FAIL == int_in_list(item->status_codes, response_code))
344 	{
345 		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Response code \"%ld\" did not match any of the"
346 				" required status codes \"%s\"", response_code, item->status_codes));
347 		goto clean;
348 	}
349 
350 	if (NULL == header.data)
351 	{
352 		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Server returned empty header"));
353 		goto clean;
354 	}
355 
356 	switch (item->retrieve_mode)
357 	{
358 		case ZBX_RETRIEVE_MODE_CONTENT:
359 			if (NULL == body.data)
360 			{
361 				SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Server returned empty content"));
362 				goto clean;
363 			}
364 
365 			if (FAIL == zbx_is_utf8(body.data))
366 			{
367 				SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Server returned invalid UTF-8 sequence"));
368 				goto clean;
369 			}
370 
371 			if (HTTP_STORE_JSON == item->output_format)
372 			{
373 				http_output_json(item->retrieve_mode, &buffer, &header, &body);
374 				SET_TEXT_RESULT(result, buffer);
375 			}
376 			else
377 			{
378 				SET_TEXT_RESULT(result, body.data);
379 				body.data = NULL;
380 			}
381 			break;
382 		case ZBX_RETRIEVE_MODE_HEADERS:
383 			if (FAIL == zbx_is_utf8(header.data))
384 			{
385 				SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Server returned invalid UTF-8 sequence"));
386 				goto clean;
387 			}
388 
389 			if (HTTP_STORE_JSON == item->output_format)
390 			{
391 				zbx_json_init(&json, ZBX_JSON_STAT_BUF_LEN);
392 				zbx_json_addobject(&json, "header");
393 				headers = header.data;
394 				while (NULL != (line = zbx_http_parse_header(&headers)))
395 				{
396 					http_add_json_header(&json, line);
397 					zbx_free(line);
398 				}
399 				SET_TEXT_RESULT(result, zbx_strdup(NULL, json.buffer));
400 				zbx_json_free(&json);
401 			}
402 			else
403 			{
404 				SET_TEXT_RESULT(result, header.data);
405 				header.data = NULL;
406 			}
407 			break;
408 		case ZBX_RETRIEVE_MODE_BOTH:
409 			if (FAIL == zbx_is_utf8(header.data) || (NULL != body.data && FAIL == zbx_is_utf8(body.data)))
410 			{
411 				SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Server returned invalid UTF-8 sequence"));
412 				goto clean;
413 			}
414 
415 			if (HTTP_STORE_JSON == item->output_format)
416 			{
417 				http_output_json(item->retrieve_mode, &buffer, &header, &body);
418 				SET_TEXT_RESULT(result, buffer);
419 			}
420 			else
421 			{
422 				zbx_strncpy_alloc(&header.data, &header.allocated, &header.offset,
423 						body.data, body.offset);
424 				SET_TEXT_RESULT(result, header.data);
425 				header.data = NULL;
426 			}
427 			break;
428 	}
429 
430 	ret = SUCCEED;
431 clean:
432 	curl_slist_free_all(headers_slist);	/* must be called after curl_easy_perform() */
433 	curl_easy_cleanup(easyhandle);
434 	zbx_free(body.data);
435 	zbx_free(header.data);
436 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
437 
438 	return ret;
439 }
440 #endif
441