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