From 926d5b66af391ae06dce1269fbd465c03878fdc0 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 26 Mar 2026 04:45:48 -0700 Subject: [PATCH] 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 --- nft_popen.c | 3 ++ ping.c | 9 +++- poller.c | 51 ++++++++++++++++---- spine.1 | 123 ++++++++++++++++++++---------------------------- spine.c | 54 ++++++++++++++++----- sql.c | 4 +- util.c | 133 +++++++++++++++++++++++++++++++++------------------- 7 files changed, 234 insertions(+), 143 deletions(-) diff --git a/nft_popen.c b/nft_popen.c index ff15e2d..811b651 100644 --- a/nft_popen.c +++ b/nft_popen.c @@ -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; diff --git a/ping.c b/ping.c index 17b4b80..c716378 100644 --- a/ping.c +++ b/ping.c @@ -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(); diff --git a/poller.c b/poller.c index 192925b..c1dc66e 100644 --- a/poller.c +++ b/poller.c @@ -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; diff --git a/spine.1 b/spine.1 index 1104605..18f1b7f 100644 --- a/spine.1 +++ b/spine.1 @@ -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 +.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 diff --git a/spine.c b/spine.c index b711044..71a8e9d 100644 --- a/spine.c +++ b/spine.c @@ -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,20 +644,28 @@ 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); + if (!strlen(set.host_id_list)) { + qp += append_hostrange(qp, "h.id"); /* AND id BETWEEN a AND b */ + } else { + remaining = MEGA_BUFSIZE - (qp - querybuf); + qp += snprintf(qp, remaining, " AND h.id IN(%s)", set.host_id_list); + } + + 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"); } - qp += sprintf(qp, " AND h.poller_id = %i", set.poller_id); - qp += sprintf(qp, " 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(); diff --git a/sql.c b/sql.c index 2e1de20..068ef9a 100644 --- a/sql.c +++ b/sql.c @@ -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); diff --git a/util.c b/util.c index cf0e9ce..03a3ce7 100644 --- a/util.c +++ b/util.c @@ -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);