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 #ifdef HAVE_UNIXODBC
23 
24 #include <sql.h>
25 #include <sqlext.h>
26 #include <sqltypes.h>
27 
28 #include "odbc.h"
29 #include "log.h"
30 #include "zbxjson.h"
31 #include "zbxalgo.h"
32 
33 struct zbx_odbc_data_source
34 {
35 	SQLHENV	henv;
36 	SQLHDBC	hdbc;
37 };
38 
39 struct zbx_odbc_query_result
40 {
41 	SQLHSTMT	hstmt;
42 	SQLSMALLINT	col_num;
43 	char		**row;
44 };
45 
46 #define ZBX_FLAG_ODBC_NONE	0x00
47 #define ZBX_FLAG_ODBC_LLD	0x01
48 
49 /******************************************************************************
50  *                                                                            *
51  * Function: zbx_odbc_rc_str                                                  *
52  *                                                                            *
53  * Purpose: get human readable representation of ODBC return code             *
54  *                                                                            *
55  * Parameters: rc - [IN] ODBC return code                                     *
56  *                                                                            *
57  * Return value: human readable representation of error code or NULL if the   *
58  *               given code is unknown                                        *
59  *                                                                            *
60  ******************************************************************************/
zbx_odbc_rc_str(SQLRETURN rc)61 static const char	*zbx_odbc_rc_str(SQLRETURN rc)
62 {
63 	switch (rc)
64 	{
65 		case SQL_ERROR:
66 			return "SQL_ERROR";
67 		case SQL_SUCCESS_WITH_INFO:
68 			return "SQL_SUCCESS_WITH_INFO";
69 		case SQL_NO_DATA:
70 			return "SQL_NO_DATA";
71 		case SQL_INVALID_HANDLE:
72 			return "SQL_INVALID_HANDLE";
73 		case SQL_STILL_EXECUTING:
74 			return "SQL_STILL_EXECUTING";
75 		case SQL_NEED_DATA:
76 			return "SQL_NEED_DATA";
77 		case SQL_SUCCESS:
78 			return "SQL_SUCCESS";
79 		default:
80 			return NULL;
81 	}
82 }
83 
84 /******************************************************************************
85  *                                                                            *
86  * Function: zbx_odbc_diag                                                    *
87  *                                                                            *
88  * Purpose: diagnose result of ODBC function call                             *
89  *                                                                            *
90  * Parameters: h_type - [IN] type of handle call was executed on              *
91  *             h      - [IN] handle call was executed on                      *
92  *             rc     - [IN] function return code                             *
93  *             diag   - [OUT] diagnostic message                              *
94  *                                                                            *
95  * Return value: SUCCEED - function call was successful                       *
96  *               FAIL    - otherwise, error message is returned in diag       *
97  *                                                                            *
98  * Comments: It is caller's responsibility to free diag in case this function *
99  *           returns FAIL!                                                    *
100  *                                                                            *
101  ******************************************************************************/
zbx_odbc_diag(SQLSMALLINT h_type,SQLHANDLE h,SQLRETURN rc,char ** diag)102 static int	zbx_odbc_diag(SQLSMALLINT h_type, SQLHANDLE h, SQLRETURN rc, char **diag)
103 {
104 	const char	*rc_str = NULL;
105 	char		*buffer = NULL;
106 	size_t		alloc = 0, offset = 0;
107 
108 	if (SQL_ERROR == rc || SQL_SUCCESS_WITH_INFO == rc)
109 	{
110 		SQLCHAR		sql_state[SQL_SQLSTATE_SIZE + 1], err_msg[128];
111 		SQLINTEGER	err_code = 0;
112 		SQLSMALLINT	rec_nr = 1;
113 
114 		while (0 != SQL_SUCCEEDED(SQLGetDiagRec(h_type, h, rec_nr++, sql_state, &err_code, err_msg,
115 				sizeof(err_msg), NULL)))
116 		{
117 			zbx_chrcpy_alloc(&buffer, &alloc, &offset, (NULL == buffer ? ':' : '|'));
118 			zbx_snprintf_alloc(&buffer, &alloc, &offset, "[%s][%ld][%s]", sql_state, (long)err_code, err_msg);
119 		}
120 	}
121 
122 	if (0 != SQL_SUCCEEDED(rc))
123 	{
124 		if (NULL == (rc_str = zbx_odbc_rc_str(rc)))
125 		{
126 			zabbix_log(LOG_LEVEL_TRACE, "%s(): [%d (unknown SQLRETURN code)]%s", __func__,
127 					(int)rc, ZBX_NULL2EMPTY_STR(buffer));
128 		}
129 		else
130 			zabbix_log(LOG_LEVEL_TRACE, "%s(): [%s]%s", __func__, rc_str, ZBX_NULL2EMPTY_STR(buffer));
131 	}
132 	else
133 	{
134 		if (NULL == (rc_str = zbx_odbc_rc_str(rc)))
135 		{
136 			*diag = zbx_dsprintf(*diag, "[%d (unknown SQLRETURN code)]%s",
137 					(int)rc, ZBX_NULL2EMPTY_STR(buffer));
138 		}
139 		else
140 			*diag = zbx_dsprintf(*diag, "[%s]%s", rc_str, ZBX_NULL2EMPTY_STR(buffer));
141 
142 		zabbix_log(LOG_LEVEL_TRACE, "%s(): %s", __func__, *diag);
143 	}
144 
145 	zbx_free(buffer);
146 
147 	return 0 != SQL_SUCCEEDED(rc) ? SUCCEED : FAIL;
148 }
149 
150 /******************************************************************************
151  *                                                                            *
152  * Function: zbx_log_odbc_connection_info                                     *
153  *                                                                            *
154  * Purpose: log details upon successful connection on behalf of caller        *
155  *                                                                            *
156  * Parameters: function - [IN] caller function name                           *
157  *             hdbc     - [IN] ODBC connection handle                         *
158  *                                                                            *
159  ******************************************************************************/
zbx_log_odbc_connection_info(const char * function,SQLHDBC hdbc)160 static void	zbx_log_odbc_connection_info(const char *function, SQLHDBC hdbc)
161 {
162 	if (SUCCEED == ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_DEBUG))
163 	{
164 		char		driver_name[MAX_STRING_LEN + 1], driver_ver[MAX_STRING_LEN + 1],
165 				db_name[MAX_STRING_LEN + 1], db_ver[MAX_STRING_LEN + 1], *diag = NULL;
166 		SQLRETURN	rc;
167 
168 		rc = SQLGetInfo(hdbc, SQL_DRIVER_NAME, driver_name, MAX_STRING_LEN, NULL);
169 
170 		if (SUCCEED != zbx_odbc_diag(SQL_HANDLE_DBC, hdbc, rc, &diag))
171 		{
172 			zabbix_log(LOG_LEVEL_DEBUG, "Cannot obtain driver name: %s", diag);
173 			zbx_strlcpy(driver_name, "unknown", sizeof(driver_name));
174 		}
175 
176 		rc = SQLGetInfo(hdbc, SQL_DRIVER_VER, driver_ver, MAX_STRING_LEN, NULL);
177 
178 		if (SUCCEED != zbx_odbc_diag(SQL_HANDLE_DBC, hdbc, rc, &diag))
179 		{
180 			zabbix_log(LOG_LEVEL_DEBUG, "Cannot obtain driver version: %s", diag);
181 			zbx_strlcpy(driver_ver, "unknown", sizeof(driver_ver));
182 		}
183 
184 		rc = SQLGetInfo(hdbc, SQL_DBMS_NAME, db_name, MAX_STRING_LEN, NULL);
185 
186 		if (SUCCEED != zbx_odbc_diag(SQL_HANDLE_DBC, hdbc, rc, &diag))
187 		{
188 			zabbix_log(LOG_LEVEL_DEBUG, "Cannot obtain database name: %s", diag);
189 			zbx_strlcpy(db_name, "unknown", sizeof(db_name));
190 		}
191 
192 		rc = SQLGetInfo(hdbc, SQL_DBMS_VER, db_ver, MAX_STRING_LEN, NULL);
193 
194 		if (SUCCEED != zbx_odbc_diag(SQL_HANDLE_DBC, hdbc, rc, &diag))
195 		{
196 			zabbix_log(LOG_LEVEL_DEBUG, "Cannot obtain database version: %s", diag);
197 			zbx_strlcpy(db_ver, "unknown", sizeof(db_ver));
198 		}
199 
200 		zabbix_log(LOG_LEVEL_DEBUG, "%s() connected to %s(%s) using %s(%s)", function,
201 				db_name, db_ver, driver_name, driver_ver);
202 		zbx_free(diag);
203 	}
204 }
205 
206 /******************************************************************************
207  *                                                                            *
208  * Function: zbx_odbc_connection_string_append                                *
209  *                                                                            *
210  * Purpose: Appends a new argument to ODBC connection string.                 *
211  *          Connection string is reallocated to fit new value.                *
212  *                                                                            *
213  * Parameters: connection_str - [IN/OUT] connection string                    *
214  *             attribute      - [IN] attribute name                           *
215  *             value          - [IN] attribute value                          *
216  *                                                                            *
217  ******************************************************************************/
zbx_odbc_connection_string_append(char ** connection_str,const char * attribute,const char * value)218 static void zbx_odbc_connection_string_append(char **connection_str, const char *attribute, const char *value)
219 {
220 	size_t	len;
221 	char	last = '\0';
222 
223 	if (NULL == value)
224 		return;
225 
226 	if (0 < (len = strlen(*connection_str)))
227 		last = (*connection_str)[len-1];
228 
229 	*connection_str = zbx_dsprintf(*connection_str, "%s%s%s=%s", *connection_str, ';' == last ? "" : ";",
230 			attribute, value);
231 }
232 
233 /******************************************************************************
234  *                                                                            *
235  * Function: zbx_odbc_connect                                                 *
236  *                                                                            *
237  * Purpose: connect to ODBC data source                                       *
238  *                                                                            *
239  * Parameters: dsn        - [IN] data source name                             *
240  *             connection - [IN] connection string                            *
241  *             user       - [IN] user name                                    *
242  *             pass       - [IN] password                                     *
243  *             timeout    - [IN] timeout                                      *
244  *             error      - [OUT] error message                               *
245  *                                                                            *
246  * Return value: pointer to opaque data source data structure or NULL in case *
247  *               of failure, allocated error message is returned in error     *
248  *                                                                            *
249  * Comments: It is caller's responsibility to free error buffer!              *
250  *                                                                            *
251  ******************************************************************************/
zbx_odbc_connect(const char * dsn,const char * connection,const char * user,const char * pass,int timeout,char ** error)252 zbx_odbc_data_source_t	*zbx_odbc_connect(const char *dsn, const char *connection, const char *user, const char *pass,
253 		int timeout, char **error)
254 {
255 	char			*diag = NULL;
256 	zbx_odbc_data_source_t	*data_source = NULL;
257 	SQLRETURN		rc;
258 
259 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() dsn:'%s' user:'%s'", __func__, dsn, user);
260 
261 	data_source = (zbx_odbc_data_source_t *)zbx_malloc(data_source, sizeof(zbx_odbc_data_source_t));
262 
263 	if (0 != SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &data_source->henv)))
264 	{
265 		rc = SQLSetEnvAttr(data_source->henv, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);
266 
267 		if (SUCCEED == zbx_odbc_diag(SQL_HANDLE_ENV, data_source->henv, rc, &diag))
268 		{
269 			rc = SQLAllocHandle(SQL_HANDLE_DBC, data_source->henv, &data_source->hdbc);
270 
271 			if(SUCCEED == zbx_odbc_diag(SQL_HANDLE_ENV, data_source->henv, rc, &diag))
272 			{
273 				rc = SQLSetConnectAttr(data_source->hdbc, (SQLINTEGER)SQL_LOGIN_TIMEOUT,
274 						(SQLPOINTER)(intptr_t)timeout, (SQLINTEGER)0);
275 
276 				if (SUCCEED == zbx_odbc_diag(SQL_HANDLE_DBC, data_source->hdbc, rc, &diag))
277 				{
278 					/* look for user in data source instead of no user */
279 					if ('\0' == *user)
280 						user = NULL;
281 
282 					/* look for password in data source instead of no password */
283 					if ('\0' == *pass)
284 						pass = NULL;
285 
286 					if (NULL != connection && '\0' != *connection)
287 					{
288 						char	*connection_str;
289 
290 						connection_str = NULL;
291 
292 						if (NULL != user || NULL != pass)
293 						{
294 							connection_str = zbx_strdup(NULL, connection);
295 							zbx_odbc_connection_string_append(&connection_str, "UID", user);
296 							zbx_odbc_connection_string_append(&connection_str, "PWD", pass);
297 							connection = connection_str;
298 						}
299 
300 						rc = SQLDriverConnect(data_source->hdbc, NULL,
301 								(SQLCHAR *)connection, SQL_NTS, NULL, 0, NULL,
302 								SQL_DRIVER_NOPROMPT);
303 
304 						zbx_free(connection_str);
305 					}
306 					else
307 					{
308 						rc = SQLConnect(data_source->hdbc, (SQLCHAR *)dsn, SQL_NTS,
309 								(SQLCHAR *)user, SQL_NTS, (SQLCHAR *)pass, SQL_NTS);
310 					}
311 
312 					if (SUCCEED == zbx_odbc_diag(SQL_HANDLE_DBC, data_source->hdbc, rc, &diag))
313 					{
314 						zbx_log_odbc_connection_info(__func__, data_source->hdbc);
315 						goto out;
316 					}
317 
318 					*error = zbx_dsprintf(*error, "Cannot connect to ODBC DSN: %s", diag);
319 				}
320 				else
321 					*error = zbx_dsprintf(*error, "Cannot set ODBC login timeout: %s", diag);
322 
323 				SQLFreeHandle(SQL_HANDLE_DBC, data_source->hdbc);
324 			}
325 			else
326 				*error = zbx_dsprintf(*error, "Cannot create ODBC connection handle: %s", diag);
327 		}
328 		else
329 			*error = zbx_dsprintf(*error, "Cannot set ODBC version: %s", diag);
330 
331 		SQLFreeHandle(SQL_HANDLE_ENV, data_source->henv);
332 	}
333 	else
334 		*error = zbx_strdup(*error, "Cannot create ODBC environment handle.");
335 
336 	zbx_free(data_source);
337 out:
338 	zbx_free(diag);
339 
340 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
341 
342 	return data_source;
343 }
344 
345 /******************************************************************************
346  *                                                                            *
347  * Function: zbx_odbc_data_source_free                                        *
348  *                                                                            *
349  * Purpose: free resources allocated by successful zbx_odbc_connect() call    *
350  *                                                                            *
351  * Parameters: data_source - [IN] pointer to data source structure            *
352  *                                                                            *
353  * Comments: Input parameter data_source must be obtained using               *
354  *           zbx_odbc_connect() and must not be NULL.                         *
355  *                                                                            *
356  ******************************************************************************/
zbx_odbc_data_source_free(zbx_odbc_data_source_t * data_source)357 void	zbx_odbc_data_source_free(zbx_odbc_data_source_t *data_source)
358 {
359 	SQLDisconnect(data_source->hdbc);
360 	SQLFreeHandle(SQL_HANDLE_DBC, data_source->hdbc);
361 	SQLFreeHandle(SQL_HANDLE_ENV, data_source->henv);
362 	zbx_free(data_source);
363 }
364 
365 /******************************************************************************
366  *                                                                            *
367  * Function: zbx_odbc_select                                                  *
368  *                                                                            *
369  * Purpose: execute a query to ODBC data source                               *
370  *                                                                            *
371  * Parameters: data_source - [IN] pointer to data source structure            *
372  *             query       - [IN] SQL query                                   *
373  *             error       - [OUT] error message                              *
374  *                                                                            *
375  * Return value: pointer to opaque query result structure or NULL in case of  *
376  *               failure, allocated error message is returned in error        *
377  *                                                                            *
378  * Comments: It is caller's responsibility to free error buffer!              *
379  *                                                                            *
380  ******************************************************************************/
zbx_odbc_select(const zbx_odbc_data_source_t * data_source,const char * query,char ** error)381 zbx_odbc_query_result_t	*zbx_odbc_select(const zbx_odbc_data_source_t *data_source, const char *query, char **error)
382 {
383 	char			*diag = NULL;
384 	zbx_odbc_query_result_t	*query_result = NULL;
385 	SQLRETURN		rc;
386 
387 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() query:'%s'", __func__, query);
388 
389 	if (NULL == query || '\0' == *query)
390 	{
391 		*error = zbx_strdup(*error, "SQL query cannot be empty.");
392 		goto out;
393 	}
394 
395 	query_result = (zbx_odbc_query_result_t *)zbx_malloc(query_result, sizeof(zbx_odbc_query_result_t));
396 
397 	rc = SQLAllocHandle(SQL_HANDLE_STMT, data_source->hdbc, &query_result->hstmt);
398 
399 	if (SUCCEED == zbx_odbc_diag(SQL_HANDLE_DBC, data_source->hdbc, rc, &diag))
400 	{
401 		rc = SQLExecDirect(query_result->hstmt, (SQLCHAR *)query, SQL_NTS);
402 
403 		if (SUCCEED == zbx_odbc_diag(SQL_HANDLE_STMT, query_result->hstmt, rc, &diag))
404 		{
405 			rc = SQLNumResultCols(query_result->hstmt, &query_result->col_num);
406 
407 			if (SUCCEED == zbx_odbc_diag(SQL_HANDLE_STMT, query_result->hstmt, rc, &diag))
408 			{
409 				SQLSMALLINT	i;
410 
411 				query_result->row = (char **)zbx_malloc(NULL, sizeof(char *) * (size_t)query_result->col_num);
412 
413 				for (i = 0; ; i++)
414 				{
415 					if (i == query_result->col_num)
416 					{
417 						zabbix_log(LOG_LEVEL_DEBUG, "selected all %d columns",
418 								(int)query_result->col_num);
419 						goto out;
420 					}
421 
422 					query_result->row[i] = NULL;
423 				}
424 			}
425 			else
426 				*error = zbx_dsprintf(*error, "Cannot get number of columns in ODBC result: %s", diag);
427 		}
428 		else
429 			*error = zbx_dsprintf(*error, "Cannot execute ODBC query: %s", diag);
430 
431 		SQLFreeHandle(SQL_HANDLE_STMT, query_result->hstmt);
432 	}
433 	else
434 		*error = zbx_dsprintf(*error, "Cannot create ODBC statement handle: %s", diag);
435 
436 	zbx_free(query_result);
437 out:
438 	zbx_free(diag);
439 
440 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
441 
442 	return query_result;
443 }
444 
445 /******************************************************************************
446  *                                                                            *
447  * Function: zbx_odbc_query_result_free                                       *
448  *                                                                            *
449  * Purpose: free resources allocated by successful zbx_odbc_select() call     *
450  *                                                                            *
451  * Parameters: query_result - [IN] pointer to query result structure          *
452  *                                                                            *
453  * Comments: Input parameter query_result must be obtained using              *
454  *           zbx_odbc_select() and must not be NULL.                          *
455  *                                                                            *
456  ******************************************************************************/
zbx_odbc_query_result_free(zbx_odbc_query_result_t * query_result)457 void	zbx_odbc_query_result_free(zbx_odbc_query_result_t *query_result)
458 {
459 	SQLSMALLINT	i;
460 
461 	SQLFreeHandle(SQL_HANDLE_STMT, query_result->hstmt);
462 
463 	for (i = 0; i < query_result->col_num; i++)
464 		zbx_free(query_result->row[i]);
465 
466 	zbx_free(query_result->row);
467 	zbx_free(query_result);
468 }
469 
470 /******************************************************************************
471  *                                                                            *
472  * Function: zbx_odbc_fetch                                                   *
473  *                                                                            *
474  * Purpose: fetch single row of ODBC query result                             *
475  *                                                                            *
476  * Parameters: query_result - [IN] pointer to query result structure          *
477  *                                                                            *
478  * Return value: array of strings or NULL (see Comments)                      *
479  *                                                                            *
480  * Comments: NULL result can signify both end of rows (which is normal) and   *
481  *           failure. There is currently no way to distinguish these cases.   *
482  *           There is no need to free strings returned by this function.      *
483  *           Lifetime of strings is limited to next call of zbx_odbc_fetch()  *
484  *           or zbx_odbc_query_result_free(), caller needs to make a copy if  *
485  *           result is needed for longer.                                     *
486  *                                                                            *
487  ******************************************************************************/
zbx_odbc_fetch(zbx_odbc_query_result_t * query_result)488 static const char	*const *zbx_odbc_fetch(zbx_odbc_query_result_t *query_result)
489 {
490 	char		*diag = NULL;
491 	SQLRETURN	rc;
492 	SQLSMALLINT	i;
493 	const char	*const *row = NULL;
494 
495 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
496 
497 	if (SQL_NO_DATA == (rc = SQLFetch(query_result->hstmt)))
498 	{
499 		/* end of rows */
500 		goto out;
501 	}
502 
503 	if (SUCCEED != zbx_odbc_diag(SQL_HANDLE_STMT, query_result->hstmt, rc, &diag))
504 	{
505 		zabbix_log(LOG_LEVEL_DEBUG, "Cannot fetch row: %s", diag);
506 		goto out;
507 	}
508 
509 	for (i = 0; i < query_result->col_num; i++)
510 	{
511 		size_t		alloc = 0, offset = 0;
512 		char		buffer[MAX_STRING_LEN + 1];
513 		SQLLEN		len;
514 
515 		zbx_free(query_result->row[i]);
516 
517 		/* force len to integer value for DB2 compatibility */
518 		do
519 		{
520 			rc = SQLGetData(query_result->hstmt, i + 1, SQL_C_CHAR, buffer, MAX_STRING_LEN, &len);
521 
522 			if (SUCCEED != zbx_odbc_diag(SQL_HANDLE_STMT, query_result->hstmt, rc, &diag))
523 			{
524 				zabbix_log(LOG_LEVEL_DEBUG, "Cannot get column data: %s", diag);
525 				goto out;
526 			}
527 
528 			if (SQL_NULL_DATA == (int)len)
529 				break;
530 
531 			zbx_strcpy_alloc(&query_result->row[i], &alloc, &offset, buffer);
532 		}
533 		while (SQL_SUCCESS != rc);
534 
535 		if (NULL != query_result->row[i])
536 			zbx_rtrim(query_result->row[i], " ");
537 
538 		zabbix_log(LOG_LEVEL_DEBUG, "column #%d value:'%s'", (int)i + 1, ZBX_NULL2STR(query_result->row[i]));
539 	}
540 
541 	row = (const char *const *)query_result->row;
542 out:
543 	zbx_free(diag);
544 
545 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
546 
547 	return row;
548 }
549 
550 /******************************************************************************
551  *                                                                            *
552  * Function: zbx_odbc_query_result_to_string                                  *
553  *                                                                            *
554  * Purpose: extract the first column of the first row of ODBC SQL query       *
555  *                                                                            *
556  * Parameters: query_result - [IN] result of SQL query                        *
557  *             string       - [OUT] the first column of the first row         *
558  *             error        - [OUT] error message                             *
559  *                                                                            *
560  * Return value: SUCCEED - result wasn't empty, the first column of the first *
561  *                         result row is not NULL and is returned in string   *
562  *                         parameter, error remains untouched in this case    *
563  *               FAIL    - otherwise, allocated error message is returned in  *
564  *                         error parameter, string remains untouched          *
565  *                                                                            *
566  * Comments: It is caller's responsibility to free allocated buffers!         *
567  *                                                                            *
568  ******************************************************************************/
zbx_odbc_query_result_to_string(zbx_odbc_query_result_t * query_result,char ** string,char ** error)569 int	zbx_odbc_query_result_to_string(zbx_odbc_query_result_t *query_result, char **string, char **error)
570 {
571 	const char	*const *row;
572 	int		ret = FAIL;
573 
574 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
575 
576 	if (NULL != (row = zbx_odbc_fetch(query_result)))
577 	{
578 		if (NULL != row[0])
579 		{
580 			*string = zbx_strdup(*string, row[0]);
581 			zbx_replace_invalid_utf8(*string);
582 			ret = SUCCEED;
583 		}
584 		else
585 			*error = zbx_strdup(*error, "SQL query returned NULL value.");
586 	}
587 	else
588 		*error = zbx_strdup(*error, "SQL query returned empty result.");
589 
590 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
591 
592 	return ret;
593 }
594 
595 /******************************************************************************
596  *                                                                            *
597  * Function: odbc_query_result_to_json                                        *
598  *                                                                            *
599  * Purpose: convert ODBC SQL query result into JSON                           *
600  *                                                                            *
601  * Parameters: query_result - [IN] result of SQL query                        *
602  *             flags        - [IN] specify if column names must be converted  *
603  *                                 to LLD macros or preserved as they are     *
604  *             out_json     - [OUT] query result converted to JSON            *
605  *             error        - [OUT] error message                             *
606  *                                                                            *
607  * Return value: SUCCEED - conversion was successful and allocated LLD JSON   *
608  *                         is returned in lld_json parameter, error remains   *
609  *                         untouched in this case                             *
610  *               FAIL    - otherwise, allocated error message is returned in  *
611  *                         error parameter, lld_json remains untouched        *
612  *                                                                            *
613  * Comments: It is caller's responsibility to free allocated buffers!         *
614  *                                                                            *
615  ******************************************************************************/
odbc_query_result_to_json(zbx_odbc_query_result_t * query_result,int flags,char ** out_json,char ** error)616 static int	odbc_query_result_to_json(zbx_odbc_query_result_t *query_result, int flags, char **out_json,
617 		char **error)
618 {
619 	const char		*const *row;
620 	struct zbx_json		json;
621 	zbx_vector_str_t	names;
622 	int			ret = FAIL, i, j;
623 
624 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
625 
626 	zbx_vector_str_create(&names);
627 	zbx_vector_str_reserve(&names, query_result->col_num);
628 
629 	for (i = 0; i < query_result->col_num; i++)
630 	{
631 		char		str[MAX_STRING_LEN], *p;
632 		SQLRETURN	rc;
633 		SQLSMALLINT	len;
634 
635 		rc = SQLColAttribute(query_result->hstmt, i + 1, SQL_DESC_LABEL, str, sizeof(str), &len, NULL);
636 
637 		if (SQL_SUCCESS != rc || sizeof(str) <= (size_t)len || '\0' == *str)
638 		{
639 			*error = zbx_dsprintf(*error, "Cannot obtain column #%d name.", i + 1);
640 			goto out;
641 		}
642 
643 		zabbix_log(LOG_LEVEL_DEBUG, "column #%d name:'%s'", i + 1, str);
644 
645 		if (flags & ZBX_FLAG_ODBC_LLD)
646 		{
647 			for (p = str; '\0' != *p; p++)
648 			{
649 				if (0 != isalpha((unsigned char)*p))
650 					*p = toupper((unsigned char)*p);
651 
652 				if (SUCCEED != is_macro_char(*p))
653 				{
654 					*error = zbx_dsprintf(*error, "Cannot convert column #%d name to macro.", i + 1);
655 					goto out;
656 				}
657 			}
658 
659 			zbx_vector_str_append(&names, zbx_dsprintf(NULL, "{#%s}", str));
660 
661 			for (j = 0; j < i; j++)
662 			{
663 				if (0 == strcmp(names.values[i], names.values[j]))
664 				{
665 					*error = zbx_dsprintf(*error, "Duplicate macro name: %s.", names.values[i]);
666 					goto out;
667 				}
668 			}
669 		}
670 		else
671 		{
672 			char	*name;
673 
674 			zbx_replace_invalid_utf8((name = zbx_strdup(NULL, str)));
675 			zbx_vector_str_append(&names, name);
676 		}
677 	}
678 
679 	zbx_json_initarray(&json, ZBX_JSON_STAT_BUF_LEN);
680 
681 	while (NULL != (row = zbx_odbc_fetch(query_result)))
682 	{
683 		zbx_json_addobject(&json, NULL);
684 
685 		for (i = 0; i < query_result->col_num; i++)
686 		{
687 			char	*value = NULL;
688 
689 			if (NULL != row[i])
690 			{
691 				value = zbx_strdup(value, row[i]);
692 				zbx_replace_invalid_utf8(value);
693 			}
694 
695 			zbx_json_addstring(&json, names.values[i], value, ZBX_JSON_TYPE_STRING);
696 			zbx_free(value);
697 		}
698 
699 		zbx_json_close(&json);
700 	}
701 
702 	zbx_json_close(&json);
703 
704 	*out_json = zbx_strdup(*out_json, json.buffer);
705 
706 	zbx_json_free(&json);
707 
708 	ret = SUCCEED;
709 out:
710 	zbx_vector_str_clear_ext(&names, zbx_str_free);
711 	zbx_vector_str_destroy(&names);
712 
713 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
714 
715 	return ret;
716 }
717 
718 /******************************************************************************
719  *                                                                            *
720  * Function: zbx_odbc_query_result_to_lld_json                                *
721  *                                                                            *
722  * Purpose: public wrapper for odbc_query_result_to_json                      *
723  *                                                                            *
724  *****************************************************************************/
zbx_odbc_query_result_to_lld_json(zbx_odbc_query_result_t * query_result,char ** lld_json,char ** error)725 int	zbx_odbc_query_result_to_lld_json(zbx_odbc_query_result_t *query_result, char **lld_json, char **error)
726 {
727 	return odbc_query_result_to_json(query_result, ZBX_FLAG_ODBC_LLD, lld_json, error);
728 }
729 
730 /******************************************************************************
731  *                                                                            *
732  * Function: zbx_odbc_query_result_to_json                                    *
733  *                                                                            *
734  * Purpose: public wrapper for odbc_query_result_to_json                      *
735  *                                                                            *
736  *****************************************************************************/
zbx_odbc_query_result_to_json(zbx_odbc_query_result_t * query_result,char ** out_json,char ** error)737 int	zbx_odbc_query_result_to_json(zbx_odbc_query_result_t *query_result, char **out_json, char **error)
738 {
739 	return odbc_query_result_to_json(query_result, ZBX_FLAG_ODBC_NONE, out_json, error);
740 }
741 
742 #endif	/* HAVE_UNIXODBC */
743