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
22 #include "db.h"
23 #include "log.h"
24 #include "dbcache.h"
25 #include "preproc.h"
26 #include "daemon.h"
27
28 #include "zbxserver.h"
29 #include "zbxregexp.h"
30 #include "zbxhttp.h"
31
32 #include "httptest.h"
33 #include "httpmacro.h"
34
35 typedef struct
36 {
37 long rspcode;
38 double total_time;
39 double speed_download;
40 }
41 zbx_httpstat_t;
42
43 extern int CONFIG_HTTPPOLLER_FORKS;
44
45 #ifdef HAVE_LIBCURL
46
47 typedef struct
48 {
49 char *data;
50 size_t allocated;
51 size_t offset;
52 }
53 zbx_httppage_t;
54
55 static zbx_httppage_t page;
56
curl_write_cb(void * ptr,size_t size,size_t nmemb,void * userdata)57 static size_t curl_write_cb(void *ptr, size_t size, size_t nmemb, void *userdata)
58 {
59 size_t r_size = size * nmemb;
60
61 ZBX_UNUSED(userdata);
62
63 /* first piece of data */
64 if (NULL == page.data)
65 {
66 page.allocated = MAX(8096, r_size);
67 page.offset = 0;
68 page.data = (char *)zbx_malloc(page.data, page.allocated);
69 }
70
71 zbx_strncpy_alloc(&page.data, &page.allocated, &page.offset, (char *)ptr, r_size);
72
73 return r_size;
74 }
75
curl_ignore_cb(void * ptr,size_t size,size_t nmemb,void * userdata)76 static size_t curl_ignore_cb(void *ptr, size_t size, size_t nmemb, void *userdata)
77 {
78 ZBX_UNUSED(ptr);
79 ZBX_UNUSED(userdata);
80
81 return size * nmemb;
82 }
83
84 #endif /* HAVE_LIBCURL */
85
86 /******************************************************************************
87 * *
88 * Function: httptest_remove_macros *
89 * *
90 * Purpose: remove all macro variables cached during http test execution *
91 * *
92 * Parameters: httptest - [IN] the http test data *
93 * *
94 * Author: Andris Zeila *
95 * *
96 ******************************************************************************/
httptest_remove_macros(zbx_httptest_t * httptest)97 static void httptest_remove_macros(zbx_httptest_t *httptest)
98 {
99 int i;
100
101 for (i = 0; i < httptest->macros.values_num; i++)
102 {
103 zbx_ptr_pair_t *pair = &httptest->macros.values[i];
104
105 zbx_free(pair->first);
106 zbx_free(pair->second);
107 }
108
109 zbx_vector_ptr_pair_clear(&httptest->macros);
110 }
111
process_test_data(zbx_uint64_t httptestid,int lastfailedstep,double speed_download,const char * err_str,zbx_timespec_t * ts)112 static void process_test_data(zbx_uint64_t httptestid, int lastfailedstep, double speed_download,
113 const char *err_str, zbx_timespec_t *ts)
114 {
115 DB_RESULT result;
116 DB_ROW row;
117 unsigned char types[3];
118 DC_ITEM items[3];
119 zbx_uint64_t itemids[3];
120 int errcodes[3];
121 size_t i, num = 0;
122 AGENT_RESULT value;
123
124 zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
125
126 result = DBselect("select type,itemid from httptestitem where httptestid=" ZBX_FS_UI64, httptestid);
127
128 while (NULL != (row = DBfetch(result)))
129 {
130 if (3 == num)
131 {
132 THIS_SHOULD_NEVER_HAPPEN;
133 break;
134 }
135
136 switch (types[num] = (unsigned char)atoi(row[0]))
137 {
138 case ZBX_HTTPITEM_TYPE_SPEED:
139 case ZBX_HTTPITEM_TYPE_LASTSTEP:
140 break;
141 case ZBX_HTTPITEM_TYPE_LASTERROR:
142 if (NULL == err_str)
143 continue;
144 break;
145 default:
146 THIS_SHOULD_NEVER_HAPPEN;
147 continue;
148 }
149
150 ZBX_STR2UINT64(itemids[num], row[1]);
151 num++;
152 }
153 DBfree_result(result);
154
155 if (0 < num)
156 {
157 DCconfig_get_items_by_itemids(items, itemids, errcodes, num);
158
159 for (i = 0; i < num; i++)
160 {
161 if (SUCCEED != errcodes[i])
162 continue;
163
164 if (ITEM_STATUS_ACTIVE != items[i].status)
165 continue;
166
167 if (HOST_STATUS_MONITORED != items[i].host.status)
168 continue;
169
170 if (HOST_MAINTENANCE_STATUS_ON == items[i].host.maintenance_status &&
171 MAINTENANCE_TYPE_NODATA == items[i].host.maintenance_type)
172 {
173 continue;
174 }
175
176 init_result(&value);
177
178 switch (types[i])
179 {
180 case ZBX_HTTPITEM_TYPE_SPEED:
181 SET_UI64_RESULT(&value, speed_download);
182 break;
183 case ZBX_HTTPITEM_TYPE_LASTSTEP:
184 SET_UI64_RESULT(&value, lastfailedstep);
185 break;
186 case ZBX_HTTPITEM_TYPE_LASTERROR:
187 SET_STR_RESULT(&value, zbx_strdup(NULL, err_str));
188 break;
189 }
190
191 items[i].state = ITEM_STATE_NORMAL;
192 zbx_preprocess_item_value(items[i].itemid, items[i].host.hostid, items[i].value_type, 0, &value,
193 ts, items[i].state, NULL);
194
195 free_result(&value);
196 }
197
198 DCconfig_clean_items(items, errcodes, num);
199 }
200
201 zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
202 }
203
204 /******************************************************************************
205 * *
206 * Function: httpstep_pairs_join *
207 * *
208 * Purpose: performs concatenation of vector of pairs into delimited string *
209 * *
210 * Parameters: str - [IN/OUT] result string *
211 * alloc_len - [IN/OUT] allocated memory size *
212 * offset - [IN/OUT] offset within string *
213 * value_delimiter - [IN] delimiter to be used between name and *
214 * value *
215 * pair_delimiter - [IN] delimiter to be used between pairs *
216 * pairs - [IN] vector of pairs *
217 * *
218 ******************************************************************************/
httpstep_pairs_join(char ** str,size_t * alloc_len,size_t * offset,const char * value_delimiter,const char * pair_delimiter,zbx_vector_ptr_pair_t * pairs)219 static void httpstep_pairs_join(char **str, size_t *alloc_len, size_t *offset, const char *value_delimiter,
220 const char *pair_delimiter, zbx_vector_ptr_pair_t *pairs)
221 {
222 int p;
223 char *key, *value;
224
225 for (p = 0; p < pairs->values_num; p++)
226 {
227 key = (char *)pairs->values[p].first;
228 value = (char *)pairs->values[p].second;
229
230 if (0 != p)
231 zbx_strcpy_alloc(str, alloc_len, offset, pair_delimiter);
232
233 zbx_strcpy_alloc(str, alloc_len, offset, key);
234 zbx_strcpy_alloc(str, alloc_len, offset, value_delimiter);
235 zbx_strcpy_alloc(str, alloc_len, offset, value);
236 }
237 }
238
239 /******************************************************************************
240 * *
241 * Function: httppairs_free *
242 * *
243 * Purpose: frees memory allocated for vector of pairs *
244 * *
245 * Parameters: pairs - [IN] vector of pairs *
246 * *
247 ******************************************************************************/
httppairs_free(zbx_vector_ptr_pair_t * pairs)248 static void httppairs_free(zbx_vector_ptr_pair_t *pairs)
249 {
250 int p;
251
252 for (p = 0; p < pairs->values_num; p++)
253 {
254 zbx_free(pairs->values[p].first);
255 zbx_free(pairs->values[p].second);
256 }
257
258 zbx_vector_ptr_pair_destroy(pairs);
259 }
260
261 #ifdef HAVE_LIBCURL
process_step_data(zbx_uint64_t httpstepid,zbx_httpstat_t * stat,zbx_timespec_t * ts)262 static void process_step_data(zbx_uint64_t httpstepid, zbx_httpstat_t *stat, zbx_timespec_t *ts)
263 {
264 DB_RESULT result;
265 DB_ROW row;
266 unsigned char types[3];
267 DC_ITEM items[3];
268 zbx_uint64_t itemids[3];
269 int errcodes[3];
270 size_t i, num = 0;
271 AGENT_RESULT value;
272
273 zabbix_log(LOG_LEVEL_DEBUG, "In %s() rspcode:%ld time:" ZBX_FS_DBL " speed:" ZBX_FS_DBL,
274 __func__, stat->rspcode, stat->total_time, stat->speed_download);
275
276 result = DBselect("select type,itemid from httpstepitem where httpstepid=" ZBX_FS_UI64, httpstepid);
277
278 while (NULL != (row = DBfetch(result)))
279 {
280 if (3 == num)
281 {
282 THIS_SHOULD_NEVER_HAPPEN;
283 break;
284 }
285
286 if (ZBX_HTTPITEM_TYPE_RSPCODE != (types[num] = (unsigned char)atoi(row[0])) &&
287 ZBX_HTTPITEM_TYPE_TIME != types[num] && ZBX_HTTPITEM_TYPE_SPEED != types[num])
288 {
289 THIS_SHOULD_NEVER_HAPPEN;
290 continue;
291 }
292
293 ZBX_STR2UINT64(itemids[num], row[1]);
294 num++;
295 }
296 DBfree_result(result);
297
298 if (0 < num)
299 {
300 DCconfig_get_items_by_itemids(items, itemids, errcodes, num);
301
302 for (i = 0; i < num; i++)
303 {
304 if (SUCCEED != errcodes[i])
305 continue;
306
307 if (ITEM_STATUS_ACTIVE != items[i].status)
308 continue;
309
310 if (HOST_STATUS_MONITORED != items[i].host.status)
311 continue;
312
313 if (HOST_MAINTENANCE_STATUS_ON == items[i].host.maintenance_status &&
314 MAINTENANCE_TYPE_NODATA == items[i].host.maintenance_type)
315 {
316 continue;
317 }
318
319 init_result(&value);
320
321 switch (types[i])
322 {
323 case ZBX_HTTPITEM_TYPE_RSPCODE:
324 SET_UI64_RESULT(&value, stat->rspcode);
325 break;
326 case ZBX_HTTPITEM_TYPE_TIME:
327 SET_DBL_RESULT(&value, stat->total_time);
328 break;
329 case ZBX_HTTPITEM_TYPE_SPEED:
330 SET_DBL_RESULT(&value, stat->speed_download);
331 break;
332 }
333
334 items[i].state = ITEM_STATE_NORMAL;
335 zbx_preprocess_item_value(items[i].itemid, items[i].host.hostid, items[i].value_type, 0, &value,
336 ts, items[i].state, NULL);
337
338 free_result(&value);
339 }
340
341 DCconfig_clean_items(items, errcodes, num);
342 }
343
344 zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
345 }
346
347 /******************************************************************************
348 * *
349 * Function: httpstep_load_pairs *
350 * *
351 * Purpose: loads http fields of web scenario step *
352 * *
353 * Parameters: host - [IN] host to be used in macro expansion *
354 * httpstep - [IN/OUT] web scenario step *
355 * *
356 * Return value: SUCCEED if http fields were loaded and macro expansion was *
357 * successful. FAIL on error. *
358 * *
359 ******************************************************************************/
httpstep_load_pairs(DC_HOST * host,zbx_httpstep_t * httpstep)360 static int httpstep_load_pairs(DC_HOST *host, zbx_httpstep_t *httpstep)
361 {
362 int type, ret = SUCCEED;
363 DB_RESULT result;
364 DB_ROW row;
365 size_t alloc_len = 0, offset;
366 zbx_ptr_pair_t pair;
367 zbx_vector_ptr_pair_t *vector, headers, query_fields, post_fields;
368 char *key, *value, *url = NULL, query_delimiter = '?';
369
370 httpstep->url = NULL;
371 httpstep->posts = NULL;
372 httpstep->headers = NULL;
373
374 zbx_vector_ptr_pair_create(&headers);
375 zbx_vector_ptr_pair_create(&query_fields);
376 zbx_vector_ptr_pair_create(&post_fields);
377 zbx_vector_ptr_pair_create(&httpstep->variables);
378
379 result = DBselect(
380 "select name,value,type"
381 " from httpstep_field"
382 " where httpstepid=" ZBX_FS_UI64
383 " order by httpstep_fieldid",
384 httpstep->httpstep->httpstepid);
385
386 while (NULL != (row = DBfetch(result)))
387 {
388 type = atoi(row[2]);
389
390 value = zbx_strdup(NULL, row[1]);
391
392 /* from now on variable values can contain macros so proper URL encoding can be performed */
393 if (SUCCEED != (ret = substitute_simple_macros_unmasked(NULL, NULL, NULL, NULL, NULL, host, NULL, NULL,
394 NULL, NULL, &value, MACRO_TYPE_HTTPTEST_FIELD, NULL, 0)))
395 {
396 zbx_free(value);
397 goto out;
398 }
399
400 key = zbx_strdup(NULL, row[0]);
401
402 /* variable names cannot contain macros, and both variable names and variable values cannot contain */
403 /* another variables */
404 if (ZBX_HTTPFIELD_VARIABLE != type && (SUCCEED != (ret = substitute_simple_macros(NULL, NULL, NULL,
405 NULL, NULL, host, NULL, NULL, NULL, NULL, &key, MACRO_TYPE_HTTPTEST_FIELD, NULL, 0)) ||
406 SUCCEED != (ret = http_substitute_variables(httpstep->httptest, &key)) ||
407 SUCCEED != (ret = http_substitute_variables(httpstep->httptest, &value))))
408 {
409 httppairs_free(&httpstep->variables);
410 zbx_free(key);
411 zbx_free(value);
412 goto out;
413 }
414
415 /* keys and values of query fields / post fields should be encoded */
416 if (ZBX_HTTPFIELD_QUERY_FIELD == type || ZBX_HTTPFIELD_POST_FIELD == type)
417 {
418 zbx_http_url_encode(key, &key);
419 zbx_http_url_encode(value, &value);
420 }
421
422 switch (type)
423 {
424 case ZBX_HTTPFIELD_HEADER:
425 vector = &headers;
426 break;
427 case ZBX_HTTPFIELD_VARIABLE:
428 vector = &httpstep->variables;
429 break;
430 case ZBX_HTTPFIELD_QUERY_FIELD:
431 vector = &query_fields;
432 break;
433 case ZBX_HTTPFIELD_POST_FIELD:
434 vector = &post_fields;
435 break;
436 default:
437 THIS_SHOULD_NEVER_HAPPEN;
438 zbx_free(key);
439 zbx_free(value);
440 ret = FAIL;
441 goto out;
442 }
443
444 pair.first = key;
445 pair.second = value;
446
447 zbx_vector_ptr_pair_append(vector, pair);
448 }
449
450 /* URL is created from httpstep->httpstep->url, query_fields and fragment */
451 zbx_strcpy_alloc(&url, &alloc_len, &offset, httpstep->httpstep->url);
452
453 value = strchr(url, '#');
454
455 if (NULL != value)
456 {
457 /* URL contains fragment delimiter, so it must be dropped */
458
459 zabbix_log(LOG_LEVEL_DEBUG, "URL contains fragment delimiter, fragment part is deleted from URL");
460 *value = '\0';
461 offset = value - url;
462 }
463
464 if (0 < query_fields.values_num)
465 {
466 /* url can contain '?' so proper delimiter should be selected */
467 if (NULL != strchr(url, '?'))
468 query_delimiter = '&';
469
470 zbx_chrcpy_alloc(&url, &alloc_len, &offset, query_delimiter);
471 httpstep_pairs_join(&url, &alloc_len, &offset, "=", "&", &query_fields);
472 }
473
474 if (SUCCEED != (ret = zbx_http_punycode_encode_url(&url)))
475 {
476 zabbix_log(LOG_LEVEL_WARNING, "cannot encode unicode URL into punycode");
477 httppairs_free(&httpstep->variables);
478 zbx_free(url);
479 goto out;
480 }
481
482 httpstep->url = url;
483
484 /* POST data can be saved as raw data or as form data */
485 if (ZBX_POSTTYPE_FORM == httpstep->httpstep->post_type)
486 httpstep_pairs_join(&httpstep->posts, &alloc_len, &offset, "=", "&", &post_fields);
487 else
488 httpstep->posts = httpstep->httpstep->posts; /* post data in raw format */
489
490 httpstep_pairs_join(&httpstep->headers, &alloc_len, &offset, ":", "\r\n", &headers);
491 out:
492 httppairs_free(&headers);
493 httppairs_free(&query_fields);
494 httppairs_free(&post_fields);
495 DBfree_result(result);
496
497 return ret;
498 }
499
500 /******************************************************************************
501 * *
502 * Function: add_http_headers *
503 * *
504 * Purpose: adds HTTP headers to curl_slist and prepares cookie header string *
505 * *
506 * Parameters: headers - [IN] HTTP headers as string *
507 * headers_slist - [IN/OUT] curl_slist *
508 * header_cookie - [IN/OUT] cookie header as string *
509 * *
510 ******************************************************************************/
add_http_headers(char * headers,struct curl_slist ** headers_slist,char ** header_cookie)511 static void add_http_headers(char *headers, struct curl_slist **headers_slist, char **header_cookie)
512 {
513 #define COOKIE_HEADER_STR "Cookie:"
514 #define COOKIE_HEADER_STR_LEN ZBX_CONST_STRLEN(COOKIE_HEADER_STR)
515
516 char *line;
517
518 while (NULL != (line = zbx_http_parse_header(&headers)))
519 {
520 if (0 == strncmp(COOKIE_HEADER_STR, line, COOKIE_HEADER_STR_LEN))
521 *header_cookie = zbx_strdup(*header_cookie, line + COOKIE_HEADER_STR_LEN);
522 else
523 *headers_slist = curl_slist_append(*headers_slist, line);
524
525 zbx_free(line);
526 }
527
528 #undef COOKIE_HEADER_STR
529 #undef COOKIE_HEADER_STR_LEN
530 }
531 #endif
532
533 /******************************************************************************
534 * *
535 * Function: httptest_load_pairs *
536 * *
537 * Purpose: loads http fields of web scenario *
538 * *
539 * Parameters: host - [IN] host to be used in macro expansion *
540 * httptest - [IN/OUT] web scenario *
541 * *
542 * Return value: SUCCEED if http fields were loaded and macro expansion was *
543 * successful. FAIL on error. *
544 * *
545 ******************************************************************************/
httptest_load_pairs(DC_HOST * host,zbx_httptest_t * httptest)546 static int httptest_load_pairs(DC_HOST *host, zbx_httptest_t *httptest)
547 {
548 int type, ret = SUCCEED;
549 DB_RESULT result;
550 DB_ROW row;
551 size_t alloc_len = 0, offset;
552 zbx_ptr_pair_t pair;
553 zbx_vector_ptr_pair_t *vector, headers;
554 char *key, *value;
555
556 zbx_vector_ptr_pair_create(&headers);
557 zbx_vector_ptr_pair_create(&httptest->variables);
558
559 httptest->headers = NULL;
560 result = DBselect(
561 "select name,value,type"
562 " from httptest_field"
563 " where httptestid=" ZBX_FS_UI64
564 " order by httptest_fieldid",
565 httptest->httptest.httptestid);
566
567 while (NULL != (row = DBfetch(result)))
568 {
569 type = atoi(row[2]);
570 value = zbx_strdup(NULL, row[1]);
571
572 /* from now on variable values can contain macros so proper URL encoding can be performed */
573 if (SUCCEED != (ret = substitute_simple_macros_unmasked(NULL, NULL, NULL, NULL, NULL, host, NULL, NULL,
574 NULL, NULL, &value, MACRO_TYPE_HTTPTEST_FIELD, NULL, 0)))
575 {
576 zbx_free(value);
577 goto out;
578 }
579
580 key = zbx_strdup(NULL, row[0]);
581
582 /* variable names cannot contain macros, and both variable names and variable values cannot contain */
583 /* another variables */
584 if (ZBX_HTTPFIELD_VARIABLE != type && SUCCEED != (ret = substitute_simple_macros(NULL, NULL, NULL,
585 NULL, NULL, host, NULL, NULL, NULL, NULL, &key, MACRO_TYPE_HTTPTEST_FIELD, NULL, 0)))
586 {
587 httppairs_free(&httptest->variables);
588 zbx_free(key);
589 zbx_free(value);
590 goto out;
591 }
592
593 switch (type)
594 {
595 case ZBX_HTTPFIELD_HEADER:
596 vector = &headers;
597 break;
598 case ZBX_HTTPFIELD_VARIABLE:
599 vector = &httptest->variables;
600 break;
601 default:
602 zbx_free(key);
603 zbx_free(value);
604 ret = FAIL;
605 goto out;
606 }
607
608 pair.first = key;
609 pair.second = value;
610
611 zbx_vector_ptr_pair_append(vector, pair);
612 }
613
614 httpstep_pairs_join(&httptest->headers, &alloc_len, &offset, ":", "\r\n", &headers);
615 out:
616 httppairs_free(&headers);
617 DBfree_result(result);
618
619 return ret;
620 }
621
622 /******************************************************************************
623 * *
624 * Function: process_httptest *
625 * *
626 * Purpose: process single scenario of http test *
627 * *
628 * Parameters: *
629 * *
630 * Return value: *
631 * *
632 * Author: Alexei Vladishev *
633 * *
634 * Comments: *
635 * *
636 ******************************************************************************/
process_httptest(DC_HOST * host,zbx_httptest_t * httptest)637 static void process_httptest(DC_HOST *host, zbx_httptest_t *httptest)
638 {
639 DB_RESULT result;
640 DB_HTTPSTEP db_httpstep;
641 char *err_str = NULL, *buffer = NULL;
642 int lastfailedstep = 0;
643 zbx_timespec_t ts;
644 int delay;
645 double speed_download = 0;
646 int speed_download_num = 0;
647 #ifdef HAVE_LIBCURL
648 DB_ROW row;
649 zbx_httpstat_t stat;
650 char errbuf[CURL_ERROR_SIZE];
651 CURL *easyhandle = NULL;
652 CURLcode err;
653 zbx_httpstep_t httpstep;
654 #endif
655
656 zabbix_log(LOG_LEVEL_DEBUG, "In %s() httptestid:" ZBX_FS_UI64 " name:'%s'",
657 __func__, httptest->httptest.httptestid, httptest->httptest.name);
658
659 result = DBselect(
660 "select httpstepid,no,name,url,timeout,posts,required,status_codes,post_type,follow_redirects,"
661 "retrieve_mode"
662 " from httpstep"
663 " where httptestid=" ZBX_FS_UI64
664 " order by no",
665 httptest->httptest.httptestid);
666
667 buffer = zbx_strdup(buffer, httptest->httptest.delay);
668 substitute_simple_macros(NULL, NULL, NULL, NULL, &host->hostid, NULL, NULL, NULL, NULL, NULL, &buffer,
669 MACRO_TYPE_COMMON, NULL, 0);
670
671 /* Avoid the potential usage of uninitialized values when: */
672 /* 1) compile without libCURL support */
673 /* 2) update interval is invalid */
674 db_httpstep.name = NULL;
675
676 if (SUCCEED != is_time_suffix(buffer, &delay, ZBX_LENGTH_UNLIMITED))
677 {
678 err_str = zbx_dsprintf(err_str, "update interval \"%s\" is invalid", buffer);
679 lastfailedstep = -1;
680 goto httptest_error;
681 }
682
683 #ifdef HAVE_LIBCURL
684 if (NULL == (easyhandle = curl_easy_init()))
685 {
686 err_str = zbx_strdup(err_str, "cannot initialize cURL library");
687 goto clean;
688 }
689
690 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_PROXY, httptest->httptest.http_proxy)) ||
691 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_COOKIEFILE, "")) ||
692 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_USERAGENT, httptest->httptest.agent)) ||
693 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_ERRORBUFFER, errbuf)))
694 {
695 err_str = zbx_strdup(err_str, curl_easy_strerror(err));
696 goto clean;
697 }
698
699 if (SUCCEED != zbx_http_prepare_ssl(easyhandle, httptest->httptest.ssl_cert_file,
700 httptest->httptest.ssl_key_file, httptest->httptest.ssl_key_password,
701 httptest->httptest.verify_peer, httptest->httptest.verify_host, &err_str))
702 {
703 goto clean;
704 }
705
706 httpstep.httptest = httptest;
707 httpstep.httpstep = &db_httpstep;
708
709 while (NULL != (row = DBfetch(result)) && ZBX_IS_RUNNING())
710 {
711 struct curl_slist *headers_slist = NULL;
712 char *header_cookie = NULL;
713 size_t (*curl_header_cb)(void *ptr, size_t size, size_t nmemb, void *userdata);
714 size_t (*curl_body_cb)(void *ptr, size_t size, size_t nmemb, void *userdata);
715
716 /* NOTE: do not break or return from this block! */
717 /* process_step_data() call is required! */
718
719 ZBX_STR2UINT64(db_httpstep.httpstepid, row[0]);
720 db_httpstep.httptestid = httptest->httptest.httptestid;
721 db_httpstep.no = atoi(row[1]);
722 db_httpstep.name = row[2];
723
724 db_httpstep.url = zbx_strdup(NULL, row[3]);
725 substitute_simple_macros_unmasked(NULL, NULL, NULL, NULL, NULL, host, NULL, NULL, NULL, NULL,
726 &db_httpstep.url, MACRO_TYPE_HTTPTEST_FIELD, NULL, 0);
727 http_substitute_variables(httptest, &db_httpstep.url);
728
729 db_httpstep.required = zbx_strdup(NULL, row[6]);
730 substitute_simple_macros(NULL, NULL, NULL, NULL, NULL, host, NULL, NULL, NULL, NULL,
731 &db_httpstep.required, MACRO_TYPE_HTTPTEST_FIELD, NULL, 0);
732
733 db_httpstep.status_codes = zbx_strdup(NULL, row[7]);
734 substitute_simple_macros(NULL, NULL, NULL, NULL, &host->hostid, NULL, NULL, NULL, NULL, NULL,
735 &db_httpstep.status_codes, MACRO_TYPE_COMMON, NULL, 0);
736
737 db_httpstep.post_type = atoi(row[8]);
738
739 if (ZBX_POSTTYPE_RAW == db_httpstep.post_type)
740 {
741 db_httpstep.posts = zbx_strdup(NULL, row[5]);
742 substitute_simple_macros_unmasked(NULL, NULL, NULL, NULL, NULL, host, NULL, NULL, NULL, NULL,
743 &db_httpstep.posts, MACRO_TYPE_HTTPTEST_FIELD, NULL, 0);
744 http_substitute_variables(httptest, &db_httpstep.posts);
745 }
746 else
747 db_httpstep.posts = NULL;
748
749 if (SUCCEED != httpstep_load_pairs(host, &httpstep))
750 {
751 err_str = zbx_strdup(err_str, "cannot load web scenario step data");
752 goto httpstep_error;
753 }
754
755 buffer = zbx_strdup(buffer, row[4]);
756 substitute_simple_macros(NULL, NULL, NULL, NULL, &host->hostid, NULL, NULL, NULL, NULL, NULL, &buffer,
757 MACRO_TYPE_COMMON, NULL, 0);
758
759 if (SUCCEED != is_time_suffix(buffer, &db_httpstep.timeout, ZBX_LENGTH_UNLIMITED))
760 {
761 err_str = zbx_dsprintf(err_str, "timeout \"%s\" is invalid", buffer);
762 goto httpstep_error;
763 }
764 else if (db_httpstep.timeout < 1 || SEC_PER_HOUR < db_httpstep.timeout)
765 {
766 err_str = zbx_dsprintf(err_str, "timeout \"%s\" is out of 1-3600 seconds bounds", buffer);
767 goto httpstep_error;
768 }
769
770 db_httpstep.follow_redirects = atoi(row[9]);
771 db_httpstep.retrieve_mode = atoi(row[10]);
772
773 memset(&stat, 0, sizeof(stat));
774
775 zabbix_log(LOG_LEVEL_DEBUG, "%s() use step \"%s\"", __func__, db_httpstep.name);
776 zabbix_log(LOG_LEVEL_DEBUG, "%s() use post \"%s\"", __func__, ZBX_NULL2EMPTY_STR(httpstep.posts));
777
778 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDS, httpstep.posts)))
779 {
780 err_str = zbx_strdup(err_str, curl_easy_strerror(err));
781 goto httpstep_error;
782 }
783
784 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_POST, (NULL != httpstep.posts &&
785 '\0' != *httpstep.posts) ? 1L : 0L)))
786 {
787 err_str = zbx_strdup(err_str, curl_easy_strerror(err));
788 goto httpstep_error;
789 }
790
791 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_FOLLOWLOCATION,
792 0 == db_httpstep.follow_redirects ? 0L : 1L)))
793 {
794 err_str = zbx_strdup(err_str, curl_easy_strerror(err));
795 goto httpstep_error;
796 }
797
798 if (0 != db_httpstep.follow_redirects)
799 {
800 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_MAXREDIRS, ZBX_CURLOPT_MAXREDIRS)))
801 {
802 err_str = zbx_strdup(err_str, curl_easy_strerror(err));
803 goto httpstep_error;
804 }
805 }
806
807 /* headers defined in a step overwrite headers defined in scenario */
808 if (NULL != httpstep.headers && '\0' != *httpstep.headers)
809 add_http_headers(httpstep.headers, &headers_slist, &header_cookie);
810 else if (NULL != httptest->headers && '\0' != *httptest->headers)
811 add_http_headers(httptest->headers, &headers_slist, &header_cookie);
812
813 err = curl_easy_setopt(easyhandle, CURLOPT_COOKIE, header_cookie);
814 zbx_free(header_cookie);
815
816 if (CURLE_OK != err)
817 {
818 err_str = zbx_strdup(err_str, curl_easy_strerror(err));
819 goto httpstep_error;
820 }
821
822 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_HTTPHEADER, headers_slist)))
823 {
824 err_str = zbx_strdup(err_str, curl_easy_strerror(err));
825 goto httpstep_error;
826 }
827
828 switch (db_httpstep.retrieve_mode)
829 {
830 case ZBX_RETRIEVE_MODE_CONTENT:
831 curl_header_cb = curl_ignore_cb;
832 curl_body_cb = curl_write_cb;
833 break;
834 case ZBX_RETRIEVE_MODE_BOTH:
835 curl_header_cb = curl_body_cb = curl_write_cb;
836 break;
837 case ZBX_RETRIEVE_MODE_HEADERS:
838 curl_header_cb = curl_write_cb;
839 curl_body_cb = curl_ignore_cb;
840 break;
841 default:
842 THIS_SHOULD_NEVER_HAPPEN;
843 err_str = zbx_strdup(err_str, "invalid retrieve mode");
844 goto httpstep_error;
845 }
846
847 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION, curl_body_cb)) ||
848 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_HEADERFUNCTION, curl_header_cb)))
849 {
850 err_str = zbx_strdup(err_str, curl_easy_strerror(err));
851 goto httpstep_error;
852 }
853
854 /* enable/disable fetching the body */
855 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_NOBODY,
856 ZBX_RETRIEVE_MODE_HEADERS == db_httpstep.retrieve_mode ? 1L : 0L)))
857 {
858 err_str = zbx_strdup(err_str, curl_easy_strerror(err));
859 goto httpstep_error;
860 }
861
862 if (SUCCEED != zbx_http_prepare_auth(easyhandle, httptest->httptest.authentication,
863 httptest->httptest.http_user, httptest->httptest.http_password, &err_str))
864 {
865 goto httpstep_error;
866 }
867
868 zabbix_log(LOG_LEVEL_DEBUG, "%s() go to URL \"%s\"", __func__, httpstep.url);
869
870 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_TIMEOUT, (long)db_httpstep.timeout)) ||
871 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_URL, httpstep.url)))
872 {
873 err_str = zbx_strdup(err_str, curl_easy_strerror(err));
874 goto httpstep_error;
875 }
876
877 /* try to retrieve page several times depending on number of retries */
878 do
879 {
880 memset(&page, 0, sizeof(page));
881 errbuf[0] = '\0';
882
883 if (CURLE_OK == (err = curl_easy_perform(easyhandle)))
884 break;
885
886 zbx_free(page.data);
887 }
888 while (0 < --httptest->httptest.retries);
889
890 curl_slist_free_all(headers_slist); /* must be called after curl_easy_perform() */
891
892 if (CURLE_OK == err)
893 {
894 char *var_err_str = NULL;
895
896 zabbix_log(LOG_LEVEL_TRACE, "%s() page.data from %s:'%s'", __func__, httpstep.url, page.data);
897
898 /* first get the data that is needed even if step fails */
899 if (CURLE_OK != (err = curl_easy_getinfo(easyhandle, CURLINFO_RESPONSE_CODE, &stat.rspcode)))
900 {
901 err_str = zbx_strdup(err_str, curl_easy_strerror(err));
902 }
903 else if ('\0' != *db_httpstep.status_codes &&
904 FAIL == int_in_list(db_httpstep.status_codes, stat.rspcode))
905 {
906 err_str = zbx_dsprintf(err_str, "response code \"%ld\" did not match any of the"
907 " required status codes \"%s\"", stat.rspcode,
908 db_httpstep.status_codes);
909 }
910
911 if (CURLE_OK != (err = curl_easy_getinfo(easyhandle, CURLINFO_TOTAL_TIME, &stat.total_time)) &&
912 NULL == err_str)
913 {
914 err_str = zbx_strdup(err_str, curl_easy_strerror(err));
915 }
916
917 if (CURLE_OK != (err = curl_easy_getinfo(easyhandle, CURLINFO_SPEED_DOWNLOAD,
918 &stat.speed_download)) && NULL == err_str)
919 {
920 err_str = zbx_strdup(err_str, curl_easy_strerror(err));
921 }
922 else
923 {
924 speed_download += stat.speed_download;
925 speed_download_num++;
926 }
927
928 /* required pattern */
929 if (NULL == err_str && '\0' != *db_httpstep.required &&
930 NULL == zbx_regexp_match(page.data, db_httpstep.required, NULL))
931 {
932 err_str = zbx_dsprintf(err_str, "required pattern \"%s\" was not found on %s",
933 db_httpstep.required, httpstep.url);
934 }
935
936 /* variables defined in scenario */
937 if (NULL == err_str && FAIL == http_process_variables(httptest, &httptest->variables, page.data,
938 &var_err_str))
939 {
940 char *variables = NULL;
941 size_t alloc_len = 0, offset;
942
943 httpstep_pairs_join(&variables, &alloc_len, &offset, "=", " ", &httptest->variables);
944
945 err_str = zbx_dsprintf(err_str, "error in scenario variables \"%s\": %s", variables,
946 var_err_str);
947
948 zbx_free(variables);
949 }
950
951 /* variables defined in a step */
952 if (NULL == err_str && FAIL == http_process_variables(httptest, &httpstep.variables, page.data,
953 &var_err_str))
954 {
955 char *variables = NULL;
956 size_t alloc_len = 0, offset;
957
958 httpstep_pairs_join(&variables, &alloc_len, &offset, "=", " ", &httpstep.variables);
959
960 err_str = zbx_dsprintf(err_str, "error in step variables \"%s\": %s", variables,
961 var_err_str);
962
963 zbx_free(variables);
964 }
965
966 zbx_free(var_err_str);
967
968 zbx_timespec(&ts);
969 process_step_data(db_httpstep.httpstepid, &stat, &ts);
970
971 zbx_free(page.data);
972 }
973 else
974 err_str = zbx_dsprintf(err_str, "%s: %s", curl_easy_strerror(err), errbuf);
975
976 httpstep_error:
977 zbx_free(db_httpstep.status_codes);
978 zbx_free(db_httpstep.required);
979 zbx_free(db_httpstep.posts);
980 zbx_free(db_httpstep.url);
981
982 httppairs_free(&httpstep.variables);
983
984 if (ZBX_POSTTYPE_FORM == httpstep.httpstep->post_type)
985 zbx_free(httpstep.posts);
986
987 zbx_free(httpstep.url);
988 zbx_free(httpstep.headers);
989
990 if (NULL != err_str)
991 {
992 lastfailedstep = db_httpstep.no;
993 break;
994 }
995 }
996 clean:
997 curl_easy_cleanup(easyhandle);
998 #else
999 err_str = zbx_strdup(err_str, "cURL library is required for Web monitoring support");
1000 #endif /* HAVE_LIBCURL */
1001
1002 httptest_error:
1003 zbx_timespec(&ts);
1004
1005 if (0 > lastfailedstep) /* update interval is invalid, delay is uninitialized */
1006 {
1007 DBexecute("update httptest set nextcheck=%d where httptestid=" ZBX_FS_UI64,
1008 0 > ts.sec ? ZBX_JAN_2038 : ts.sec, httptest->httptest.httptestid);
1009 }
1010 else if (0 > ts.sec + delay)
1011 {
1012 zabbix_log(LOG_LEVEL_WARNING, "nextcheck update causes overflow for web scenario \"%s\" on host \"%s\"",
1013 httptest->httptest.name, host->name);
1014 DBexecute("update httptest set nextcheck=%d where httptestid=" ZBX_FS_UI64,
1015 ZBX_JAN_2038, httptest->httptest.httptestid);
1016 }
1017 else
1018 {
1019 DBexecute("update httptest set nextcheck=%d where httptestid=" ZBX_FS_UI64,
1020 ts.sec + delay, httptest->httptest.httptestid);
1021 }
1022
1023 if (NULL != err_str)
1024 {
1025 if (0 >= lastfailedstep)
1026 {
1027 /* we are here because web scenario update interval is invalid, */
1028 /* cURL initialization failed or we have been compiled without cURL library */
1029
1030 lastfailedstep = 1;
1031 }
1032
1033 if (NULL != db_httpstep.name)
1034 {
1035 zabbix_log(LOG_LEVEL_DEBUG, "cannot process step \"%s\" of web scenario \"%s\" on host \"%s\": "
1036 "%s", db_httpstep.name, httptest->httptest.name, host->name, err_str);
1037 }
1038 }
1039 DBfree_result(result);
1040
1041 if (0 != speed_download_num)
1042 speed_download /= speed_download_num;
1043
1044 process_test_data(httptest->httptest.httptestid, lastfailedstep, speed_download, err_str, &ts);
1045
1046 zbx_free(buffer);
1047 zbx_free(err_str);
1048 zbx_preprocessor_flush();
1049
1050 zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
1051 }
1052
1053 /******************************************************************************
1054 * *
1055 * Function: process_httptests *
1056 * *
1057 * Purpose: process httptests *
1058 * *
1059 * Parameters: now - current timestamp *
1060 * *
1061 * Return value: number of processed httptests *
1062 * *
1063 * Author: Alexei Vladishev *
1064 * *
1065 * Comments: always SUCCEED *
1066 * *
1067 ******************************************************************************/
process_httptests(int httppoller_num,int now)1068 int process_httptests(int httppoller_num, int now)
1069 {
1070 DB_RESULT result;
1071 DB_ROW row;
1072 zbx_httptest_t httptest;
1073 DC_HOST host;
1074 int httptests_count = 0;
1075
1076 zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
1077
1078 /* create macro cache to use in http tests */
1079 zbx_vector_ptr_pair_create(&httptest.macros);
1080
1081 result = DBselect(
1082 "select h.hostid,h.host,h.name,t.httptestid,t.name,t.agent,"
1083 "t.authentication,t.http_user,t.http_password,t.http_proxy,t.retries,t.ssl_cert_file,"
1084 "t.ssl_key_file,t.ssl_key_password,t.verify_peer,t.verify_host,t.delay"
1085 " from httptest t,hosts h"
1086 " where t.hostid=h.hostid"
1087 " and t.nextcheck<=%d"
1088 " and " ZBX_SQL_MOD(t.httptestid,%d) "=%d"
1089 " and t.status=%d"
1090 " and h.proxy_hostid is null"
1091 " and h.status=%d"
1092 " and (h.maintenance_status=%d or h.maintenance_type=%d)",
1093 now,
1094 CONFIG_HTTPPOLLER_FORKS, httppoller_num - 1,
1095 HTTPTEST_STATUS_MONITORED,
1096 HOST_STATUS_MONITORED,
1097 HOST_MAINTENANCE_STATUS_OFF, MAINTENANCE_TYPE_NORMAL);
1098
1099 while (NULL != (row = DBfetch(result)) && ZBX_IS_RUNNING())
1100 {
1101 ZBX_STR2UINT64(host.hostid, row[0]);
1102 strscpy(host.host, row[1]);
1103 zbx_strlcpy_utf8(host.name, row[2], sizeof(host.name));
1104
1105 ZBX_STR2UINT64(httptest.httptest.httptestid, row[3]);
1106 httptest.httptest.name = row[4];
1107
1108 if (SUCCEED != httptest_load_pairs(&host, &httptest))
1109 {
1110 zabbix_log(LOG_LEVEL_WARNING, "cannot process web scenario \"%s\" on host \"%s\": "
1111 "cannot load web scenario data", httptest.httptest.name, host.name);
1112 THIS_SHOULD_NEVER_HAPPEN;
1113 continue;
1114 }
1115
1116 httptest.httptest.agent = zbx_strdup(NULL, row[5]);
1117 substitute_simple_macros(NULL, NULL, NULL, NULL, &host.hostid, NULL, NULL, NULL, NULL, NULL,
1118 &httptest.httptest.agent, MACRO_TYPE_COMMON, NULL, 0);
1119
1120 if (HTTPTEST_AUTH_NONE != (httptest.httptest.authentication = atoi(row[6])))
1121 {
1122 httptest.httptest.http_user = zbx_strdup(NULL, row[7]);
1123 substitute_simple_macros_unmasked(NULL, NULL, NULL, NULL, &host.hostid, NULL, NULL, NULL, NULL,
1124 NULL, &httptest.httptest.http_user, MACRO_TYPE_COMMON, NULL, 0);
1125
1126 httptest.httptest.http_password = zbx_strdup(NULL, row[8]);
1127 substitute_simple_macros_unmasked(NULL, NULL, NULL, NULL, &host.hostid, NULL, NULL, NULL, NULL,
1128 NULL, &httptest.httptest.http_password, MACRO_TYPE_COMMON, NULL, 0);
1129 }
1130
1131 if ('\0' != *row[9])
1132 {
1133 httptest.httptest.http_proxy = zbx_strdup(NULL, row[9]);
1134 substitute_simple_macros(NULL, NULL, NULL, NULL, &host.hostid, NULL, NULL, NULL, NULL, NULL,
1135 &httptest.httptest.http_proxy, MACRO_TYPE_COMMON, NULL, 0);
1136 }
1137 else
1138 httptest.httptest.http_proxy = NULL;
1139
1140 httptest.httptest.retries = atoi(row[10]);
1141
1142 httptest.httptest.ssl_cert_file = zbx_strdup(NULL, row[11]);
1143 substitute_simple_macros(NULL, NULL, NULL, NULL, NULL, &host, NULL, NULL, NULL, NULL,
1144 &httptest.httptest.ssl_cert_file, MACRO_TYPE_HTTPTEST_FIELD, NULL, 0);
1145
1146 httptest.httptest.ssl_key_file = zbx_strdup(NULL, row[12]);
1147 substitute_simple_macros(NULL, NULL, NULL, NULL, NULL, &host, NULL, NULL, NULL, NULL,
1148 &httptest.httptest.ssl_key_file, MACRO_TYPE_HTTPTEST_FIELD, NULL, 0);
1149
1150 httptest.httptest.ssl_key_password = zbx_strdup(NULL, row[13]);
1151 substitute_simple_macros_unmasked(NULL, NULL, NULL, NULL, &host.hostid, NULL, NULL, NULL, NULL, NULL,
1152 &httptest.httptest.ssl_key_password, MACRO_TYPE_COMMON, NULL, 0);
1153
1154 httptest.httptest.verify_peer = atoi(row[14]);
1155 httptest.httptest.verify_host = atoi(row[15]);
1156
1157 httptest.httptest.delay = row[16];
1158
1159 /* add httptest variables to the current test macro cache */
1160 http_process_variables(&httptest, &httptest.variables, NULL, NULL);
1161
1162 process_httptest(&host, &httptest);
1163
1164 zbx_free(httptest.httptest.ssl_key_password);
1165 zbx_free(httptest.httptest.ssl_key_file);
1166 zbx_free(httptest.httptest.ssl_cert_file);
1167 zbx_free(httptest.httptest.http_proxy);
1168
1169 if (HTTPTEST_AUTH_NONE != httptest.httptest.authentication)
1170 {
1171 zbx_free(httptest.httptest.http_password);
1172 zbx_free(httptest.httptest.http_user);
1173 }
1174 zbx_free(httptest.httptest.agent);
1175 zbx_free(httptest.headers);
1176 httppairs_free(&httptest.variables);
1177
1178 /* clear the macro cache used in this http test */
1179 httptest_remove_macros(&httptest);
1180
1181 httptests_count++; /* performance metric */
1182 }
1183 /* destroy the macro cache used in http tests */
1184 zbx_vector_ptr_pair_destroy(&httptest.macros);
1185
1186 DBfree_result(result);
1187
1188 zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
1189
1190 return httptests_count;
1191 }
1192