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..ffe1de9 100644 --- a/ping.c +++ b/ping.c @@ -274,7 +274,7 @@ int ping_icmp(host_t *host, ping_t *ping) { ssize_t return_code; fd_set socket_fds; - static unsigned int seq = 0; + static volatile unsigned int seq = 0; struct icmp *icmp; struct ip *ip; struct icmp *pkt; @@ -351,10 +351,8 @@ int ping_icmp(host_t *host, ping_t *ping) { icmp->icmp_code = 0; icmp->icmp_id = getpid() & 0xFFFF; - /* lock set/get the sequence and unlock */ - thread_mutex_lock(LOCK_GHBN); - icmp->icmp_seq = seq++; - thread_mutex_unlock(LOCK_GHBN); + /* atomically increment the sequence counter */ + icmp->icmp_seq = __sync_fetch_and_add(&seq, 1); icmp->icmp_cksum = 0; memcpy(packet+ICMP_HDR_SIZE, cacti_msg, strlen(cacti_msg)); @@ -404,8 +402,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(); @@ -609,6 +614,13 @@ int ping_udp(host_t *host, ping_t *ping) { /* initialize file descriptor to review for input/output */ FD_ZERO(&socket_fds); + if (udp_socket >= FD_SETSIZE) { + SPINE_LOG(("ERROR: Device[%i] UDP socket %d exceeds FD_SETSIZE %d", host->id, udp_socket, FD_SETSIZE)); + snprintf(ping->ping_status, 50, "down"); + snprintf(ping->ping_response, SMALL_BUFSIZE, "UDP: fd exceeds FD_SETSIZE"); + close(udp_socket); + return HOST_DOWN; + } FD_SET(udp_socket,&socket_fds); while (1) { @@ -643,7 +655,7 @@ int ping_udp(host_t *host, ping_t *ping) { /* wait for a response on the socket */ wait_more: - return_code = select(FD_SETSIZE, &socket_fds, NULL, NULL, &timeout); + return_code = select(udp_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..6c39a90 100644 --- a/poller.c +++ b/poller.c @@ -222,6 +222,10 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread buf_size = malloc(sizeof(int)); buf_errors = malloc(sizeof(int)); + if (error_string == NULL || buf_size == NULL || buf_errors == NULL) { + die("ERROR: Fatal malloc error: poller.c error_string/buf_size/buf_errors"); + } + *buf_size = 0; *buf_errors = 0; @@ -233,10 +237,20 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread //db_connect(LOCAL, &mysql); local_cnn = db_get_connection(LOCAL); + + if (local_cnn == NULL) { + die("ERROR: No available database connections"); + } + mysql = local_cnn->mysql; if (set.poller_id > 1 && set.mode == REMOTE_ONLINE) { remote_cnn = db_get_connection(REMOTE); + + if (remote_cnn == NULL) { + die("ERROR: No available remote database connections"); + } + mysqlr = remote_cnn->mysql; } @@ -653,7 +667,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* populate host structure */ host->ignore_host = FALSE; - if (row[0] != NULL) host->id = atoi(row[0]); + if (row[0] != NULL) host->id = (int)strtol(row[0], NULL, 10); if (row[1] != NULL) { name = get_namebyhost(row[1], NULL); @@ -664,7 +678,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread if (row[2] != NULL) STRNCOPY(host->snmp_community, row[2]); - if (row[3] != NULL) host->snmp_version = atoi(row[3]); + if (row[3] != NULL) host->snmp_version = (int)strtol(row[3], NULL, 10); if (row[4] != NULL) STRNCOPY(host->snmp_username, row[4]); if (row[5] != NULL) STRNCOPY(host->snmp_password, row[5]); @@ -674,18 +688,18 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread if (row[9] != NULL) STRNCOPY(host->snmp_context, row[9]); if (row[10] != NULL) STRNCOPY(host->snmp_engine_id, row[10]); - if (row[11] != NULL) host->snmp_port = atoi(row[11]); - if (row[12] != NULL) host->snmp_timeout = atoi(row[12]); - if (row[13] != NULL) host->max_oids = atoi(row[13]); + if (row[11] != NULL) host->snmp_port = (int)strtol(row[11], NULL, 10); + if (row[12] != NULL) host->snmp_timeout = (int)strtol(row[12], NULL, 10); + if (row[13] != NULL) host->max_oids = (int)strtol(row[13], NULL, 10); - if (row[14] != NULL) host->availability_method = atoi(row[14]); - if (row[15] != NULL) host->ping_method = atoi(row[15]); - if (row[16] != NULL) host->ping_port = atoi(row[16]); - if (row[17] != NULL) host->ping_timeout = atoi(row[17]); - if (row[18] != NULL) host->ping_retries = atoi(row[18]); + if (row[14] != NULL) host->availability_method = (int)strtol(row[14], NULL, 10); + if (row[15] != NULL) host->ping_method = (int)strtol(row[15], NULL, 10); + if (row[16] != NULL) host->ping_port = (int)strtol(row[16], NULL, 10); + if (row[17] != NULL) host->ping_timeout = (int)strtol(row[17], NULL, 10); + if (row[18] != NULL) host->ping_retries = (int)strtol(row[18], NULL, 10); - if (row[19] != NULL) host->status = atoi(row[19]); - if (row[20] != NULL) host->status_event_count = atoi(row[20]); + if (row[19] != NULL) host->status = (int)strtol(row[19], NULL, 10); + if (row[20] != NULL) host->status_event_count = (int)strtol(row[20], NULL, 10); if (row[21] != NULL) STRNCOPY(host->status_fail_date, row[21]); if (row[22] != NULL) STRNCOPY(host->status_rec_date, row[22]); @@ -696,8 +710,8 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread if (row[25] != NULL) host->max_time = atof(row[25]); if (row[26] != NULL) host->cur_time = atof(row[26]); if (row[27] != NULL) host->avg_time = atof(row[27]); - if (row[28] != NULL) host->total_polls = atoi(row[28]); - if (row[29] != NULL) host->failed_polls = atoi(row[29]); + if (row[28] != NULL) host->total_polls = (int)strtol(row[28], NULL, 10); + if (row[29] != NULL) host->failed_polls = (int)strtol(row[29], NULL, 10); if (row[30] != NULL) host->availability = atof(row[30]); if (row[31] != NULL) host->snmp_sysUpTimeInstance=atoll(row[31]); @@ -917,8 +931,8 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread reindex->assert_value[0] = '\0'; reindex->arg1[0] = '\0'; - if (row[0] != NULL) reindex->data_query_id = atoi(row[0]); - if (row[1] != NULL) reindex->action = atoi(row[1]); + if (row[0] != NULL) reindex->data_query_id = (int)strtol(row[0], NULL, 10); + if (row[1] != NULL) reindex->action = (int)strtol(row[1], NULL, 10); if (row[2] != NULL) snprintf(reindex->op, sizeof(reindex->op), "%s", row[2]); @@ -1014,6 +1028,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)) { @@ -1271,6 +1292,10 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* retrieve each hosts polling items from poller cache and load into array */ poller_items = (target_t *) calloc(num_rows, sizeof(target_t)); + if (poller_items == NULL) { + die("ERROR: Fatal calloc error: poller.c poller_items"); + } + i = 0; while ((row = mysql_fetch_row(result))) { /* initialize monitored object */ @@ -1297,12 +1322,12 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread poller_items[i].rrd_num = 0; poller_items[i].output_regex[0] = '\0'; - if (row[0] != NULL) poller_items[i].action = atoi(row[0]); + if (row[0] != NULL) poller_items[i].action = (int)strtol(row[0], NULL, 10); if (row[1] != NULL) snprintf(poller_items[i].hostname, sizeof(poller_items[i].hostname), "%s", row[1]); if (row[2] != NULL) snprintf(poller_items[i].snmp_community, sizeof(poller_items[i].snmp_community), "%s", row[2]); - if (row[3] != NULL) poller_items[i].snmp_version = atoi(row[3]); + if (row[3] != NULL) poller_items[i].snmp_version = (int)strtol(row[3], NULL, 10); if (row[4] != NULL) snprintf(poller_items[i].snmp_username, sizeof(poller_items[i].snmp_username), "%s", row[4]); if (row[5] != NULL) snprintf(poller_items[i].snmp_password, sizeof(poller_items[i].snmp_password), "%s", row[5]); @@ -1313,11 +1338,11 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread if (row[9] != NULL) snprintf(poller_items[i].arg2, sizeof(poller_items[i].arg2), "%s", row[9]); if (row[10] != NULL) snprintf(poller_items[i].arg3, sizeof(poller_items[i].arg3), "%s", row[10]); - if (row[11] != NULL) poller_items[i].local_data_id = atoi(row[11]); + if (row[11] != NULL) poller_items[i].local_data_id = (int)strtol(row[11], NULL, 10); - if (row[12] != NULL) poller_items[i].rrd_num = atoi(row[12]); - if (row[13] != NULL) poller_items[i].snmp_port = atoi(row[13]); - if (row[14] != NULL) poller_items[i].snmp_timeout = atoi(row[14]); + if (row[12] != NULL) poller_items[i].rrd_num = (int)strtol(row[12], NULL, 10); + if (row[13] != NULL) poller_items[i].snmp_port = (int)strtol(row[13], NULL, 10); + if (row[14] != NULL) poller_items[i].snmp_timeout = (int)strtol(row[14], NULL, 10); if (row[15] != NULL) snprintf(poller_items[i].snmp_auth_protocol, sizeof(poller_items[i].snmp_auth_protocol), "%s", row[15]); @@ -1345,6 +1370,10 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* create an array for snmp oids */ snmp_oids = (snmp_oids_t *) calloc(host->max_oids, sizeof(snmp_oids_t)); + if (snmp_oids == NULL) { + die("ERROR: Fatal calloc error: poller.c snmp_oids"); + } + /* initialize all the memory to insure we don't get issues */ memset(snmp_oids, 0, sizeof(snmp_oids_t)*host->max_oids); @@ -1600,6 +1629,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 +1693,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 +1886,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); @@ -2044,7 +2095,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; } } @@ -2140,7 +2191,7 @@ void get_system_information(host_t *host, MYSQL *mysql, int system) { if (poll_result && is_numeric(poll_result)) { host->snmp_sysUpTimeInstance = atoll(poll_result) * 100; - snprintf(poll_result, BUFSIZE, "%llu", host->snmp_sysUpTimeInstance); + snprintf(poll_result, RESULTS_BUFFER, "%llu", host->snmp_sysUpTimeInstance); } SPINE_FREE(poll_result); @@ -2195,7 +2246,7 @@ void get_system_information(host_t *host, MYSQL *mysql, int system) { if (poll_result && is_numeric(poll_result)) { host->snmp_sysUpTimeInstance = atoll(poll_result) * 100; - snprintf(poll_result, BUFSIZE, "%llu", host->snmp_sysUpTimeInstance); + snprintf(poll_result, RESULTS_BUFFER, "%llu", host->snmp_sysUpTimeInstance); } SPINE_FREE(poll_result); @@ -2242,6 +2293,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; @@ -2356,6 +2410,17 @@ char *exec_poll(host_t *current_host, char *command, int id, const char *type) { #endif if (cmd_fd > 0) { + if (cmd_fd >= FD_SETSIZE) { + SPINE_LOG(("ERROR: Device[%i] file descriptor %d exceeds FD_SETSIZE %d", current_host->id, cmd_fd, FD_SETSIZE)); + SET_UNDEFINED(result_string); + #ifdef USING_TPOPEN + pclose(fd); + #else + nft_pclose(cmd_fd); + #endif + goto popen_done; + } + retry: /* Initialize File Descriptors to Review for Input/Output */ @@ -2363,7 +2428,7 @@ char *exec_poll(host_t *current_host, char *command, int id, const char *type) { FD_SET(cmd_fd, &fds); /* wait x seconds for pipe response */ - switch (select(FD_SETSIZE, &fds, NULL, NULL, &timeout)) { + switch (select(cmd_fd + 1, &fds, NULL, NULL, &timeout)) { case -1: switch (errno) { case EBADF: @@ -2461,6 +2526,7 @@ char *exec_poll(host_t *current_host, char *command, int id, const char *type) { #else nft_pclose(cmd_fd); #endif + popen_done: ; } else { SPINE_LOG(("Device[%i] ERROR: Problem executing POPEN [%s]: '%s'", current_host->id, current_host->hostname, command)); SET_UNDEFINED(result_string); diff --git a/snmp.c b/snmp.c index 60708d2..a961d47 100644 --- a/snmp.c +++ b/snmp.c @@ -128,7 +128,7 @@ void *snmp_host_init(int host_id, char *hostname, int snmp_version, char *snmp_c char *Xpsz = NULL; char *Cpsz = NULL; int priv_type; - int zero_sensitive = 0; + int zero_sensitive = 1; /* initialize SNMP */ snmp_sess_init(&session); @@ -269,28 +269,36 @@ void *snmp_host_init(int host_id, char *hostname, int snmp_version, char *snmp_c session.securityPrivProto = snmp_duplicate_objid(priv_proto, session.securityPrivProtoLen); session.securityLevel = SNMP_SEC_LEVEL_AUTHPRIV; - // Auth Protocol Setup + /* Auth Protocol Setup - zero old credential before freeing */ if (Apsz && zero_sensitive) { - memset(Apsz, 0x0, strlen(Apsz)); + volatile char *vp = (volatile char *)Apsz; + size_t slen = strlen(Apsz); + while (slen--) { *vp++ = 0; } } free(Apsz); Apsz = strdup(snmp_password); if (zero_sensitive) { - memset(snmp_password, 0x0, strlen(snmp_password)); + volatile char *vp = (volatile char *)snmp_password; + size_t slen = strlen(snmp_password); + while (slen--) { *vp++ = 0; } } - // Privacy Protocol Setup + /* Privacy Protocol Setup - zero old credential before freeing */ if (Xpsz && zero_sensitive) { - memset(Xpsz, 0x0, strlen(Xpsz)); + volatile char *vp = (volatile char *)Xpsz; + size_t slen = strlen(Xpsz); + while (slen--) { *vp++ = 0; } } free(Xpsz); Xpsz = strdup(snmp_priv_passphrase); if (zero_sensitive) { - memset(snmp_priv_passphrase, 0x0, strlen(snmp_priv_passphrase)); + volatile char *vp = (volatile char *)snmp_priv_passphrase; + size_t slen = strlen(snmp_priv_passphrase); + while (slen--) { *vp++ = 0; } } if (Apsz) { @@ -377,6 +385,15 @@ void *snmp_host_init(int host_id, char *hostname, int snmp_version, char *snmp_c sessp = snmp_sess_open(&session); thread_mutex_unlock(LOCK_SNMP); + /* zero sensitive key material now that session has copied it */ + { + volatile char *vp; + vp = (volatile char *)session.securityAuthKey; + memset((char *)vp, 0, sizeof(session.securityAuthKey)); + vp = (volatile char *)session.securityPrivKey; + memset((char *)vp, 0, sizeof(session.securityPrivKey)); + } + free(session.peername); free(session.securityAuthProto); free(session.securityPrivProto); 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. -.TP -\fB\-O\fR, \fB\-\-option\fR=\fIS:V\fR -Override DB settings 'S' with value 'V'. -.TP -\fB\-M\fR, \fB\-\-mibs\fR -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. -.TP -\fB\-R\fR, \fB\-\-readonly\fR -Spine will not write any output to the database. -.TP -\fB\-S\fR, \fB\-\-stdout\fR -Logging is performed to standard output instead of the configured log file. -.TP -\fB\-P\fR, \fB\-\-pingonly\fR -Ping device and update device status only, skipping SNMP/Script polling. -.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\-C\fR/\-\-conf=F +Read spine configuration from file F +.TP +\fB\-O\fR/\-\-option=S:V +Override DB settings 'set' with value 'V' +.TP +\fB\-M\fR/\-\-mibs +Refresh the device System Mib data +.TP +\fB\-N\fR/\-\-mode=online +For remote pollers, the operating mode. +Options include: online, offline, recovery. +The default is 'online'. +.TP +\fB\-R\fR/\-\-readonly +Spine will not write output to the DB +.TP +\fB\-S\fR/\-\-stdout +Logging is performed to standard output +.TP +\fB\-P\fR/\-\-pingonly +Ping device and update device status only +.TP +\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..97b987b 100644 --- a/spine.c +++ b/spine.c @@ -235,6 +235,9 @@ int main(int argc, char *argv[]) { if (geteuid() == 0) { drop_root(getuid(), getgid()); } + + /* disable core dumps to prevent credential leakage */ + prctl(PR_SET_DUMPABLE, 0); #endif /* HAVE_LCAP */ /* we must initialize snmp in the main thread */ @@ -340,7 +343,7 @@ int main(int argc, char *argv[]) { die("ERROR: %s can only be used once", arg); } - set.start_host_id = atoi(opt = getarg(opt, &argv)); + set.start_host_id = (int)strtol(opt = getarg(opt, &argv), NULL, 10); if (!HOSTID_DEFINED(set.start_host_id)) { die("ERROR: '%s=%s' is invalid first-host ID", arg, opt); @@ -352,7 +355,7 @@ int main(int argc, char *argv[]) { die("ERROR: %s can only be used once", arg); } - set.end_host_id = atoi(opt = getarg(opt, &argv)); + set.end_host_id = (int)strtol(opt = getarg(opt, &argv), NULL, 10); if (!HOSTID_DEFINED(set.end_host_id)) { die("ERROR: '%s=%s' is invalid last-host ID", arg, opt); @@ -360,11 +363,11 @@ int main(int argc, char *argv[]) { } else if (STRIMATCH(arg, "-p") || STRIMATCH(arg, "--poller")) { - set.poller_id = atoi(getarg(opt, &argv)); + set.poller_id = (int)strtol(getarg(opt, &argv), NULL, 10); } else if (STRMATCH(arg, "-t") || STRIMATCH(arg, "--threads")) { - set.threads = atoi(getarg(opt, &argv)); + set.threads = (int)strtol(getarg(opt, &argv), NULL, 10); set.threads_set = TRUE; } @@ -386,6 +389,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")) { @@ -438,11 +452,11 @@ int main(int argc, char *argv[]) { } else if (!HOSTID_DEFINED(set.start_host_id) && all_digits(arg)) { - set.start_host_id = atoi(arg); + set.start_host_id = (int)strtol(arg, NULL, 10); } else if (!HOSTID_DEFINED(set.end_host_id) && all_digits(arg)) { - set.end_host_id = atoi(arg); + set.end_host_id = (int)strtol(arg, NULL, 10); } else { @@ -525,7 +539,7 @@ int main(int argc, char *argv[]) { SPINE_LOG_DEBUG(("DEBUG: Selective Debug Devices %s", set.selective_device_debug)); token = strtok(set.selective_device_debug, ","); while(token && debug_idx < MAX_DEBUG_DEVICES - 1) { - debug_devices[debug_idx] = atoi(token); + debug_devices[debug_idx] = (int)strtol(token, NULL, 10); debug_devices[debug_idx+1] = '\0'; token = strtok(NULL, ","); debug_idx++; @@ -747,8 +761,8 @@ int main(int argc, char *argv[]) { if (change_host) { mysql_row = mysql_fetch_row(result); - host_id = atoi(mysql_row[0]); - device_threads = atoi(mysql_row[1]); + host_id = (int)strtol(mysql_row[0], NULL, 10); + device_threads = (int)strtol(mysql_row[1], NULL, 10); current_thread = 1; if (device_threads < 1) { @@ -769,7 +783,7 @@ int main(int argc, char *argv[]) { tresult = db_query(&mysql, LOCAL, querybuf); mysql_row = mysql_fetch_row(tresult); - total_items = atoi(mysql_row[0]); + total_items = (int)strtol(mysql_row[0], NULL, 10); db_free_result(tresult); if (total_items && total_items < device_threads) { @@ -793,7 +807,7 @@ int main(int argc, char *argv[]) { tresult = db_query(&mysql, LOCAL, querybuf); mysql_row = mysql_fetch_row(tresult); - items_per_thread = atoi(mysql_row[0]); + items_per_thread = (int)strtol(mysql_row[0], NULL, 10); db_free_result(tresult); @@ -808,7 +822,7 @@ int main(int argc, char *argv[]) { tresult = db_query(&mysql, LOCAL, querybuf); mysql_row = mysql_fetch_row(tresult); - items_per_thread = atoi(mysql_row[0]); + items_per_thread = (int)strtol(mysql_row[0], NULL, 10); db_free_result(tresult); @@ -1113,6 +1127,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..d15f35d 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,":"))) { @@ -585,16 +587,21 @@ void db_escape(MYSQL *mysql, char *output, int max_size, const char *input) { char input_trimmed[DBL_BUFSIZE]; int max_escaped_input_size; int trim_limit; + int input_cap; if (input == NULL) return; max_escaped_input_size = (strlen(input) * 2) + 1; trim_limit = (max_size < DBL_BUFSIZE) ? max_size : DBL_BUFSIZE; + /* always cap input to (max_size / 2) - 1 to prevent output overflow */ + input_cap = (trim_limit / 2) - 1; + if (input_cap < 1) input_cap = 1; + if (max_escaped_input_size > max_size) { - snprintf(input_trimmed, (trim_limit / 2) - 1, "%s", input); + snprintf(input_trimmed, input_cap, "%s", input); } else { - snprintf(input_trimmed, trim_limit, "%s", input); + snprintf(input_trimmed, input_cap, "%s", input); } mysql_real_escape_string(mysql, output, input_trimmed, strlen(input_trimmed)); @@ -606,8 +613,17 @@ void db_free_result(MYSQL_RES *result) { int db_column_exists(MYSQL *mysql, int type, const char *table, const char *column) { char query_frag[BUFSIZE]; - MYSQL_RES *result; + MYSQL_RES *result; int exists; + const char *p; + + /* validate column name: only alphanumeric and underscore allowed */ + for (p = column; *p; p++) { + if (!isalnum((unsigned char)*p) && *p != '_') { + SPINE_LOG(("ERROR: db_column_exists: invalid column name '%s'", column)); + return FALSE; + } + } /* save a fragment just in case */ memset(query_frag, 0, BUFSIZE); diff --git a/util.c b/util.c index cf0e9ce..3bca2e7 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); @@ -383,7 +383,7 @@ void read_config_options(void) { /* get logging level from database - overrides spine.conf */ if ((res = getsetting(&mysql, LOCAL, "log_verbosity")) != 0) { - const int n = atoi(res); + const int n = (int)strtol(res, NULL, 10); free(res); if (n != 0) set.log_level = n; } @@ -413,7 +413,7 @@ void read_config_options(void) { /* get log separator */ if ((res = getsetting(&mysql, LOCAL, "default_datechar")) != 0) { - set.log_datetime_separator = atoi(res); + set.log_datetime_separator = (int)strtol(res, NULL, 10); free(res); if (set.log_datetime_separator < GDC_MIN || set.log_datetime_separator > GDC_MAX) { @@ -423,7 +423,7 @@ void read_config_options(void) { /* get log separator */ if ((res = getsetting(&mysql, LOCAL, "default_datechar")) != 0) { - set.log_datetime_separator = atoi(res); + set.log_datetime_separator = (int)strtol(res, NULL, 10); free(res); if (set.log_datetime_separator < GDC_MIN || set.log_datetime_separator > GDC_MAX) { @@ -466,7 +466,7 @@ void read_config_options(void) { /* set availability_method */ if ((res = getsetting(&mysql, LOCAL, "availability_method")) != 0) { - set.availability_method = atoi(res); + set.availability_method = (int)strtol(res, NULL, 10); free(res); } @@ -475,7 +475,7 @@ void read_config_options(void) { /* set ping_recovery_count */ if ((res = getsetting(&mysql, LOCAL, "ping_recovery_count")) != 0) { - set.ping_recovery_count = atoi(res); + set.ping_recovery_count = (int)strtol(res, NULL, 10); free(res); } @@ -484,7 +484,7 @@ void read_config_options(void) { /* set ping_failure_count */ if ((res = getsetting(&mysql, LOCAL, "ping_failure_count")) != 0) { - set.ping_failure_count = atoi(res); + set.ping_failure_count = (int)strtol(res, NULL, 10); free(res); } @@ -493,7 +493,7 @@ void read_config_options(void) { /* set ping_method */ if ((res = getsetting(&mysql, LOCAL, "ping_method")) != 0) { - set.ping_method = atoi(res); + set.ping_method = (int)strtol(res, NULL, 10); free(res); } @@ -502,7 +502,7 @@ void read_config_options(void) { /* set ping_retries */ if ((res = getsetting(&mysql, LOCAL, "ping_retries")) != 0) { - set.ping_retries = atoi(res); + set.ping_retries = (int)strtol(res, NULL, 10); free(res); } @@ -511,7 +511,7 @@ void read_config_options(void) { /* set ping_timeout */ if ((res = getsetting(&mysql, LOCAL, "ping_timeout")) != 0) { - set.ping_timeout = atoi(res); + set.ping_timeout = (int)strtol(res, NULL, 10); free(res); } else { set.ping_timeout = 400; @@ -522,7 +522,7 @@ void read_config_options(void) { /* set snmp_retries */ if ((res = getsetting(&mysql, LOCAL, "snmp_retries")) != 0) { - set.snmp_retries = atoi(res); + set.snmp_retries = (int)strtol(res, NULL, 10); free(res); } else { set.snmp_retries = 3; @@ -564,7 +564,7 @@ void read_config_options(void) { /* get Cacti defined max threads override spine.conf */ if (set.threads_set == FALSE) { if ((res = getpsetting(&mysql, mode, "threads")) != 0) { - set.threads = atoi(res); + set.threads = (int)strtol(res, NULL, 10); free(res); if (set.threads > MAX_THREADS) { set.threads = MAX_THREADS; @@ -577,7 +577,7 @@ void read_config_options(void) { /* get the poller_interval for those who have elected to go with a 1 minute polling interval */ if ((res = getsetting(&mysql, LOCAL, "poller_interval")) != 0) { - set.poller_interval = atoi(res); + set.poller_interval = (int)strtol(res, NULL, 10); free(res); } else { set.poller_interval = 0; @@ -592,7 +592,7 @@ void read_config_options(void) { /* get the concurrent_processes variable to determine thread sleep values */ if ((res = getsetting(&mysql, LOCAL, "concurrent_processes")) != 0) { - set.num_parent_processes = atoi(res); + set.num_parent_processes = (int)strtol(res, NULL, 10); free(res); } else { set.num_parent_processes = 1; @@ -603,7 +603,7 @@ void read_config_options(void) { /* get the script timeout to establish timeouts */ if ((res = getsetting(&mysql, LOCAL, "script_timeout")) != 0) { - set.script_timeout = atoi(res); + set.script_timeout = (int)strtol(res, NULL, 10); free(res); if (set.script_timeout < 5) { set.script_timeout = 5; @@ -626,7 +626,7 @@ void read_config_options(void) { /* get spine_log_level */ if ((res = getsetting(&mysql, LOCAL, "spine_log_level")) != 0) { - set.spine_log_level = atoi(res); + set.spine_log_level = (int)strtol(res, NULL, 10); free(res); } @@ -635,7 +635,7 @@ void read_config_options(void) { /* get the number of script server processes to run */ if ((res = getsetting(&mysql, LOCAL, "php_servers")) != 0) { - set.php_servers = atoi(res); + set.php_servers = (int)strtol(res, NULL, 10); free(res); if (set.php_servers > MAX_PHP_SERVERS) { @@ -654,7 +654,7 @@ void read_config_options(void) { /* get the number of active profiles on the system run */ if ((res = getsetting(&mysql, LOCAL, "active_profiles")) != 0) { - set.active_profiles = atoi(res); + set.active_profiles = (int)strtol(res, NULL, 10); free(res); if (set.active_profiles <= 0) { @@ -669,7 +669,7 @@ void read_config_options(void) { /* get the number of snmp_ports in use */ if ((res = getsetting(&mysql, LOCAL, "total_snmp_ports")) != 0) { - set.total_snmp_ports = atoi(res); + set.total_snmp_ports = (int)strtol(res, NULL, 10); free(res); if (set.total_snmp_ports <= 0) { @@ -736,7 +736,7 @@ void read_config_options(void) { /* determine the maximum oid's to obtain in a single get request */ if ((res = getsetting(&mysql, LOCAL, "max_get_size")) != 0) { - set.snmp_max_get_size = atoi(res); + set.snmp_max_get_size = (int)strtol(res, NULL, 10); free(res); if (set.snmp_max_get_size > 128) { @@ -749,38 +749,43 @@ void read_config_options(void) { /* log the snmp_max_get_size variable */ SPINE_LOG_DEBUG(("DEBUG: The Maximum SNMP OID Get Size is %i", set.snmp_max_get_size)); - #ifndef NETSNMP_DISABLE_MD5 - strcat(spine_auth, "MD5"); - #endif + { + int off = 0; - strcat(spine_auth, (strlen(spine_auth) > 0 ? ",SHA":"SHA")); + #ifndef NETSNMP_DISABLE_MD5 + off += snprintf(spine_auth + off, sizeof(spine_auth) - off, "MD5"); + #endif - #if defined(NETSNMP_USMAUTH_HMAC128SHA224) - strcat(spine_auth, ",SHA224,SHA256"); - #endif + off += snprintf(spine_auth + off, sizeof(spine_auth) - off, "%sSHA", off > 0 ? "," : ""); - #if defined(NETSNMP_USMAUTH_HMAC192SHA256) - strcat(spine_auth, ",SHA384,SHA512"); - #endif + #if defined(NETSNMP_USMAUTH_HMAC128SHA224) + off += snprintf(spine_auth + off, sizeof(spine_auth) - off, ",SHA224,SHA256"); + #endif - #ifndef NETSNMP_DISABLE_DES - strcat(spine_priv, "DES"); - #endif + #if defined(NETSNMP_USMAUTH_HMAC192SHA256) + off += snprintf(spine_auth + off, sizeof(spine_auth) - off, ",SHA384,SHA512"); + #endif + } - #ifdef HAVE_AES - // cppcheck-suppress knownConditionTrueFalse - strcat(spine_priv, (strlen(spine_priv) > 0 ? ",AES128":"AES128")); - #endif + { + int off = 0; - #if defined(NETSNMP_DRAFT_BLUMENTHAL_AES_04) - // cppcheck-suppress knownConditionTrueFalse - strcat(spine_priv, (strlen(spine_priv) > 0 ? ",AES192":"AES192")); - #endif + #ifndef NETSNMP_DISABLE_DES + off += snprintf(spine_priv + off, sizeof(spine_priv) - off, "DES"); + #endif - #if defined(NETSNMP_DRAFT_BLUMENTHAL_AES_04) - // cppcheck-suppress knownConditionTrueFalse - strcat(spine_priv, (strlen(spine_priv) > 0 ? ",AES256":"AES256")); - #endif + #ifdef HAVE_AES + off += snprintf(spine_priv + off, sizeof(spine_priv) - off, "%sAES128", off > 0 ? "," : ""); + #endif + + #if defined(NETSNMP_DRAFT_BLUMENTHAL_AES_04) + off += snprintf(spine_priv + off, sizeof(spine_priv) - off, "%sAES192", off > 0 ? "," : ""); + #endif + + #if defined(NETSNMP_DRAFT_BLUMENTHAL_AES_04) + off += snprintf(spine_priv + off, sizeof(spine_priv) - off, "%sAES256", off > 0 ? "," : ""); + #endif + } snprintf(spine_capabilities, BUFSIZE, "{ authProtocols: \"%s\", privProtocols: \"%s\" }", spine_auth, spine_priv); @@ -897,59 +902,87 @@ void poller_push_data_to_main(void) { rows = 0; if (num_rows > 0) { + int remaining; + while ((row = mysql_fetch_row(result))) { 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); - - 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', ", tmpstr); + + 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 +991,8 @@ void poller_push_data_to_main(void) { } if (rows > 0) { - sqlp += sprintf(sqlp, "%s", suffix); + int remaining = HUGE_BUFSIZE - (sqlp - sqlbuf); + sqlp += snprintf(sqlp, remaining, "%s", suffix); db_insert(&mysqlr, REMOTE, sqlbuf); } } @@ -1070,12 +1104,16 @@ int read_spine_config(const char *file) { if (chars != NULL && !feof(fp) && *buff != '#' && *buff != ' ' && *buff != '\n') { sscanf(buff, "%15s %255s", p1, p2); + if (strlen(p2) >= 255) { + SPINE_LOG(("WARNING: Configuration value for '%s' may be truncated", p1)); + } + if (STRIMATCH(p1, "RDB_Host")) STRNCOPY(set.rdb_host, p2); else if (STRIMATCH(p1, "RDB_Database")) STRNCOPY(set.rdb_db, p2); else if (STRIMATCH(p1, "RDB_User")) STRNCOPY(set.rdb_user, p2); else if (STRIMATCH(p1, "RDB_Pass")) STRNCOPY(set.rdb_pass, p2); - else if (STRIMATCH(p1, "RDB_Port")) set.rdb_port = atoi(p2); - else if (STRIMATCH(p1, "RDB_UseSSL")) set.rdb_ssl = atoi(p2); + else if (STRIMATCH(p1, "RDB_Port")) set.rdb_port = (int)strtol(p2, NULL, 10); + else if (STRIMATCH(p1, "RDB_UseSSL")) set.rdb_ssl = (int)strtol(p2, NULL, 10); else if (STRIMATCH(p1, "RDB_SSL_Key")) STRNCOPY(set.rdb_ssl_key, p2); else if (STRIMATCH(p1, "RDB_SSL_Cert")) STRNCOPY(set.rdb_ssl_cert, p2); else if (STRIMATCH(p1, "RDB_SSL_CA")) STRNCOPY(set.rdb_ssl_ca, p2); @@ -1083,12 +1121,12 @@ int read_spine_config(const char *file) { else if (STRIMATCH(p1, "DB_Database")) STRNCOPY(set.db_db, p2); else if (STRIMATCH(p1, "DB_User")) STRNCOPY(set.db_user, p2); else if (STRIMATCH(p1, "DB_Pass")) STRNCOPY(set.db_pass, p2); - else if (STRIMATCH(p1, "DB_Port")) set.db_port = atoi(p2); - else if (STRIMATCH(p1, "DB_UseSSL")) set.db_ssl = atoi(p2); + else if (STRIMATCH(p1, "DB_Port")) set.db_port = (int)strtol(p2, NULL, 10); + else if (STRIMATCH(p1, "DB_UseSSL")) set.db_ssl = (int)strtol(p2, NULL, 10); else if (STRIMATCH(p1, "DB_SSL_Key")) STRNCOPY(set.db_ssl_key, p2); else if (STRIMATCH(p1, "DB_SSL_Cert")) STRNCOPY(set.db_ssl_cert, p2); else if (STRIMATCH(p1, "DB_SSL_CA")) STRNCOPY(set.db_ssl_ca, p2); - else if (STRIMATCH(p1, "Poller")) set.poller_id = atoi(p2); + else if (STRIMATCH(p1, "Poller")) set.poller_id = (int)strtol(p2, NULL, 10); else if (STRIMATCH(p1, "DB_PreG")) { if (!set.stderr_notty) { fprintf(stderr,"WARNING: DB_PreG is no longer supported\n"); @@ -1222,18 +1260,25 @@ char *get_date_format(void) { switch (set.log_datetime_format) { case GD_MO_D_Y: snprintf(log_fmt, GD_FMT_SIZE, "%%m%c%%d%c%%Y %%H:%%M:%%S - ", log_sep, log_sep); + break; case GD_MN_D_Y: snprintf(log_fmt, GD_FMT_SIZE, "%%b%c%%d%c%%Y %%H:%%M:%%S - ", log_sep, log_sep); + break; case GD_D_MO_Y: snprintf(log_fmt, GD_FMT_SIZE, "%%d%c%%m%c%%Y %%H:%%M:%%S - ", log_sep, log_sep); + break; case GD_D_MN_Y: snprintf(log_fmt, GD_FMT_SIZE, "%%d%c%%b%c%%Y %%H:%%M:%%S - ", log_sep, log_sep); + break; case GD_Y_MO_D: snprintf(log_fmt, GD_FMT_SIZE, "%%Y%c%%m%c%%d %%H:%%M:%%S - ", log_sep, log_sep); + break; case GD_Y_MN_D: snprintf(log_fmt, GD_FMT_SIZE, "%%Y%c%%b%c%%d %%H:%%M:%%S - ", log_sep, log_sep); + break; default: snprintf(log_fmt, GD_FMT_SIZE, "%%Y%c%%m%c%%d %%H:%%M:%%S - ", log_sep, log_sep); + break; } return (log_fmt); @@ -1365,7 +1410,11 @@ int spine_log(const char *format, ...) { /* append a line feed to the log message if needed */ if (!strstr(flogmessage, "\n")) { - strcat(flogmessage, "\n"); + size_t flog_len = strlen(flogmessage); + if (flog_len < LOGSIZE - 1) { + flogmessage[flog_len] = '\n'; + flogmessage[flog_len + 1] = '\0'; + } } if ((IS_LOGGING_TO_FILE() && @@ -1634,12 +1683,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 +2082,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);