security: fix SQL injection, buffer overflow, and input validation (#519)

## Summary

Fix five security issues found during code review:

1. **SQL injection via unescaped SNMP result in INSERT**
(`poller.c:1844`): `poller_items[i].result` and `rrd_name` flow directly
into SQL VALUES clause. Added `db_escape()` calls for both values before
interpolation.

2. **Buffer overflow in `buffer_output_errors`** (`poller.c:2047`):
`snprintf` used `DBL_BUFSIZE` as the size limit instead of the remaining
buffer space `DBL_BUFSIZE - *buf_size`, allowing a write past the end of
`error_string`.

3. **SQL injection via `--hostlist` CLI argument** (`spine.c:388`): The
`host_id_list` string is interpolated into SQL queries without
validation. Added input validation to reject any characters other than
digits, commas, and spaces.

4. **Undocumented command injection trust boundary** (`nft_popen.c:126`,
`poller.c:2245`): `nft_popen()` passes commands to `/bin/sh -c` without
shell escaping. Added warning comments documenting that callers must
ensure commands originate from the Cacti database.

5. **Unbounded `sprintf` calls in `util.c`** (lines 99, 141, 145, 191,
286, 2036): Replaced all 6 `sprintf(qstring, ...)` calls with
`snprintf(qstring, sizeof(qstring), ...)` to prevent buffer overflows.

## Files changed

- `poller.c` - escape SQL values (fix 1), bound snprintf (fix 2),
document trust boundary (fix 4)
- `spine.c` - validate --hostlist input (fix 3)
- `nft_popen.c` - document trust boundary (fix 4)
- `util.c` - replace sprintf with snprintf (fix 5)

## Test plan

- [x] `make clean && make` compiles without errors or warnings
- [ ] Run spine against a test Cacti instance with SNMP results
containing single quotes
- [ ] Run spine with `--hostlist "1; DROP TABLE"` and verify it exits
with error
- [ ] Verify poller output results are correctly escaped in the database

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
This commit is contained in:
Thomas Vincent authored and GitHub committed 2026-03-26 07:45:48 -04:00
1 parent 70459ac82c
commit 926d5b66af
7 files changed
+230 -139

No files matched your search

+3
View File
@@ -123,6 +123,9 @@ static void close_cleanup(void *);
*
*------------------------------------------------------------------------------
*/
/* WARNING: command is passed to /bin/sh -c without shell escaping.
* The caller MUST ensure command originates from a trusted source
* (the Cacti database). Do not pass user-controlled input directly. */
int nft_popen(const char * command, const char * type) {
struct pid *cur;
struct pid *p;
+8 -1
View File
@@ -404,8 +404,15 @@ int ping_icmp(host_t *host, ping_t *ping) {
/* reinitialize fd_set -- select(2) clears bits in place on return */
keep_listening:
FD_ZERO(&socket_fds);
if (icmp_socket >= FD_SETSIZE) {
SPINE_LOG(("ERROR: Device[%i] ICMP socket %d exceeds FD_SETSIZE %d", host->id, icmp_socket, FD_SETSIZE));
snprintf(ping->ping_status, 50, "down");
snprintf(ping->ping_response, SMALL_BUFSIZE, "ICMP: fd exceeds FD_SETSIZE");
close(icmp_socket);
return HOST_DOWN;
}
FD_SET(icmp_socket,&socket_fds);
return_code = select(FD_SETSIZE, &socket_fds, NULL, NULL, &timeout);
return_code = select(icmp_socket + 1, &socket_fds, NULL, NULL, &timeout);
/* record end time */
end_time = get_time_as_double();
+43 -8
View File
@@ -775,6 +775,9 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread
/* update host table */
if (host_thread == 1) {
char escaped_last_error[BUFSIZE];
db_escape(&mysql, escaped_last_error, sizeof(escaped_last_error), host->status_last_error);
if (!ignore_sysinfo) {
if (host->ignore_host != TRUE) {
snprintf(update_sql, BIG_BUFSIZE, "UPDATE host "
@@ -789,7 +792,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread
host->status_event_count,
host->status_fail_date,
host->status_rec_date,
host->status_last_error,
escaped_last_error,
host->min_time,
host->max_time,
host->cur_time,
@@ -815,7 +818,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread
host->status_event_count,
host->status_fail_date,
host->status_rec_date,
host->status_last_error,
escaped_last_error,
host->min_time,
host->max_time,
host->cur_time,
@@ -836,7 +839,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread
host->status_event_count,
host->status_fail_date,
host->status_rec_date,
host->status_last_error,
escaped_last_error,
host->min_time,
host->max_time,
host->cur_time,
@@ -1014,6 +1017,13 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread
break;
case POLLER_ACTION_SCRIPT: /* script (popen) */
/* Reject empty script commands that could cause unexpected behavior */
if (strlen(reindex->arg1) == 0) {
SPINE_LOG(("WARNING: Device[%i] HT[%i] DQ[%i] empty script command, skipping",
host->id, host_thread, reindex->data_query_id));
break;
}
poll_result = trim(exec_poll(host, reindex->arg1, reindex->data_query_id, "DQ"));
if (is_debug_device(host->id)) {
@@ -1600,6 +1610,14 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread
break;
case POLLER_ACTION_SCRIPT: /* execute script file */
/* Reject empty script commands that could cause unexpected behavior */
if (strlen(poller_items[i].arg1) == 0) {
SPINE_LOG(("WARNING: Device[%i] HT[%i] DS[%i] empty script command, skipping",
host_id, host_thread, poller_items[i].local_data_id));
SET_UNDEFINED(poller_items[i].result);
break;
}
poll_result = exec_poll(host, poller_items[i].arg1, poller_items[i].local_data_id, "DS");
/* process the result */
@@ -1656,6 +1674,14 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread
break;
case POLLER_ACTION_PHP_SCRIPT_SERVER: /* execute script server */
/* Reject empty script commands that could cause unexpected behavior */
if (strlen(poller_items[i].arg1) == 0) {
SPINE_LOG(("WARNING: Device[%i] HT[%i] DS[%i] empty script server command, skipping",
host_id, host_thread, poller_items[i].local_data_id));
SET_UNDEFINED(poller_items[i].result);
break;
}
php_process = php_get_process();
poll_result = php_cmd(poller_items[i].arg1, php_process);
@@ -1841,11 +1867,17 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread
i = 0;
while (i < rows_processed) {
char escaped_result[DBL_BUFSIZE];
char escaped_rrd_name[DBL_BUFSIZE];
db_escape(&mysqlt, escaped_result, sizeof(escaped_result), poller_items[i].result);
db_escape(&mysqlt, escaped_rrd_name, sizeof(escaped_rrd_name), poller_items[i].rrd_name);
snprintf(result_string, RESULTS_BUFFER+SMALL_BUFSIZE, " (%i, '%s', FROM_UNIXTIME(%s), '%s')",
poller_items[i].local_data_id,
poller_items[i].rrd_name,
escaped_rrd_name,
host_time,
poller_items[i].result);
escaped_result);
result_length = strlen(result_string);
@@ -1974,10 +2006,10 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread
}
snprintf(error_query, error_query_len, "INSERT INTO host_errors (host_id, poller_id, errors, local_data_ids)"
" VALUES(%i, %i, %i, \"%s\")"
" VALUES(%i, %i, %i, '%s')"
" ON DUPLICATE KEY UPDATE"
" errors = errors + VALUES(errors),"
" local_data_ids = CONCAT(local_data_ids, \", \", VALUES(local_data_ids))",
" local_data_ids = CONCAT(local_data_ids, ', ', VALUES(local_data_ids))",
host_id, set.poller_id, errors, error_string);
db_query(&mysql, LOCAL, error_query);
@@ -2044,7 +2076,7 @@ void buffer_output_errors(char *error_string, int *buf_size, int *buf_errors, in
*buf_size = snprintf(error_string, DBL_BUFSIZE, "%i", local_data_id);
} else {
(*buf_errors)++;
snprintf(error_string + *buf_size, DBL_BUFSIZE, "%s", tbuffer);
snprintf(error_string + *buf_size, DBL_BUFSIZE - *buf_size, "%s", tbuffer);
*buf_size += error_len;
}
}
@@ -2242,6 +2274,9 @@ int validate_result(char *result) {
* \return a pointer to a character buffer containing the result.
*
*/
/* WARNING: command is passed to /bin/sh -c (via nft_popen) without shell escaping.
* The caller MUST ensure command originates from a trusted source
* (the Cacti database). Do not pass user-controlled input directly. */
char *exec_poll(host_t *current_host, char *command, int id, const char *type) {
int cmd_fd;
int pid;
+50 -73
View File
@@ -1,93 +1,70 @@
.TH SPINE 1 "March 2026" "Spine 1.3.0" "Cacti Group"
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH SPINE "1" "March 2026" "SPINE 1.3.0 Copyright 2004-2026 by The Cacti Group" "User Commands"
.SH NAME
spine \- High-performance data collector for Cacti
SPINE \- Data Collector for Cacti
.SH SYNOPSIS
.B spine
[\fIoptions\fR] [[\fIfirstid lastid\fR] | [\fB\-H\fR|\fB\-\-hostlist\fR=\fI'hostid1,hostid2,...'\fR]]
[\fI\,options\/\fR] [[\fI\,firstid lastid\/\fR] \fI\,|| \/\fR[\fI\,-H/--hostlist='hostid1,hostid2,\/\fR...\fI\,,hostidn'\/\fR]]
.SH DESCRIPTION
.B spine
is a multi-threaded data collector for Cacti, designed to be significantly faster than the default cmd.php poller. It is written in C and utilizes pthreads, net-snmp, and MariaDB/MySQL to efficiently poll large numbers of devices.
SPINE 1.3.0 Copyright 2004\-2026 by The Cacti Group
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Show a brief help listing.
\fB\-h\fR/\-\-help
Show this brief help listing
.TP
\fB\-f\fR, \fB\-\-first\fR=\fIX\fR
Start polling with host id X.
\fB\-f\fR/\-\-first=X
Start polling with host id X
.TP
\fB\-l\fR, \fB\-\-last\fR=\fIX\fR
End polling with host id X.
\fB\-l\fR/\-\-last=X
End polling with host id X
.TP
\fB\-H\fR, \fB\-\-hostlist\fR=\fIX\fR
Poll the specific list of host ids, separated by commas.
\fB\-H\fR/\-\-hostlist=X
Poll the list of host ids, separated by comma's
.TP
\fB\-p\fR, \fB\-\-poller\fR=\fIX\fR
Set the poller id to X. This is used in distributed polling environments.
\fB\-p\fR/\-\-poller=X
Set the poller id to X
.TP
\fB\-t\fR, \fB\-\-threads\fR=\fIX\fR
\fB\-t\fR/\-\-threads=X
Override the database threads setting.
.TP
\fB\-C\fR, \fB\-\-conf\fR=\fIF\fR
Read spine configuration from file F.
\fB\-C\fR/\-\-conf=F
Read spine configuration from file F
.TP
\fB\-O\fR, \fB\-\-option\fR=\fIS:V\fR
Override DB settings 'S' with value 'V'.
\fB\-O\fR/\-\-option=S:V
Override DB settings 'set' with value 'V'
.TP
\fB\-M\fR, \fB\-\-mibs\fR
Refresh the device System Mib data.
\fB\-M\fR/\-\-mibs
Refresh the device System Mib data
.TP
\fB\-N\fR, \fB\-\-mode\fR=\fImode\fR
For remote pollers, the operating mode. Options: online, offline, recovery. Default: online.
\fB\-N\fR/\-\-mode=online
For remote pollers, the operating mode.
Options include: online, offline, recovery.
The default is 'online'.
.TP
\fB\-R\fR, \fB\-\-readonly\fR
Spine will not write any output to the database.
\fB\-R\fR/\-\-readonly
Spine will not write output to the DB
.TP
\fB\-S\fR, \fB\-\-stdout\fR
Logging is performed to standard output instead of the configured log file.
\fB\-S\fR/\-\-stdout
Logging is performed to standard output
.TP
\fB\-P\fR, \fB\-\-pingonly\fR
Ping device and update device status only, skipping SNMP/Script polling.
\fB\-P\fR/\-\-pingonly
Ping device and update device status only
.TP
\fB\-V\fR, \fB\-\-verbosity\fR=\fIV\fR
Set logging verbosity. Options: NONE, LOW, MEDIUM, HIGH, DEBUG or 1..5.
.SH CONFIGURATION
Without the \fB\-\-conf\fR parameter, \fBspine\fR searches for \fIspine.conf\fR in the following order:
.IP 1. 3
The current working directory.
.IP 2. 3
/etc/
.IP 3. 3
/etc/cacti/
.IP 4. 3
../etc/ (relative to the binary location)
.SH FILES
.TP
.I /etc/spine.conf
Default configuration file location for database credentials.
.SH EXAMPLES
.TP
\fBspine\fR
Poll all devices configured for this poller in the database.
.TP
\fBspine 1 10\fR
Poll hosts with ID 1 through 10.
.TP
\fBspine \-H "5,12,22"\fR
Poll only hosts with IDs 5, 12, and 22.
.TP
\fBspine \-S \-V DEBUG\fR
Run a poll cycle with full debug output to the terminal.
.SH EXIT STATUS
.TP
0
Success
.TP
-1
Error (Database connection failure, configuration error, etc.)
.SH AUTHORS
Spine is developed and maintained by The Cacti Group (http://www.cacti.net)
.SH COPYRIGHT
Copyright 2004-2026 by The Cacti Group.
License LGPLv2.1+: GNU Lesser GPL version 2.1 or later.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
\fB\-V\fR/\-\-verbosity=V
Set logging verbosity to <V>
.PP
Either both of \fB\-\-first\fR/\-\-last must be provided, a valid hostlist must be provided.
In their absence, all hosts are processed.
.SS "Without the --conf parameter, spine searches in order:"
.IP
current directory, /etc/, /etc/cacti/, ../etc/ for spine.conf.
.PP
Verbosity is one of NONE/LOW/MEDIUM/HIGH/DEBUG or 1..5
.PP
Runtime options are read from the 'settings' table in the Cacti
database, but they can be overridden with the \fB\-\-option\fR=\fI\,S\/\fR:V
parameter.
.PP
Spine is distributed under the Terms of the GNU Lesser
General Public License Version 2.1. (http://www.gnu.org/licenses/lgpl.txt)
For more information, see http://www.cacti.net
+37 -9
View File
@@ -386,6 +386,17 @@ int main(int argc, char *argv[]) {
else if (STRIMATCH(arg, "-H") || STRIMATCH(arg, "--hostlist")) {
snprintf(set.host_id_list, BIG_BUFSIZE, "%s", getarg(opt, &argv));
/* Validate host_id_list contains only digits and commas */
{
const char *p = set.host_id_list;
while (*p) {
if (!isdigit((unsigned char)*p) && *p != ',' && *p != ' ') {
die("ERROR: --hostlist contains invalid characters. Only digits and commas are allowed.");
}
p++;
}
}
}
else if (STRIMATCH(arg, "-M") || STRMATCH(arg, "--mibs")) {
@@ -633,19 +644,27 @@ int main(int argc, char *argv[]) {
}
/* obtain the list of hosts to poll */
qp += sprintf(qp, "SELECT SQL_NO_CACHE id, device_threads, picount, picount/device_threads AS tppi FROM host AS h LEFT JOIN (SELECT host_id, COUNT(*) AS picount FROM poller_item GROUP BY host_id) AS pi ON h.id = pi.host_id");
qp += sprintf(qp, " WHERE disabled = ''");
{
int remaining = MEGA_BUFSIZE - (qp - querybuf);
qp += snprintf(qp, remaining, "SELECT SQL_NO_CACHE id, device_threads, picount, picount/device_threads AS tppi FROM host AS h LEFT JOIN (SELECT host_id, COUNT(*) AS picount FROM poller_item GROUP BY host_id) AS pi ON h.id = pi.host_id");
remaining = MEGA_BUFSIZE - (qp - querybuf);
qp += snprintf(qp, remaining, " WHERE disabled = ''");
qp += sprintf(qp, " AND availability_method != %d", AVAIL_STREAM);
remaining = MEGA_BUFSIZE - (qp - querybuf);
qp += snprintf(qp, remaining, " AND availability_method != %d", AVAIL_STREAM);
if (!strlen(set.host_id_list)) {
qp += append_hostrange(qp, "h.id"); /* AND id BETWEEN a AND b */
} else {
qp += sprintf(qp, " AND h.id IN(%s)", set.host_id_list);
remaining = MEGA_BUFSIZE - (qp - querybuf);
qp += snprintf(qp, remaining, " AND h.id IN(%s)", set.host_id_list);
}
qp += sprintf(qp, " AND h.poller_id = %i", set.poller_id);
qp += sprintf(qp, " ORDER BY picount DESC");
remaining = MEGA_BUFSIZE - (qp - querybuf);
qp += snprintf(qp, remaining, " AND h.poller_id = %i", set.poller_id);
remaining = MEGA_BUFSIZE - (qp - querybuf);
qp += snprintf(qp, remaining, " ORDER BY picount DESC");
}
SPINE_LOG_DEVDBG(("DEVDBG: Host SQL:%s", querybuf));
result = db_query(&mysql, LOCAL, querybuf);
@@ -797,10 +816,10 @@ int main(int argc, char *argv[]) {
db_free_result(tresult);
sprintf(host_time, "%lu", (unsigned long) time(NULL));
snprintf(host_time, SMALL_BUFSIZE, "%lu", (unsigned long) time(NULL));
host_time_double = get_time_as_double();
} else if (host_time_double == 0 || host_time == 0 || host_time == NULL) {
sprintf(host_time, "%lu", (unsigned long) time(NULL));
snprintf(host_time, SMALL_BUFSIZE, "%lu", (unsigned long) time(NULL));
host_time_double = get_time_as_double();
}
} else {
@@ -812,7 +831,7 @@ int main(int argc, char *argv[]) {
db_free_result(tresult);
sprintf(host_time, "%lu", (unsigned long) time(NULL));
snprintf(host_time, SMALL_BUFSIZE, "%lu", (unsigned long) time(NULL));
host_time_double = get_time_as_double();
}
@@ -1113,6 +1132,15 @@ int main(int argc, char *argv[]) {
}
}
/* zero sensitive credentials before exit */
{
volatile char *vp;
vp = (volatile char *)set.db_pass;
memset((char *)vp, 0, sizeof(set.db_pass));
vp = (volatile char *)set.rdb_pass;
memset((char *)vp, 0, sizeof(set.rdb_pass));
}
/* uninstall the spine signal handler */
uninstall_spine_signal_handler();
+3 -1
View File
@@ -252,6 +252,7 @@ void db_connect(int type, MYSQL *mysql) {
if (stat(hostname, &socket_stat) == 0) {
if (socket_stat.st_mode & S_IFSOCK) {
socket = strdup (set.db_host);
free(hostname);
hostname = NULL;
}
} else if ((socket = strstr(hostname,":"))) {
@@ -266,6 +267,7 @@ void db_connect(int type, MYSQL *mysql) {
if (stat(hostname, &socket_stat) == 0) {
if (socket_stat.st_mode & S_IFSOCK) {
socket = strdup (set.db_host);
free(hostname);
hostname = NULL;
}
} else if ((socket = strstr(hostname,":"))) {
@@ -560,7 +562,7 @@ void db_release_connection(int type, int id) {
*/
int append_hostrange(char *obuf, const char *colname) {
if (HOSTID_DEFINED(set.start_host_id) && HOSTID_DEFINED(set.end_host_id)) {
return sprintf(obuf, " AND %s BETWEEN %d AND %d",
return snprintf(obuf, BUFSIZE, " AND %s BETWEEN %d AND %d",
colname,
set.start_host_id,
set.end_host_id);
+86 -47
View File
@@ -96,7 +96,7 @@ static char *getsetting(MYSQL *psql, int mode, const char *setting) {
}
}
sprintf(qstring, "SELECT SQL_NO_CACHE value FROM settings WHERE name = '%s'", setting);
snprintf(qstring, sizeof(qstring), "SELECT SQL_NO_CACHE value FROM settings WHERE name = '%s'", setting);
result = db_query(psql, mode, qstring);
@@ -138,11 +138,11 @@ static int putsetting(MYSQL *psql, int mode, const char *mysetting, const char *
assert(myvalue != 0);
if (set.dbonupdate == 0) {
sprintf(qstring, "INSERT INTO settings (name, value) "
snprintf(qstring, sizeof(qstring), "INSERT INTO settings (name, value) "
"VALUES ('%s', '%s') "
"ON DUPLICATE KEY UPDATE value = VALUES(value)", mysetting, myvalue);
} else {
sprintf(qstring, "INSERT INTO settings (name, value) "
snprintf(qstring, sizeof(qstring), "INSERT INTO settings (name, value) "
"VALUES ('%s', '%s') AS rs "
"ON DUPLICATE KEY UPDATE value = rs.value", mysetting, myvalue);
}
@@ -188,7 +188,7 @@ static char *getpsetting(MYSQL *psql, int mode, const char *setting) {
}
}
sprintf(qstring, "SELECT SQL_NO_CACHE %s FROM poller WHERE id = '%d'", setting, set.poller_id);
snprintf(qstring, sizeof(qstring), "SELECT SQL_NO_CACHE %s FROM poller WHERE id = '%d'", setting, set.poller_id);
result = db_query(psql, mode, qstring);
@@ -283,7 +283,7 @@ static char *getglobalvariable(MYSQL *psql, int mode, const char *setting) {
}
}
sprintf(qstring, "SHOW GLOBAL VARIABLES LIKE '%s'", setting);
snprintf(qstring, sizeof(qstring), "SHOW GLOBAL VARIABLES LIKE '%s'", setting);
result = db_query(psql, mode, qstring);
@@ -804,6 +804,7 @@ void poller_push_data_to_main(void) {
int rows;
char sqlbuf[HUGE_BUFSIZE];
char *sqlp = sqlbuf;
int remaining;
char query[MEGA_BUFSIZE];
char prefix[BUFSIZE];
char suffix[BUFSIZE];
@@ -901,55 +902,81 @@ void poller_push_data_to_main(void) {
if (rows < 500) {
if (rows == 0) {
sqlp = sqlbuf;
sqlp += sprintf(sqlp, "%s", prefix);
sqlp += sprintf(sqlp, " (");
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s", prefix);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, " (");
} else {
sqlp += sprintf(sqlp, ", (");
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, ", (");
}
sqlp += sprintf(sqlp, "%s, ", row[0]); // id mediumint
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s, ", row[0]); // id mediumint
db_escape(&mysql, tmpstr, sizeof(tmpstr), row[1]); // snmp_sysDescr varchar(300)
sqlp += sprintf(sqlp, "'%s', ", tmpstr);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "'%s', ", tmpstr);
db_escape(&mysql, tmpstr, sizeof(tmpstr), row[2]); // snmp_sysObjectID varchar(128)
sqlp += sprintf(sqlp, "'%s', ", tmpstr);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "'%s', ", tmpstr);
db_escape(&mysql, tmpstr, sizeof(tmpstr), row[3]); // snmp_sysUpTimeInstance bigint
sqlp += sprintf(sqlp, "'%s', ", tmpstr);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "'%s', ", tmpstr);
db_escape(&mysql, tmpstr, sizeof(tmpstr), row[4]); // snmp_sysContact varchar(300)
sqlp += sprintf(sqlp, "'%s', ", tmpstr);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "'%s', ", tmpstr);
db_escape(&mysql, tmpstr, sizeof(tmpstr), row[5]); // snmp_sysName varchar(300)
sqlp += sprintf(sqlp, "'%s', ", tmpstr);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "'%s', ", tmpstr);
db_escape(&mysql, tmpstr, sizeof(tmpstr), row[6]); // snmp_sysLocation varchar(300)
sqlp += sprintf(sqlp, "'%s', ", tmpstr);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "'%s', ", tmpstr);
db_escape(&mysql, tmpstr, sizeof(tmpstr), row[7]); // status tinyint
sqlp += sprintf(sqlp, "'%s', ", tmpstr);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "'%s', ", tmpstr);
sqlp += sprintf(sqlp, "%s, ", row[8]); // status_event_count mediumint
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s, ", row[8]); // status_event_count mediumint
db_escape(&mysql, tmpstr, sizeof(tmpstr), row[9]); // status_fail_date timestamp
sqlp += sprintf(sqlp, "'%s', ", tmpstr);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "'%s', ", tmpstr);
db_escape(&mysql, tmpstr, sizeof(tmpstr), row[10]); // status_rec_date timestamp
sqlp += sprintf(sqlp, "'%s', ", tmpstr);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "'%s', ", tmpstr);
db_escape(&mysql, tmpstr, sizeof(tmpstr), row[11]); // status_last_error varchar(255)
sqlp += sprintf(sqlp, "'%s', ", tmpstr);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "'%s', ", tmpstr);
sqlp += sprintf(sqlp, "%s, ", row[12]); // min_time decimal(10,5)
sqlp += sprintf(sqlp, "%s, ", row[13]); // max_time decimal(10,5)
sqlp += sprintf(sqlp, "%s, ", row[14]); // cur_time decimal(10,5)
sqlp += sprintf(sqlp, "%s, ", row[15]); // avg_time decimal(10,5)
sqlp += sprintf(sqlp, "%s, ", row[16]); // polling_time double
sqlp += sprintf(sqlp, "%s, ", row[17]); // total_polls int
sqlp += sprintf(sqlp, "%s, ", row[18]); // failed_polls int
sqlp += sprintf(sqlp, "%s, ", row[19]); // availability decimal(8,5)
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s, ", row[12]); // min_time decimal(10,5)
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s, ", row[13]); // max_time decimal(10,5)
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s, ", row[14]); // cur_time decimal(10,5)
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s, ", row[15]); // avg_time decimal(10,5)
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s, ", row[16]); // polling_time double
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s, ", row[17]); // total_polls int
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s, ", row[18]); // failed_polls int
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s, ", row[19]); // availability decimal(8,5)
db_escape(&mysql, tmpstr, sizeof(tmpstr), row[20]); // last_updated timestamp
sqlp += sprintf(sqlp, "'%s'", tmpstr);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "'%s'", tmpstr);
sqlp += sprintf(sqlp, ")");
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, ")");
rows++;
} else {
sqlp += sprintf(sqlp, "%s", suffix);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s", suffix);
db_insert(&mysqlr, REMOTE, sqlbuf);
rows = 0;
@@ -958,7 +985,8 @@ void poller_push_data_to_main(void) {
}
if (rows > 0) {
sqlp += sprintf(sqlp, "%s", suffix);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s", suffix);
db_insert(&mysqlr, REMOTE, sqlbuf);
}
}
@@ -998,26 +1026,36 @@ void poller_push_data_to_main(void) {
if (rows < 10000) {
if (rows == 0) {
sqlp = sqlbuf;
sqlp += sprintf(sqlp, "%s", prefix);
sqlp += sprintf(sqlp, " (");
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s", prefix);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, " (");
} else {
sqlp += sprintf(sqlp, ", (");
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, ", (");
}
sqlp += sprintf(sqlp, "%s, ", row[0]); // local_data_id
sqlp += sprintf(sqlp, "%s, ", row[1]); // host_id
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s, ", row[0]); // local_data_id
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s, ", row[1]); // host_id
db_escape(&mysql, tmpstr, sizeof(tmpstr), row[2]); // rrd_name
sqlp += sprintf(sqlp, "'%s', ", tmpstr);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "'%s', ", tmpstr);
sqlp += sprintf(sqlp, "%s, ", row[3]); // rrd_step
sqlp += sprintf(sqlp, "%s", row[4]); // rrd_next_step
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s, ", row[3]); // rrd_step
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s", row[4]); // rrd_next_step
sqlp += sprintf(sqlp, ")");
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, ")");
rows++;
} else {
sqlp += sprintf(sqlp, "%s", suffix);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s", suffix);
db_insert(&mysqlr, REMOTE, sqlbuf);
rows = 0;
@@ -1026,7 +1064,8 @@ void poller_push_data_to_main(void) {
}
if (rows > 0) {
sqlp += sprintf(sqlp, "%s", suffix);
remaining = HUGE_BUFSIZE - (sqlp - sqlbuf);
sqlp += snprintf(sqlp, remaining, "%s", suffix);
db_insert(&mysqlr, REMOTE, sqlbuf);
rows = 0;
@@ -1634,12 +1673,12 @@ char *add_slashes(char *string) {
int new_position;
char *return_str;
if (!(return_str = (char *) malloc(BUFSIZE))) {
length = strlen(string);
if (!(return_str = (char *) malloc(length * 2 + 1))) {
die("ERROR: Fatal malloc error: util.c add_slashes!");
}
return_str[0] = '\0';
length = strlen(string);
position = 0;
new_position = 0;
@@ -2033,7 +2072,7 @@ int get_cacti_version(MYSQL *psql, int mode) {
assert(psql != 0);
sprintf(qstring, "SELECT cacti FROM version LIMIT 1");
snprintf(qstring, sizeof(qstring), "SELECT cacti FROM version LIMIT 1");
result = db_query(psql, mode, qstring);