From 1c1b3f9e83fc0794d820547d6dca24824bb8ba98 Mon Sep 17 00:00:00 2001 From: Steven Bell Date: Sun, 19 Feb 2017 00:56:27 -0500 Subject: [PATCH] Implemented certificate checking conditionally if compiler finds upsclient library version >= 2.7. Otherwise will show warnings if this feature is used. --- configure.ac | 5 ++ src/collectd.conf.in | 2 + src/collectd.conf.pod | 24 +++++++ src/nut.c | 185 +++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 183 insertions(+), 33 deletions(-) diff --git a/configure.ac b/configure.ac index 1b9b3fbf..572bedec 100644 --- a/configure.ac +++ b/configure.ac @@ -5186,6 +5186,11 @@ if test "x$with_libupsclient" = "xyes"; then [with_libupsclient="no (symbol upscli_connect not found)"] ) + AC_CHECK_LIB([upsclient], [upscli_init], + [AC_DEFINE([WITH_UPSCLIENT_27], [1], [At least version 2-7])], + [] + ) + LDFLAGS="$SAVE_LDFLAGS" fi diff --git a/src/collectd.conf.in b/src/collectd.conf.in index ff7b01b4..261d6a1f 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -943,6 +943,8 @@ # # UPS "upsname@hostname:port" # ForceSSL true +# VerifyPeer true +# CAPath "/path/to/folder" # # diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 4c045a6d..81bc0743 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -5122,6 +5122,30 @@ L. Stops connections from falling back to unsecured if an SSL connection cannot be established. Defaults to false if undeclared. +=item B I|I + +If set to true, requires a CAPath be provided. Will use the CAPath to find +certificates to use as Trusted Certificates to validate a upsd server certificate. +If validation of the upsd server certificate fails, the connection will not be +established. If ForceSSL is undeclared or set to false, setting VerifyPeer to true +will override and set ForceSSL to true. + +=item B I/path/to/certs/folder + +If VerifyPeer is set to true, this is required. Otherwise this is ignored. +The folder pointed at must contain certificate(s) named according to their hash. +Ex: XXXXXXXX.Y where X is the hash value of a cert and Y is 0. If name collisions +occur because two different certs have the same hash value, Y can be incremented +in order to avoid conflict. To create a symbolic link to a certificate the following +command can be used from within the directory where the cert resides: + +C + +Alternatively, the package openssl-perl provides a command C that will +generate links like the one described above for ALL certs in a given folder. +Example usage: +C + =back =head2 Plugin C diff --git a/src/nut.c b/src/nut.c index 0d506f03..5acbdde6 100644 --- a/src/nut.c +++ b/src/nut.c @@ -54,9 +54,11 @@ static nut_ups_t *upslist_head = NULL; static pthread_mutex_t read_lock = PTHREAD_MUTEX_INITIALIZER; static int read_busy = 0; -static const char *config_keys[] = {"UPS", "FORCESSL"}; +static const char *config_keys[] = {"UPS", "FORCESSL", "VERIFYPEER", "CAPATH"}; static int config_keys_num = STATIC_ARRAY_SIZE(config_keys); static int force_ssl = 0; // Initialized to default of 0 (false) +static int verify_peer = 0; // Initialized to default of 0 (false) +static char *ca_path = NULL; static void free_nut_ups_t(nut_ups_t *ups) { if (ups->conn != NULL) { @@ -112,11 +114,38 @@ static int nut_force_ssl(const char *value) { return (0); } /* int nut_parse_force_ssl */ +static int nut_verify_peer(const char *value) { + if (strcasecmp(value, "true") == 0) + verify_peer = 1; + else if (strcasecmp(value, "false") == 0) + verify_peer = 0; // Should already be set to 0 from initialization + else { + verify_peer = 0; + WARNING("nut plugin: nut_verify_peer: invalid VERIFYPEER value " + "found. Defaulting to false."); + } + return (0); +} /* int nut_verify_peer */ + +static int nut_ca_path(const char *value) { + if (value != NULL && strcmp(value, "") != 0) { + ca_path = malloc(strlen(value) + 1); + strncpy(ca_path, value, (strlen(value) + 1)); + } else { + ca_path = NULL; // Should alread be set to NULL from initialization + } + return (0); +} /* int nut_ca_path */ + static int nut_config(const char *key, const char *value) { if (strcasecmp(key, "UPS") == 0) return (nut_add_ups(value)); else if (strcasecmp(key, "FORCESSL") == 0) return (nut_force_ssl(value)); + else if (strcasecmp(key, "VERIFYPEER") == 0) + return (nut_verify_peer(value)); + else if (strcasecmp(key, "CAPATH") == 0) + return (nut_ca_path(value)); else return (-1); } /* int nut_config */ @@ -139,14 +168,125 @@ static void nut_submit(nut_ups_t *ups, const char *type, plugin_dispatch_values(&vl); } /* void nut_submit */ +static int nut_connect(nut_ups_t *ups) { +#ifdef WITH_UPSCLIENT_27 + int status; + int ssl_status; + int ssl_flags; + + if (verify_peer == 1 && force_ssl == 0) { + WARNING("nut plugin: nut_connect: VerifyPeer true but ForceSSL " + "false. Setting ForceSSL to true."); + force_ssl = 1; + } + + if (verify_peer == 1 && ca_path == NULL) { + ERROR("nut plugin: nut_connect: VerifyPeer true but missing " + "CAPath value."); + return (-1); + } + + if (verify_peer == 1) { + status = upscli_init(verify_peer, ca_path, NULL, NULL); + + if (status != 1) { + ERROR("nut plugin: nut_connect: upscli_init (%i, %s) failed: %s", + verify_peer, ca_path, upscli_strerror(ups->conn)); + upscli_cleanup(); + return (-1); + } + } /* if (verify_peer == 1) */ + + if (verify_peer == 1) + ssl_flags = (UPSCLI_CONN_REQSSL | UPSCLI_CONN_CERTVERIF); + else if (force_ssl == 1) + ssl_flags = UPSCLI_CONN_REQSSL; + else + ssl_flags = UPSCLI_CONN_TRYSSL; + + status = upscli_connect(ups->conn, ups->hostname, ups->port, ssl_flags); + + if (status != 0) { + ERROR("nut plugin: nut_connect: upscli_connect (%s, %i) failed: %s", + ups->hostname, ups->port, upscli_strerror(ups->conn)); + sfree(ups->conn); + upscli_cleanup(); + return (-1); + } /* if (status != 0) */ + + INFO("nut plugin: Connection to (%s, %i) established.", ups->hostname, + ups->port); + + // Output INFO or WARNING based on SSL and VERIFICATION + ssl_status = upscli_ssl(ups->conn); // 1 for SSL, 0 for not, -1 for error + if (ssl_status == 1 && verify_peer == 1) { + INFO("nut plugin: Connection is secured with SSL and certificate " + "has been verified."); + } else if (ssl_status == 1) { + INFO("nut plugin: Connection is secured with SSL with no verification " + "of server SSL certificate."); + } else if (ssl_status == 0) { + WARNING("nut plugin: Connection is unsecured (no SSL)."); + } else { + ERROR("nut plugin: nut_connect: upscli_ssl failed: %s", + upscli_strerror(ups->conn)); + sfree(ups->conn); + upscli_cleanup(); + return (-1); + } /* if (ssl_status == 1 && verify_peer == 1) */ + return (0); + +#else /* #ifdef WITH_UPSCLIENT_27 */ + int status; + int ssl_status; + int ssl_flags; + + if (verify_peer == 1 || ca_path != NULL) { + WARNING("nut plugin: nut_connect: Dependency libupsclient version " + "insufficient (<2.7) for VerifyPeer support. Ignoring VerifyPeer " + "and CAPath."); + } + + if (force_ssl == 1) + ssl_flags = UPSCLI_CONN_REQSSL; + else + ssl_flags = UPSCLI_CONN_TRYSSL; + + status = upscli_connect(ups->conn, ups->hostname, ups->port, ssl_flags); + + if (status != 0) { + ERROR("nut plugin: nut_connect: upscli_connect (%s, %i) failed: %s", + ups->hostname, ups->port, upscli_strerror(ups->conn)); + sfree(ups->conn); + return (-1); + } /* if (status != 0) */ + + INFO("nut plugin: Connection to (%s, %i) established.", ups->hostname, + ups->port); + + // Output INFO or WARNING based on SSL + ssl_status = upscli_ssl(ups->conn); // 1 for SSL, 0 for not, -1 for error + if (ssl_status == 1) { + INFO("nut plugin: Connection is secured with SSL with no verification " + "of server SSL certificate."); + } else if (ssl_status == 0) { + WARNING("nut plugin: Connection is unsecured (no SSL)."); + } else { + ERROR("nut plugin: nut_connect: upscli_ssl failed: %s", + upscli_strerror(ups->conn)); + sfree(ups->conn); + return (-1); + } /* if (ssl_status == 1 && verify_peer == 1) */ + return (0); +#endif +} + static int nut_read_one(nut_ups_t *ups) { const char *query[3] = {"VAR", ups->upsname, NULL}; unsigned int query_num = 2; char **answer; unsigned int answer_num; int status; - int ssl_status; - int ssl_flags; /* (Re-)Connect if we have no connection */ if (ups->conn == NULL) { @@ -156,36 +296,9 @@ static int nut_read_one(nut_ups_t *ups) { return (-1); } - if (force_ssl == 1) - ssl_flags = UPSCLI_CONN_REQSSL; - else - ssl_flags = UPSCLI_CONN_TRYSSL; - - status = upscli_connect(ups->conn, ups->hostname, ups->port, ssl_flags); - - if (status != 0) { - ERROR("nut plugin: nut_read_one: upscli_connect (%s, %i) failed: %s", - ups->hostname, ups->port, upscli_strerror(ups->conn)); - sfree(ups->conn); - return (-1); - } /* if (status != 0) */ - - INFO("nut plugin: Connection to (%s, %i) established.", ups->hostname, - ups->port); - - // Output INFO or WARNING based on SSL and VERIFICATION - ssl_status = upscli_ssl(ups->conn); // 1 for SSL, 0 for not, -1 for error - if (ssl_status == 1){ - INFO("nut plugin: Connection is secured with SSL."); - } - else if (ssl_status == 0){ - WARNING("nut plugin: Connection is unsecured (no SSL)."); - }else{ - ERROR("nut plugin: nut_read_one: upscli_ssl failed: %s", - upscli_strerror(ups->conn)); - sfree(ups->conn); - return (-1); - } /* if (ssl_status == 1 && verify_peer == 1) */ + status = nut_connect(ups); + if (status == -1) + return -1; } /* if (ups->conn == NULL) */ @@ -197,6 +310,9 @@ static int nut_read_one(nut_ups_t *ups) { ups->upsname, upscli_strerror(ups->conn)); upscli_disconnect(ups->conn); sfree(ups->conn); +#ifdef WITH_UPSCLIENT_27 + upscli_cleanup(); +#endif return (-1); } @@ -284,6 +400,9 @@ static int nut_shutdown(void) { free_nut_ups_t(this); this = next; } +#ifdef WITH_UPSCLIENT_27 + upscli_cleanup(); +#endif return (0); } /* int nut_shutdown */ -- 2.11.0