From 018433a6da60c6395baaaa0833d5c50ac6645552 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 6 May 2024 03:20:09 +0200 Subject: Adding upstream version 3.2.1. Signed-off-by: Daniel Baumann --- src/Makefile.in | 87 ++ src/acl.c | 705 +++++++++++++ src/check_nrpe.c | 1633 ++++++++++++++++++++++++++++++ src/nrpe.c | 2907 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/snprintf.c | 1452 +++++++++++++++++++++++++++ src/utils.c | 590 +++++++++++ 6 files changed, 7374 insertions(+) create mode 100644 src/Makefile.in create mode 100644 src/acl.c create mode 100644 src/check_nrpe.c create mode 100644 src/nrpe.c create mode 100644 src/snprintf.c create mode 100644 src/utils.c (limited to 'src') diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..ba0d381 --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,87 @@ +############################### +# +# NRPE Makefile +# +############################### + +srcdir=@srcdir@ + +# Source code directories +SRC_INCLUDE=@srcdir@/../include +CFG_INCLUDE=../include + +# Mainly used for rpmbuild +# DESTDIR= + +CC=@CC@ +CFLAGS=@CFLAGS@ @DEFS@ -I $(CFG_INCLUDE) -I $(SRC_INCLUDE) +LDFLAGS=@LDFLAGS@ @LIBS@ +SOCKETLIBS=@SOCKETLIBS@ +LIBWRAPLIBS=@LIBWRAPLIBS@ +OTHERLIBS=@OTHERLIBS@ + +CP=@CP@ + +prefix=$(DESTDIR)@prefix@ +exec_prefix=$(DESTDIR)@exec_prefix@ +CFGDIR=$(DESTDIR)@pkgsysconfdir@ +BINDIR=$(DESTDIR)@bindir@ +SBINDIR=$(DESTDIR)@sbindir@ +LIBEXECDIR=$(DESTDIR)@libexecdir@ +INSTALL=@INSTALL@ +NAGIOS_INSTALL_OPTS=@NAGIOS_INSTALL_OPTS@ +NRPE_INSTALL_OPTS=@NRPE_INSTALL_OPTS@ + +PLUGINSDIR=$(DESTDIR)@pluginsdir@ +PIDDIR=$(DESTDIR)@piddir@ +TMPFILESDIR=$(DESTDIR)@tmpfilesd@ +SRC_TMPFILE=@src_tmpfile@ + + +# Generated automatically from configure script +SNPRINTF_O=@SNPRINTF_O@ + + +all: nrpe check_nrpe + +nrpe: $(srcdir)/nrpe.c $(srcdir)/utils.c $(srcdir)/acl.c $(SRC_INCLUDE)/nrpe.h $(SRC_INCLUDE)/utils.h $(SRC_INCLUDE)/common.h $(CFG_INCLUDE)/config.h $(SRC_INCLUDE)/acl.h $(SNPRINTF_O) + $(CC) $(CFLAGS) -o $@ $(srcdir)/nrpe.c $(srcdir)/utils.c $(srcdir)/acl.c $(LDFLAGS) $(SOCKETLIBS) $(LIBWRAPLIBS) $(SNPRINTF_O) $(OTHERLIBS) + +check_nrpe: $(srcdir)/check_nrpe.c $(srcdir)/utils.c $(SRC_INCLUDE)/utils.h $(SRC_INCLUDE)/common.h $(CFG_INCLUDE)/config.h + $(CC) $(CFLAGS) -o $@ $(srcdir)/check_nrpe.c $(srcdir)/utils.c $(LDFLAGS) $(SOCKETLIBS) $(SNPRINTF_O) $(OTHERLIBS) + +install: + $(MAKE) install-plugin + $(MAKE) install-daemon + +install-plugin: install-uninstall + $(INSTALL) -m 775 $(NAGIOS_INSTALL_OPTS) -d $(LIBEXECDIR) + $(INSTALL) -m 775 $(NAGIOS_INSTALL_OPTS) -d $(PLUGINSDIR) + $(INSTALL) -m 775 $(NAGIOS_INSTALL_OPTS) check_nrpe $(PLUGINSDIR) + +install-daemon: install-uninstall + $(INSTALL) -m 755 nrpe $(SBINDIR) + @if test ! -d "$(PIDDIR)" ; then \ + echo $(INSTALL) -m 755 $(NRPE_INSTALL_OPTS) -d $(PIDDIR); \ + $(INSTALL) -m 755 $(NRPE_INSTALL_OPTS) -d $(PIDDIR); \ + fi + @if test "$(TMPFILESDIR)" != "N/A" -a x$(SRC_TMPFILE) != x ; then \ + echo $(INSTALL) -m 755 -d `dirname $(TMPFILESDIR)`; \ + $(INSTALL) -m 755 -d `dirname $(TMPFILESDIR)`; \ + echo $(INSTALL) -m 644 ../startup/$(SRC_TMPFILE) $(TMPFILESDIR); \ + $(INSTALL) -m 644 ../startup/$(SRC_TMPFILE) $(TMPFILESDIR); \ + fi + +install-uninstall: + $(INSTALL) -m 755 -d $(SBINDIR) + $(INSTALL) -m 755 ../uninstall $(SBINDIR)/nrpe-uninstall + +clean: + rm -f core nrpe check_nrpe $(SNPRINTF_O) + rm -f *~ */*~ + rm -rf nrpe.dSYM check_nrpe.dSYM + +distclean: clean + rm -f Makefile + +devclean: distclean diff --git a/src/acl.c b/src/acl.c new file mode 100644 index 0000000..f38c321 --- /dev/null +++ b/src/acl.c @@ -0,0 +1,705 @@ +/**************************************************************************** + * + * acl.c - a small library for nrpe.c. It adds IPv4 subnets support to ACL in nrpe. + * + * License: GPLv2 + * Copyright (c) 2011 Kaspersky Lab ZAO + * + * Description: + * + * acl.c creates two linked lists. One is for IPv4 hosts and networks, another + * is for domain names. All connecting hosts (if allowed_hosts is defined) + * are checked in these two lists. + * + * Note: + * Only ANCII names are supported in ACL. + * + * License Notice: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + ****************************************************************************/ + +#include "../include/config.h" +#include "../include/common.h" +#include "../include/utils.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../include/acl.h" + +extern int debug; + +/* This function checks if a char argument from valid char range. + * Valid range is: ASCII only, a number or a letter, a space, a dot, a slash, a dash, a comma. + * + * Returns: + * 0 - char isn't from valid group + * 1 - char is a number + * 2 - char is a letter + * 3 - char is a space(' ') + * 4 - char is a dot('.') + * 5 - char is a slash('/') + * 6 - char is a dash('-') + * 7 - char is a comma(',') + */ + +int isvalidchar(int c) { + if (!isascii(c)) + return 0; + + if (isdigit(c)) + return 1; + + if (isalpha(c)) + return 2; + + if (isspace(c)) + return 3; + + switch (c) { + case '.': + return 4; + case '/': + return 5; + case '-': + return 6; + case ',': + return 7; + default: + return 0; + } +} + +/* + * Get substring from allowed_hosts from s position to e position. + */ + +char * acl_substring(char *string, int s, int e) { + char *substring; + int len = e - s; + + if (len < 0) + return NULL; + + if ( (substring = malloc(len + 1)) == NULL) + return NULL; + + memmove(substring, string + s, len + 1); + return substring; +} + +/* + * Add IPv4 host or network to IP ACL. IPv4 format is X.X.X.X[/X]. + * Host will be added to ACL only if it has passed IPv4 format check. + * + * Returns: + * 1 - on success + * 0 - on failure + * + * States for IPv4 format check: + * 0 - numbers(-> 1), dot(-> -1), slash(-> -1), other(-> -1) + * 1 - numbers(-> 1), dot(-> 2), slash(-> -1), other(-> -1) + * 2 - numbers(-> 3), dot(-> -1), slash(-> -1), other(-> -1) + * 3 - numbers(-> 3), dot(-> 4), slash(-> -1), other(-> -1) + * 4 - numbers(-> 5), dot(-> -1), slash(-> -1), other(-> -1) + * 5 - numbers(-> 5), dot(-> 6), slash(-> -1), other(-> -1) + * 6 - numbers(-> 7), dot(-> -1), slash(-> -1), other(-> -1) + * 7 - numbers(-> 7), dor(-> -1), slash(-> 8), other(-> -1) + * 8 - numbers(-> 9), dor(-> -1), slash(-> -1), other(-> -1) + * 9 - numbers(-> 9), dot(-> -1), slash(-> -1), other(-> -1) + * + * Good states are 7(IPv4 host) and 9(IPv4 network) + */ + +int add_ipv4_to_acl(char *ipv4) { + + int state = 0; + int octet = 0; + int index = 0; /* position in data array */ + int data[5]; /* array to store ip octets and mask */ + int len = strlen(ipv4); + int i, c; + unsigned long ip, mask; + struct ip_acl *ip_acl_curr; + + if(debug == TRUE) + logit(LOG_INFO, "add_ipv4_to_acl: checking ip-address >%s<", ipv4); + + /* Check for min and max IPv4 valid length */ + if (len < 7 || len > 18) { + logit(LOG_INFO, "add_ipv4_to_acl: Error, ip-address >%s< incorrect length", ipv4); + return 0; + } + + /* default mask for ipv4 */ + data[4] = 32; + + /* Basic IPv4 format check */ + for (i = 0; i < len; i++) { + /* Return 0 on error state */ + if (state == -1) { + if(debug == TRUE) + logit(LOG_INFO, "add_ipv4_to_acl: Error, ip-address >%s< incorrect " + "format, continue with next check ...", ipv4); + return 0; + } + + c = ipv4[i]; + + switch (c) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + octet = octet * 10 + CHAR_TO_NUMBER(c); + switch (state) { + case 0: case 2: case 4: case 6: case 8: + state++; + break; + } + break; + case '.': + switch (state) { + case 1: case 3: case 5: + data[index++] = octet; + octet = 0; + state++; + break; + default: + state = -1; + } + break; + case '/': + switch (state) { + case 7: + data[index++] = octet; + octet = 0; + state++; + break; + default: + state = -1; + } + break; + default: + state = -1; + } + } + + /* Exit state handling */ + switch (state) { + case 7: case 9: + data[index] = octet; + break; + default: + /* Bad states */ + logit(LOG_INFO, "add_ipv4_to_acl: Error, ip-address >%s< bad state", ipv4); + return 0; + } + + /* + * Final IPv4 format check. + */ + for (i=0; i < 4; i++) { + if (data[i] < 0 || data[i] > 255) { + logit(LOG_ERR,"Invalid IPv4 address/network format(%s) in allowed_hosts option\n",ipv4); + return 0; + } + } + + if (data[4] < 0 || data[4] > 32) { + logit(LOG_ERR,"Invalid IPv4 network mask format(%s) in allowed_hosts option\n",ipv4); + return 0; + } + + /* Convert ip and mask to unsigned long */ + ip = htonl((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]); + mask = htonl(-1 << (32 - data[4])); + + /* Wrong network address */ + if ( (ip & mask) != ip) { + logit(LOG_ERR,"IP address and mask do not match in %s\n",ipv4); + return 0; + } + + /* Add addr to ip_acl list */ + if ( (ip_acl_curr = malloc(sizeof(*ip_acl_curr))) == NULL) { + logit(LOG_ERR,"Can't allocate memory for ACL, malloc error\n"); + return 0; + } + + /* Save result in ACL ip list */ + ip_acl_curr->family = AF_INET; + ip_acl_curr->addr.s_addr = ip; + ip_acl_curr->mask.s_addr = mask; + ip_acl_curr->next = NULL; + + if (ip_acl_head == NULL) { + ip_acl_head = ip_acl_curr; + } else { + ip_acl_prev->next = ip_acl_curr; + } + ip_acl_prev = ip_acl_curr; + + if(debug == TRUE) + logit(LOG_INFO, "add_ipv4_to_acl: ip-address >%s< correct, adding.", ipv4); + + return 1; +} + +/* + * Add IPv6 host or network to IP ACL. Host will be added to ACL only if + * it has passed IPv6 format check. + * + */ + +int add_ipv6_to_acl(char *ipv6) { + char *ipv6tmp; + char *addr_part, *mask_part; + struct in6_addr addr; + struct in6_addr mask; + int maskval; + int byte, bit; + int nbytes = sizeof(mask.s6_addr) / sizeof(mask.s6_addr[0]); + int x; + struct ip_acl *ip_acl_curr; + + /* Save temporary copy of ipv6 so we can use the original in error + messages if needed */ + ipv6tmp = strdup(ipv6); + if(NULL == ipv6tmp) { + logit(LOG_ERR, "Memory allocation failed for copy of address: %s\n", + ipv6); + return 0; + } + + addr_part = ipv6tmp; + mask_part = strchr(ipv6tmp, '/'); + if (mask_part) { + *mask_part = '\0'; + ++mask_part; + } + + /* Parse the address itself */ + if(inet_pton(AF_INET6, addr_part, &addr) <= 0) { + free(ipv6tmp); + return 0; + } + + /* Check whether there is a netmask */ + if (mask_part && *mask_part) { + /* If so, build a netmask */ + /* Get the number of bits in the mask */ + maskval = atoi(mask_part); + if(maskval < 0 || maskval > 128) { + free(ipv6tmp); + return 0; + } + + /* Initialize to zero */ + for(x = 0; x < nbytes; x++) { + mask.s6_addr[x] = 0; + } + + /* Set mask based on mask bits */ + byte = 0; + bit = 7; + while(maskval > 0) { + mask.s6_addr[byte] |= 1 << bit; + bit -= 1; + if(bit < 0) { + bit = 7; + byte++; + } + maskval--; + } + } + else { + /* Otherwise, this is a single address */ + for(x = 0; x < nbytes; x++) { + mask.s6_addr[x] = 0xFF; + } + } + + /* Add address to ip_acl list */ + ip_acl_curr = malloc(sizeof(*ip_acl_curr)); + if(NULL == ip_acl_curr) { + logit(LOG_ERR, "Memory allocation failed for ACL: %s\n", ipv6); + return 0; + } + + /* Save result in ACL ip list */ + ip_acl_curr->family = AF_INET6; + for(x = 0; x < nbytes; x++) { + ip_acl_curr->addr6.s6_addr[x] = + addr.s6_addr[x] & mask.s6_addr[x]; + ip_acl_curr->mask6.s6_addr[x] = mask.s6_addr[x]; + } + ip_acl_curr->next = NULL; + + if(NULL == ip_acl_head) { + ip_acl_head = ip_acl_curr; + } + else { + ip_acl_prev->next = ip_acl_curr; + } + ip_acl_prev = ip_acl_curr; + + free(ipv6tmp); + return 1; + } + +/* + * Add domain to DNS ACL list + * Domain will be added only if it has passed domain name check. + * + * In this case domain valid format is: + * 1) Domain names must use only alphanumeric characters and dashes (-). + * 2) Domain names mustn't begin or end with dashes (-). + * 3) Domain names mustn't have more than 63 characters. + * + * Return: + * 1 - for success + * 0 - for failure + * + * 0 - alpha(-> 1), number(-> 1), dot(-> -1), dash(-> -1), all other(-> -1) + * 1 - alpha(-> 1), number(-> 1), dot(-> 2), dash(-> 6), all other(-> -1) + * 2 - alpha(-> 3), number(-> 1), dot(-> -1), dash(-> -1), all other(-> -1) + * 3 - alpha(-> 4), number(-> 1), dot(-> 2), dash(-> 6), all other(-> -1) + * 4 - alpha(-> 5), number(-> 1), dot(-> 2), dash(-> 6), all other(-> -1) + * 5 - alpha(-> 1), number(-> 1), dot(-> 2), dash(-> 6), all other(-> -1) + * 6 - alpha(-> 1), number(-> 1), dot(-> 2), dash(-> 6), all other(-> -1) + + * For real FQDN only 4 and 5 states are good for exit. + * I don't check if top domain exists (com, ru and etc.) + * But in real life NRPE could work in LAN, + * with local domain zones like .local or with names like 'mars' added to /etc/hosts. + * So 1 is good state too. And maybe this check is not necessary at all... + */ + +int add_domain_to_acl(char *domain) { + int state = 0; + int len = strlen(domain); + int i, c; + + struct dns_acl *dns_acl_curr; + + if (len > 63) { + logit(LOG_INFO, + "ADD_DOMAIN_TO_ACL: Error, did not add >%s< to acl list, too long!", + domain); + return 0; + } + + for (i = 0; i < len; i++) { + c = domain[i]; + switch (isvalidchar(c)) { + case 1: + state = 1; + break; + case 2: + switch (state) { + case 0: case 1: case 5: case 6: + state = 1; + break; + case 2: case 3: case 4: + state++; + break; + } + break; + + case 4: + switch (state) { + case 0: case 2: + state = -1; + break; + default: + state = 2; + } + break; + case 6: + switch (state) { + case 0: case 2: + state = -1; + break; + default: + state = 6; + } + break; + default: + logit(LOG_INFO, + "ADD_DOMAIN_TO_ACL: Error, did not add >%s< to acl list, " + "invalid chars!", domain); + /* Not valid chars */ + return 0; + } + } + + /* Check exit code */ + switch (state) { + case 1: case 4: case 5: + /* Add name to domain ACL list */ + if ( (dns_acl_curr = malloc(sizeof(*dns_acl_curr))) == NULL) { + logit(LOG_ERR,"Can't allocate memory for ACL, malloc error\n"); + return 0; + } + strcpy(dns_acl_curr->domain, domain); + dns_acl_curr->next = NULL; + + if (dns_acl_head == NULL) + dns_acl_head = dns_acl_curr; + else + dns_acl_prev->next = dns_acl_curr; + + dns_acl_prev = dns_acl_curr; + if(debug == TRUE) + logit(LOG_INFO, "ADD_DOMAIN_TO_ACL: added >%s< to acl list!", domain); + return 1; + default: + logit(LOG_INFO, + "ADD_DOMAIN_TO_ACL: ERROR, did not add >%s< to acl list, " + "check allowed_host in config file!", domain); + return 0; + } +} + +/* Checks connection host in ACL + * + * Returns: + * 1 - on success + * 0 - on failure + */ + +int is_an_allowed_host(int family, void *host) +{ + struct ip_acl *ip_acl_curr = ip_acl_head; + int nbytes; + int x; + struct dns_acl *dns_acl_curr = dns_acl_head; + struct sockaddr_in *addr; + struct sockaddr_in6 addr6; + struct addrinfo *res, *ai; + struct in_addr tmp; + + while (ip_acl_curr != NULL) { + if(ip_acl_curr->family == family) { + switch(ip_acl_curr->family) { + case AF_INET: + if (debug == TRUE) { + tmp.s_addr = ((struct in_addr*)host)->s_addr; + logit(LOG_INFO, "is_an_allowed_host (AF_INET): is host >%s< " + "an allowed host >%s<\n", + inet_ntoa(tmp), inet_ntoa(ip_acl_curr->addr)); + } + if((((struct in_addr *)host)->s_addr & + ip_acl_curr->mask.s_addr) == + ip_acl_curr->addr.s_addr) { + if (debug == TRUE) + logit(LOG_INFO, "is_an_allowed_host (AF_INET): host is in allowed host list!"); + return 1; + } + break; + case AF_INET6: + nbytes = sizeof(ip_acl_curr->mask6.s6_addr) / + sizeof(ip_acl_curr->mask6.s6_addr[0]); + for(x = 0; x < nbytes; x++) { + if((((struct in6_addr *)host)->s6_addr[x] & + ip_acl_curr->mask6.s6_addr[x]) != + ip_acl_curr->addr6.s6_addr[x]) { + break; + } + } + if(x == nbytes) { + /* All bytes in host's address pass the netmask mask */ + return 1; + } + break; + } + } + ip_acl_curr = ip_acl_curr->next; + } + + while(dns_acl_curr != NULL) { + if (!getaddrinfo(dns_acl_curr->domain, NULL, NULL, &res)) { + + for (ai = res; ai; ai = ai->ai_next) { + + switch(ai->ai_family) { + + case AF_INET: + if(debug == TRUE) { + tmp.s_addr=((struct in_addr *)host)->s_addr; + logit(LOG_INFO, "is_an_allowed_host (AF_INET): is host >%s< " + "an allowed host >%s<\n", + inet_ntoa(tmp), dns_acl_curr->domain); + } + + addr = (struct sockaddr_in*)(ai->ai_addr); + if (addr->sin_addr.s_addr == ((struct in_addr*)host)->s_addr) { + if (debug == TRUE) + logit(LOG_INFO, "is_an_allowed_host (AF_INET): " + "host is in allowed host list!"); + return 1; + } + break; + + case AF_INET6: + memcpy((char*)&addr6, ai->ai_addr, sizeof(addr6)); + if (!memcmp(&addr6.sin6_addr, &host, sizeof(addr6.sin6_addr))) + return 1; + break; + } + } + } + + dns_acl_curr = dns_acl_curr->next; + } + return 0; +} + +/* The trim() function takes a source string and copies it to the destination string, + * stripped of leading and training whitespace. The destination string must be + * allocated at least as large as the source string. + */ + +void trim( char *src, char *dest) { + char *sptr, *dptr; + + for( sptr = src; isspace( *sptr) && *sptr; sptr++); /* Jump past leading spaces */ + for( dptr = dest; !isspace( *sptr) && *sptr; ) { + *dptr = *sptr; + sptr++; + dptr++; + } + *dptr = '\0'; + return; +} + +/* This function splits allowed_hosts to substrings with comma(,) as a delimiter. + * It doesn't check validness of ACL record (add_ipv4_to_acl() and add_domain_to_acl() do), + * just trims spaces from ACL records. + * After this it sends ACL records to add_ipv4_to_acl() or add_domain_to_acl(). + */ + +void parse_allowed_hosts(char *allowed_hosts) { + char *hosts = strdup( allowed_hosts); /* Copy since strtok* modifies original */ + char *saveptr; + char *tok; + const char *delim = ","; + char *trimmed_tok; + int add_to_acl = 0; + + if (debug == TRUE) + logit(LOG_INFO, + "parse_allowed_hosts: parsing the allowed host string >%s< to add to ACL list\n", + allowed_hosts); + +#ifdef HAVE_STRTOK_R + tok = strtok_r(hosts, delim, &saveptr); +#else + if (debug == TRUE) + logit(LOG_INFO,"parse_allowed_hosts: using strtok, this might lead to " + "problems in the allowed_hosts string determination!\n"); + tok = strtok(hosts, delim); +#endif + while( tok) { + trimmed_tok = malloc(sizeof(char) * (strlen(tok) + 1)); + trim(tok, trimmed_tok); + if (debug == TRUE) + logit(LOG_DEBUG, "parse_allowed_hosts: ADDING this record (%s) to ACL list!\n", trimmed_tok); + if (strlen(trimmed_tok) > 0) { + + /* lets check the type of the address before we try and add it to the acl */ + + if (strchr(trimmed_tok, ':') != NULL) { + + /* its an ipv6 address */ + add_to_acl = add_ipv6_to_acl(trimmed_tok); + + } else { + + /* its either a fqdn or an ipv4 address + unfortunately, i don't want to re-invent the wheel here + the logic exists inside of add_ipv4_to_acl() to detect + whether or not it is a ip or not */ + add_to_acl = add_ipv4_to_acl(trimmed_tok); + } + + /* but we only try to add it to a domain if the other tests have failed */ + if (!add_to_acl && !add_domain_to_acl(trimmed_tok)) { + logit(LOG_ERR,"Can't add to ACL this record (%s). Check allowed_hosts option!\n",trimmed_tok); + } else if (debug == TRUE) + logit(LOG_DEBUG,"parse_allowed_hosts: Record added to ACL list!\n"); + } + free( trimmed_tok); +#ifdef HAVE_STRTOK_R + tok = strtok_r(NULL, delim, &saveptr); +#else + tok = strtok(NULL, delim); +#endif + } + + free( hosts); +} + +/* + * Converts mask in unsigned long format to two digit prefix + */ + +unsigned int prefix_from_mask(struct in_addr mask) { + int prefix = 0; + unsigned long bit = 1; + int i; + + for (i = 0; i < 32; i++) { + if (mask.s_addr & bit) + prefix++; + + bit = bit << 1; + } + return (prefix); +} + +/* + * It shows all hosts in ACL lists + */ + +void show_acl_lists(void) +{ + struct ip_acl *ip_acl_curr = ip_acl_head; + struct dns_acl *dns_acl_curr = dns_acl_head; + + logit(LOG_INFO, "Showing ACL lists for both IP and DOMAIN acl's:\n" ); + + while (ip_acl_curr != NULL) { + logit(LOG_INFO, " IP ACL: %s/%u %u\n", inet_ntoa(ip_acl_curr->addr), + prefix_from_mask(ip_acl_curr->mask), ip_acl_curr->addr.s_addr); + ip_acl_curr = ip_acl_curr->next; + } + + while (dns_acl_curr != NULL) { + logit(LOG_INFO, " DNS ACL: %s\n", dns_acl_curr->domain); + dns_acl_curr = dns_acl_curr->next; + } +} diff --git a/src/check_nrpe.c b/src/check_nrpe.c new file mode 100644 index 0000000..ace7041 --- /dev/null +++ b/src/check_nrpe.c @@ -0,0 +1,1633 @@ +/**************************************************************************** + * + * check_nrpe.c - NRPE Plugin For Nagios + * + * License: GPLv2 + * Copyright (c) 2009-2017 Nagios Enterprises + * 1999-2008 Ethan Galstad (nagios@nagios.org) + * + * Command line: + * + * check_nrpe -H [-p port] [-c command] [-to to_sec] + * + * Description: + * + * This plugin will attempt to connect to the NRPE daemon on the specified + * server and port. The daemon will attempt to run the command + * defined as [command]. Program output and return code are sent back + * from the daemon and displayed as this plugin's own + * output and return code. + * + * License Notice: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + ****************************************************************************/ + +#include "config.h" +#include "common.h" +#include "utils.h" + +#define DEFAULT_NRPE_COMMAND "_NRPE_CHECK" /* check version of NRPE daemon */ + +u_short server_port = 0; +char *server_name = NULL; +char *bind_address = NULL; +char *config_file = NULL; +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE +struct sockaddr_storage hostaddr; +#else +struct sockaddr hostaddr; +#endif +int address_family = AF_UNSPEC; +char *command_name = NULL; +int socket_timeout = DEFAULT_SOCKET_TIMEOUT; +char timeout_txt[10]; +int timeout_return_code = -1; +int stderr_to_stdout = 0; +int sd; + +char rem_host[MAX_HOST_ADDRESS_LENGTH]; +char query[MAX_INPUT_BUFFER] = ""; + +int show_help = FALSE; +int show_license = FALSE; +int show_version = FALSE; +int packet_ver = NRPE_PACKET_VERSION_3; +int force_v2_packet = 0; +int payload_size = 0; +extern char *log_file; + +#ifdef HAVE_SSL +# if (defined(__sun) && defined(SOLARIS_10)) || defined(_AIX) || defined(__hpux) +SSL_METHOD *meth; +# else +const SSL_METHOD *meth; +# endif +SSL_CTX *ctx; +SSL *ssl; +int use_ssl = TRUE; +unsigned long ssl_opts = SSL_OP_ALL; +#else +int use_ssl = FALSE; +#endif + +/* SSL/TLS parameters */ +typedef enum _SSL_VER { + SSL_Ver_Invalid = 0, SSLv2 = 1, SSLv2_plus, SSLv3, SSLv3_plus, + TLSv1, TLSv1_plus, TLSv1_1, TLSv1_1_plus, TLSv1_2, TLSv1_2_plus +} SslVer; + +typedef enum _CLNT_CERTS { Ask_For_Cert = 1, Require_Cert = 2 } ClntCerts; + +typedef enum _SSL_LOGGING { + SSL_NoLogging = 0, SSL_LogStartup = 1, SSL_LogIpAddr = 2, + SSL_LogVersion = 4, SSL_LogCipher = 8, SSL_LogIfClientCert = 16, + SSL_LogCertDetails = 32, +} SslLogging; + +struct _SSL_PARMS { + char *cert_file; + char *cacert_file; + char *privatekey_file; + char cipher_list[MAX_FILENAME_LENGTH]; + SslVer ssl_proto_ver; + int allowDH; + ClntCerts client_certs; + SslLogging log_opts; +} sslprm = { +NULL, NULL, NULL, "", SSL_Ver_Invalid, -1, 0, SSL_NoLogging}; +int have_log_opts = FALSE; + +int process_arguments(int, char **, int); +int read_config_file(char *); +const char *state_text (int result); +int translate_state (char *state_text); +void set_timeout_state (char *state); +int parse_timeout_string (char *timeout_str); +void usage(int result); +void setup_ssl(); +void set_sig_handlers(); +int connect_to_remote(); +int send_request(); +int read_response(); +int read_packet(int sock, void *ssl_ptr, v2_packet ** v2_pkt, v3_packet ** v3_pkt); +#ifdef HAVE_SSL +static int verify_callback(int ok, X509_STORE_CTX * ctx); +#endif +void alarm_handler(int); +int graceful_close(int, int); + +int main(int argc, char **argv) +{ + int16_t result; + + result = process_arguments(argc, argv, 0); + + if (result != OK || show_help == TRUE || show_license == TRUE || show_version == TRUE) + usage(result); /* usage() will call exit() */ + + snprintf(timeout_txt, sizeof(timeout_txt), "%d", socket_timeout); + + if (server_port == 0) + server_port = DEFAULT_SERVER_PORT; + if (socket_timeout == -1) + socket_timeout = DEFAULT_SOCKET_TIMEOUT; + if (timeout_return_code == -1) + timeout_return_code = STATE_CRITICAL; + if (sslprm.cipher_list[0] == '\0') +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + strncpy(sslprm.cipher_list, "ALL:!MD5:@STRENGTH:@SECLEVEL=0", MAX_FILENAME_LENGTH - 1); +#else + strncpy(sslprm.cipher_list, "ALL:!MD5:@STRENGTH", MAX_FILENAME_LENGTH - 1); +#endif + if (sslprm.ssl_proto_ver == SSL_Ver_Invalid) + sslprm.ssl_proto_ver = TLSv1_plus; + if (sslprm.allowDH == -1) + sslprm.allowDH = TRUE; + + generate_crc32_table(); /* generate the CRC 32 table */ + setup_ssl(); /* Do all the SSL/TLS set up */ + set_sig_handlers(); /* initialize alarm signal handling */ + result = connect_to_remote(); /* Make the connection */ + if (result != STATE_OK) { + alarm(0); + return result; + } + + result = send_request(); /* Send the request */ + if (result != STATE_OK) + return result; + + result = read_response(); /* Get the response */ + + if (result == -1) { + /* Failure reading from remote, so try version 2 packet */ + logit(LOG_INFO, "Remote %s does not support Version 3 Packets", rem_host); + packet_ver = NRPE_PACKET_VERSION_2; + + /* Rerun the setup */ + setup_ssl(); + set_sig_handlers(); + result = connect_to_remote(); /* Connect */ + if (result != STATE_OK) { + alarm(0); + close_log_file(); /* close the log file */ + return result; + } + + result = send_request(); /* Send the request */ + if (result != STATE_OK) { + close_log_file(); /* close the log file */ + return result; + } + + result = read_response(); /* Get the response */ + } + + if (result != -1 && force_v2_packet == 0 && packet_ver == NRPE_PACKET_VERSION_2) + logit(LOG_DEBUG, "Remote %s accepted a Version %d Packet", rem_host, packet_ver); + + close_log_file(); /* close the log file */ + return result; +} + +/* process command line arguments */ +int process_arguments(int argc, char **argv, int from_config_file) +{ + char optchars[MAX_INPUT_BUFFER]; + int argindex = 0; + int c = 1; + int i = 1; + int has_cert = 0, has_priv_key = 0, rc; + +#ifdef HAVE_GETOPT_LONG + int option_index = 0; + static struct option long_options[] = { + {"host", required_argument, 0, 'H'}, + {"config-file", required_argument, 0, 'f'}, + {"bind", required_argument, 0, 'b'}, + {"command", required_argument, 0, 'c'}, + {"args", required_argument, 0, 'a'}, + {"no-ssl", no_argument, 0, 'n'}, + {"unknown-timeout", no_argument, 0, 'u'}, + {"v2-packets-only", no_argument, 0, '2'}, + {"ipv4", no_argument, 0, '4'}, + {"ipv6", no_argument, 0, '6'}, + {"use-adh", required_argument, 0, 'd'}, + {"ssl-version", required_argument, 0, 'S'}, + {"cipher-list", required_argument, 0, 'L'}, + {"client-cert", required_argument, 0, 'C'}, + {"key-file", required_argument, 0, 'K'}, + {"ca-cert-file", required_argument, 0, 'A'}, + {"ssl-logging", required_argument, 0, 's'}, + {"timeout", required_argument, 0, 't'}, + {"port", required_argument, 0, 'p'}, + {"payload-size", required_argument, 0, 'P'}, + {"log-file", required_argument, 0, 'g'}, + {"help", no_argument, 0, 'h'}, + {"license", no_argument, 0, 'l'}, + {"version", no_argument, 0, 'V'}, + {"stderr-to-stdout", no_argument, 0, 'E'}, + {0, 0, 0, 0} + }; +#endif + + /* no options were supplied */ + if (argc < 2) + return ERROR; + + optind = 0; + snprintf(optchars, MAX_INPUT_BUFFER, "H:f:b:c:a:t:p:S:L:C:K:A:d:s:P:g:246hlnuVE"); + + while (1) { + if (argindex > 0) + break; +#ifdef HAVE_GETOPT_LONG + c = getopt_long(argc, argv, optchars, long_options, &option_index); +#else + c = getopt(argc, argv, optchars); +#endif + if (c == -1 || c == EOF) + break; + + /* process all arguments */ + switch (c) { + + case '?': + case 'h': + show_help = TRUE; + break; + + case 'b': + bind_address = strdup(optarg); + break; + + case 'f': + if (from_config_file) { + printf("Error: The config file should not have a config-file (-f) option.\n"); + break; + } + config_file = strdup(optarg); + break; + + case 'V': + show_version = TRUE; + break; + + case 'l': + show_license = TRUE; + break; + + case 't': + if (from_config_file && socket_timeout != -1) { + logit(LOG_WARNING, "WARNING: Command-line socket timeout overrides the config file option."); + break; + } + socket_timeout=parse_timeout_string(optarg); + if (socket_timeout <= 0) + return ERROR; + break; + + case 'p': + if (from_config_file && server_port != 0) { + logit(LOG_WARNING, "WARNING: Command-line server port overrides the config file option."); + break; + } + server_port = atoi(optarg); + if (server_port <= 0) + return ERROR; + break; + + case 'P': + if (from_config_file && payload_size > 0) { + logit(LOG_WARNING, "WARNING: Command-line payload-size (-P) overrides the config file option."); + break; + } + payload_size = atoi(optarg); + if (payload_size < 0) + return ERROR; + break; + + case 'H': + if (from_config_file && server_name != NULL) { + logit(LOG_WARNING, "WARNING: Command-line server name overrides the config file option."); + break; + } + server_name = strdup(optarg); + break; + + case 'E': + if (from_config_file && stderr_to_stdout != 0) { + logit(LOG_WARNING, "WARNING: Command-line stderr redirection overrides the config file option."); + break; + } + stderr_to_stdout = 1; + break; + + case 'c': + if (from_config_file) { + printf("Error: The config file should not have a command (-c) option.\n"); + return ERROR; + } + command_name = strdup(optarg); + break; + + case 'a': + if (from_config_file) { + printf("Error: The config file should not have args (-a) arguments.\n"); + return ERROR; + } + argindex = optind; + break; + + case 'n': + use_ssl = FALSE; + break; + + case 'u': + if (from_config_file && timeout_return_code != -1) { + logit(LOG_WARNING, "WARNING: Command-line unknown-timeout (-u) overrides the config file option."); + break; + } + timeout_return_code = STATE_UNKNOWN; + break; + + case '2': + if (from_config_file && packet_ver != NRPE_PACKET_VERSION_3) { + logit(LOG_WARNING, "WARNING: Command-line v2-packets-only (-2) overrides the config file option."); + break; + } + packet_ver = NRPE_PACKET_VERSION_2; + force_v2_packet = 1; + break; + + case '4': + if (from_config_file && address_family != AF_UNSPEC) { + logit(LOG_WARNING, "WARNING: Command-line ipv4 (-4) or ipv6 (-6) overrides the config file option."); + break; + } + address_family = AF_INET; + break; + + case '6': + if (from_config_file && address_family != AF_UNSPEC) { + logit(LOG_WARNING, "WARNING: Command-line ipv4 (-4) or ipv6 (-6) overrides the config file option."); + break; + } + address_family = AF_INET6; + break; + + case 'd': + if (from_config_file && sslprm.allowDH != -1) { + logit(LOG_WARNING, "WARNING: Command-line use-adh (-d) overrides the config file option."); + break; + } + if (!optarg || optarg[0] < '0' || optarg[0] > '2') + return ERROR; + sslprm.allowDH = atoi(optarg); + break; + + case 'A': + if (from_config_file && sslprm.cacert_file != NULL) { + logit(LOG_WARNING, "WARNING: Command-line ca-cert-file (-A) overrides the config file option."); + break; + } + sslprm.cacert_file = strdup(optarg); + break; + + case 'C': + if (from_config_file && sslprm.cert_file != NULL) { + logit(LOG_WARNING, "WARNING: Command-line client-cert (-C) overrides the config file option."); + break; + } + sslprm.cert_file = strdup(optarg); + has_cert = 1; + break; + + case 'K': + if (from_config_file && sslprm.privatekey_file != NULL) { + logit(LOG_WARNING, "WARNING: Command-line key-file (-K) overrides the config file option."); + break; + } + sslprm.privatekey_file = strdup(optarg); + has_priv_key = 1; + break; + + case 'S': + if (from_config_file && sslprm.ssl_proto_ver != SSL_Ver_Invalid) { + logit(LOG_WARNING, "WARNING: Command-line ssl-version (-S) overrides the config file option."); + break; + } + + if (!strcmp(optarg, "TLSv1.2")) + sslprm.ssl_proto_ver = TLSv1_2; + else if (!strcmp(optarg, "TLSv1.2+")) + sslprm.ssl_proto_ver = TLSv1_2_plus; + else if (!strcmp(optarg, "TLSv1.1")) + sslprm.ssl_proto_ver = TLSv1_1; + else if (!strcmp(optarg, "TLSv1.1+")) + sslprm.ssl_proto_ver = TLSv1_1_plus; + else if (!strcmp(optarg, "TLSv1")) + sslprm.ssl_proto_ver = TLSv1; + else if (!strcmp(optarg, "TLSv1+")) + sslprm.ssl_proto_ver = TLSv1_plus; + else if (!strcmp(optarg, "SSLv3")) + sslprm.ssl_proto_ver = SSLv3; + else if (!strcmp(optarg, "SSLv3+")) + sslprm.ssl_proto_ver = SSLv3_plus; +#if OPENSSL_VERSION_NUMBER < 0x10100000 + else if (!strcmp(optarg, "SSLv2")) + sslprm.ssl_proto_ver = SSLv2; + else if (!strcmp(optarg, "SSLv2+")) + sslprm.ssl_proto_ver = SSLv2_plus; +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + else + return ERROR; + break; + + case 'L': + if (from_config_file && sslprm.cipher_list[0] != '\0') { + logit(LOG_WARNING, "WARNING: Command-line cipher-list (-L) overrides the config file option."); + break; + } + strncpy(sslprm.cipher_list, optarg, sizeof(sslprm.cipher_list) - 1); + sslprm.cipher_list[sizeof(sslprm.cipher_list) - 1] = '\0'; + break; + + case 's': + if (from_config_file && have_log_opts == TRUE) { + logit(LOG_WARNING, "WARNING: Command-line ssl-logging (-s) overrides the config file option."); + break; + } + sslprm.log_opts = strtoul(optarg, NULL, 0); + have_log_opts = TRUE; + break; + + case 'g': + if (from_config_file && log_file != NULL) { + logit(LOG_WARNING, "WARNING: Command-line log-file (-g) overrides the config file option."); + break; + } + log_file = strdup(optarg); + open_log_file(); + break; + + default: + return ERROR; + } + } + + /* determine (base) command query */ + if (!from_config_file) { + snprintf(query, sizeof(query), "%s", + (command_name == NULL) ? DEFAULT_NRPE_COMMAND : command_name); + query[sizeof(query) - 1] = '\x0'; + } + + /* get the command args */ + if (!from_config_file && argindex > 0) { + + for (c = argindex - 1; c < argc; c++) { + + i = sizeof(query) - strlen(query) - 2; + if (i <= 0) + break; + + strcat(query, "!"); + strncat(query, argv[c], i); + query[sizeof(query) - 1] = '\x0'; + } + } + if (!from_config_file && config_file != NULL) { + if ((rc = read_config_file(config_file)) != OK) + return rc; + } + + if ((has_cert && !has_priv_key) || (!has_cert && has_priv_key)) { + printf("Error: the client certificate and the private key must both be given or neither\n"); + return ERROR; + } + + if (payload_size > 0 && packet_ver != NRPE_PACKET_VERSION_2) { + printf("Error: if a fixed payload size is specified, '-2' must also be specified\n"); + return ERROR; + } + + /* make sure required args were supplied */ + if (server_name == NULL && show_help == FALSE && show_version == FALSE + && show_license == FALSE) + return ERROR; + + return OK; +} + +int read_config_file(char *fname) +{ + int rc, argc = 0; + FILE *f; + char *buf, *bufp, **argv; + char *delims = " \t\r\n"; + struct stat st; + size_t sz; + + if (stat(fname, &st)) { + logit(LOG_ERR, "Error: Could not stat config file %s", fname); + return ERROR; + } + if ((f = fopen(fname, "r")) == NULL) { + logit(LOG_ERR, "Error: Could not open config file %s", fname); + return ERROR; + } + if ((buf = (char*)calloc(1, st.st_size + 2)) == NULL) { + fclose(f); + logit(LOG_ERR, "Error: read_config_file fail to allocate memory"); + return ERROR; + } + if ((sz = fread(buf, 1, st.st_size, f)) != st.st_size) { + fclose(f); + free(buf); + logit(LOG_ERR, "Error: Failed to completely read config file %s", fname); + return ERROR; + } + if ((argv = calloc(50, sizeof(char*))) == NULL) { + fclose(f); + free(buf); + logit(LOG_ERR, "Error: read_config_file fail to allocate memory"); + return ERROR; + } + + argv[argc++] = "check_nrpe"; + + bufp = buf; + while (argc < 50) { + while (*bufp && strchr(delims, *bufp)) + ++bufp; + if (*bufp == '\0') + break; + argv[argc] = my_strsep(&bufp, delims); + if (!argv[argc++]) + break; + if (!bufp) + break; + } + + fclose(f); + + if (argc == 50) { + free(buf); + free(argv); + logit(LOG_ERR, "Error: too many parameters in config file %s", fname); + return ERROR; + } + + rc = process_arguments(argc, argv, 1); + free(buf); + free(argv); + return rc; +} + +const char *state_text (int result) +{ + switch (result) { + case STATE_OK: + return "OK"; + case STATE_WARNING: + return "WARNING"; + case STATE_CRITICAL: + return "CRITICAL"; + default: + return "UNKNOWN"; + } +} + +int translate_state (char *state_text) { + if (!strcasecmp(state_text,"OK") || !strcmp(state_text,"0")) + return STATE_OK; + if (!strcasecmp(state_text,"WARNING") || !strcmp(state_text,"1")) + return STATE_WARNING; + if (!strcasecmp(state_text,"CRITICAL") || !strcmp(state_text,"2")) + return STATE_CRITICAL; + if (!strcasecmp(state_text,"UNKNOWN") || !strcmp(state_text,"3")) + return STATE_UNKNOWN; + return ERROR; +} + +void set_timeout_state (char *state) { + if ((timeout_return_code = translate_state(state)) == ERROR) + printf("Timeout state must be a valid state name (OK, WARNING, CRITICAL, UNKNOWN) or integer (0-3).\n"); +} + +int parse_timeout_string (char *timeout_str) +{ + char *separated_str; + char *timeout_val = NULL; + char *timeout_sta = NULL; + + if (strstr(timeout_str, ":") == NULL) + timeout_val = timeout_str; + else if (strncmp(timeout_str, ":", 1) == 0) { + separated_str = strtok(timeout_str, ":"); + if (separated_str != NULL) + timeout_sta = separated_str; + } else { + separated_str = strtok(timeout_str, ":"); + timeout_val = separated_str; + separated_str = strtok(NULL, ":"); + if (separated_str != NULL) { + timeout_sta = separated_str; + } + } + + if ( timeout_sta != NULL ) + set_timeout_state(timeout_sta); + + if ((timeout_val == NULL) || (timeout_val[0] == '\0')) + return socket_timeout; + else if (atoi(timeout_val) > 0) + return atoi(timeout_val); + else { + printf("Timeout value must be a positive integer\n"); + exit (STATE_UNKNOWN); + } +} + +void usage(int result) +{ + if (result != OK) { + printf("\n"); + printf("Incorrect command line arguments supplied\n"); + printf("\n"); + } + printf("NRPE Plugin for Nagios\n"); + printf("Version: %s\n", PROGRAM_VERSION); + printf("\n"); + + if (result != OK || show_help == TRUE) { + printf("Copyright (c) 2009-2017 Nagios Enterprises\n"); + printf(" 1999-2008 Ethan Galstad (nagios@nagios.org)\n"); + printf("\n"); + printf("Last Modified: %s\n", MODIFICATION_DATE); + printf("\n"); + printf("License: GPL v2 with exemptions (-l for more info)\n"); + printf("\n"); +#ifdef HAVE_SSL + printf("SSL/TLS Available: OpenSSL 0.9.6 or higher required\n"); + printf("\n"); +#endif + printf("Usage: check_nrpe -H [-2] [-4] [-6] [-n] [-u] [-V] [-l] [-d ]\n"); + printf(" [-P ] [-S ] [-L ] [-C ]\n"); + printf(" [-K ] [-A ] [-s ] [-b ]\n"); + printf(" [-f ] [-p ] [-t :] [-g ]\n"); + printf(" [-c ] [-E] [-a ]\n"); + printf("\n"); + printf("Options:\n"); + printf(" -H, --host=HOST The address of the host running the NRPE daemon\n"); + printf(" -2, --v2-packets-only Only use version 2 packets, not version 3\n"); + printf(" -4, --ipv4 Bind to ipv4 only\n"); + printf(" -6, --ipv6 Bind to ipv6 only\n"); + printf(" -n, --no-ssl Do no use SSL\n"); + printf(" -u, --unknown-timeout Make connection problems return UNKNOWN instead of CRITICAL\n"); + printf(" -V, --version Print version info and quit\n"); + printf(" -l, --license Show license\n"); + printf(" -E, --stderr-to-stdout Redirect stderr to stdout\n"); + printf(" -d, --use-dh=DHOPT Anonymous Diffie Hellman use:\n"); + printf(" 0 Don't use Anonymous Diffie Hellman\n"); + printf(" (This will be the default in a future release.)\n"); + printf(" 1 Allow Anonymous Diffie Hellman (default)\n"); + printf(" 2 Force Anonymous Diffie Hellman\n"); + printf(" -P, --payload-size=SIZE Specify non-default payload size for NSClient++\n"); + printf(" -S, --ssl-version=VERSION The SSL/TLS version to use. Can be any one of:\n"); +#if OPENSSL_VERSION_NUMBER < 0x10100000 + printf(" SSLv2 SSL v2 only\n"); + printf(" SSLv2+ SSL v2 or above\n"); +#endif + printf(" SSLv3 SSL v3 only\n"); + printf(" SSLv3+ SSL v3 or above \n"); + printf(" TLSv1 TLS v1 only\n"); + printf(" TLSv1+ TLS v1 or above (DEFAULT)\n"); + printf(" TLSv1.1 TLS v1.1 only\n"); + printf(" TLSv1.1+ TLS v1.1 or above\n"); + printf(" TLSv1.2 TLS v1.2 only\n"); + printf(" TLSv1.2+ TLS v1.2 or above\n"); + printf(" -L, --cipher-list=LIST The list of SSL ciphers to use (currently defaults\n"); +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + printf(" to \"ALL:!MD5:@STRENGTH:@SECLEVEL=0\". THIS WILL change in a future release.)\n"); +#else + printf(" to \"ALL:!MD5:@STRENGTH\". THIS WILL change in a future release.)\n"); +#endif + printf(" -C, --client-cert=FILE The client certificate to use for PKI\n"); + printf(" -K, --key-file=FILE The private key to use with the client certificate\n"); + printf(" -A, --ca-cert-file=FILE The CA certificate to use for PKI\n"); + printf(" -s, --ssl-logging=OPTIONS SSL Logging Options\n"); + printf(" -b, --bind=IPADDR Local address to bind to\n"); + printf(" -f, --config-file=FILE Configuration file to use\n"); + printf(" -g, --log-file=FILE Log file to write to\n"); + printf(" -p, --port=PORT The port on which the daemon is running (default=%d)\n", DEFAULT_SERVER_PORT); + printf(" -c, --command=COMMAND The name of the command that the remote daemon should run\n"); + printf(" -a, --args=LIST Optional arguments that should be passed to the command,\n"); + printf(" separated by a space. If provided, this must be the last\n"); + printf(" option supplied on the command line.\n"); + printf("\n"); + printf(" NEW TIMEOUT SYNTAX\n"); + printf(" -t, --timeout=INTERVAL:STATE\n"); + printf(" INTERVAL Number of seconds before connection times out (default=%d)\n", DEFAULT_SOCKET_TIMEOUT); + printf(" STATE Check state to exit with in the event of a timeout (default=CRITICAL)\n"); + printf(" Timeout STATE must be a valid state name (case-insensitive) or integer:\n"); + printf(" (OK, WARNING, CRITICAL, UNKNOWN) or integer (0-3)\n"); + printf("\n"); + printf("Note:\n"); + printf("This plugin requires that you have the NRPE daemon running on the remote host.\n"); + printf("You must also have configured the daemon to associate a specific plugin command\n"); + printf("with the [command] option you are specifying here. Upon receipt of the\n"); + printf("[command] argument, the NRPE daemon will run the appropriate plugin command and\n"); + printf("send the plugin output and return code back to *this* plugin. This allows you\n"); + printf("to execute plugins on remote hosts and 'fake' the results to make Nagios think\n"); + printf("the plugin is being run locally.\n"); + printf("\n"); + } + + if (show_license == TRUE) + display_license(); + + exit(STATE_UNKNOWN); +} + +void setup_ssl() +{ +#ifdef HAVE_SSL + int vrfy, x; + + if (sslprm.log_opts & SSL_LogStartup) { + char *val; + + logit(LOG_INFO, "SSL Certificate File: %s", sslprm.cert_file ? sslprm.cert_file : "None"); + logit(LOG_INFO, "SSL Private Key File: %s", sslprm.privatekey_file ? sslprm.privatekey_file : "None"); + logit(LOG_INFO, "SSL CA Certificate File: %s", sslprm.cacert_file ? sslprm.cacert_file : "None"); + logit(LOG_INFO, "SSL Cipher List: %s", sslprm.cipher_list); + logit(LOG_INFO, "SSL Allow ADH: %d", sslprm.allowDH); + logit(LOG_INFO, "SSL Log Options: 0x%02x", sslprm.log_opts); + + switch (sslprm.ssl_proto_ver) { + case SSLv2: + val = "SSLv2"; + break; + case SSLv2_plus: + val = "SSLv2 And Above"; + break; + case SSLv3: + val = "SSLv3"; + break; + case SSLv3_plus: + val = "SSLv3_plus And Above"; + break; + case TLSv1: + val = "TLSv1"; + break; + case TLSv1_plus: + val = "TLSv1_plus And Above"; + break; + case TLSv1_1: + val = "TLSv1_1"; + break; + case TLSv1_1_plus: + val = "TLSv1_1_plus And Above"; + break; + case TLSv1_2: + val = "TLSv1_2"; + break; + case TLSv1_2_plus: + val = "TLSv1_2_plus And Above"; + break; + default: + val = "INVALID VALUE!"; + break; + } + logit(LOG_INFO, "SSL Version: %s", val); + } + + /* initialize SSL */ + if (use_ssl == TRUE) { + SSL_load_error_strings(); + SSL_library_init(); + ENGINE_load_builtin_engines(); + RAND_set_rand_engine(NULL); + ENGINE_register_all_complete(); + +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + + meth = TLS_method(); + +#else /* OPENSSL_VERSION_NUMBER >= 0x10100000 */ + + meth = SSLv23_client_method(); + +# ifndef OPENSSL_NO_SSL2 + if (sslprm.ssl_proto_ver == SSLv2) + meth = SSLv2_client_method(); +# endif +# ifndef OPENSSL_NO_SSL3 + if (sslprm.ssl_proto_ver == SSLv3) + meth = SSLv3_client_method(); +# endif + if (sslprm.ssl_proto_ver == TLSv1) + meth = TLSv1_client_method(); +# ifdef SSL_TXT_TLSV1_1 + if (sslprm.ssl_proto_ver == TLSv1_1) + meth = TLSv1_1_client_method(); +# ifdef SSL_TXT_TLSV1_2 + if (sslprm.ssl_proto_ver == TLSv1_2) + meth = TLSv1_2_client_method(); +# endif /* ifdef SSL_TXT_TLSV1_2 */ +# endif /* ifdef SSL_TXT_TLSV1_1 */ + +#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000 */ + + if ((ctx = SSL_CTX_new(meth)) == NULL) { + printf("CHECK_NRPE: Error - could not create SSL context.\n"); + exit(STATE_CRITICAL); + } + +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + + SSL_CTX_set_max_proto_version(ctx, 0); + + switch(sslprm.ssl_proto_ver) { + + case TLSv1_2: + SSL_CTX_set_max_proto_version(ctx, TLS1_2_VERSION); + case TLSv1_2_plus: + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + break; + + case TLSv1_1: + SSL_CTX_set_max_proto_version(ctx, TLS1_1_VERSION); + case TLSv1_1_plus: + SSL_CTX_set_min_proto_version(ctx, TLS1_1_VERSION); + break; + + case TLSv1: + SSL_CTX_set_max_proto_version(ctx, TLS1_VERSION); + case TLSv1_plus: + SSL_CTX_set_min_proto_version(ctx, TLS1_VERSION); + break; + + case SSLv3: + SSL_CTX_set_max_proto_version(ctx, SSL3_VERSION); + case SSLv3_plus: + SSL_CTX_set_min_proto_version(ctx, SSL3_VERSION); + break; + } + +#else /* OPENSSL_VERSION_NUMBER >= 0x10100000 */ + + switch(sslprm.ssl_proto_ver) { + case SSLv2: + case SSLv2_plus: + break; + case TLSv1_2: + case TLSv1_2_plus: +#ifdef SSL_OP_NO_TLSv1_1 + ssl_opts |= SSL_OP_NO_TLSv1_1; +#endif + case TLSv1_1: + case TLSv1_1_plus: + ssl_opts |= SSL_OP_NO_TLSv1; + case TLSv1: + case TLSv1_plus: + ssl_opts |= SSL_OP_NO_SSLv3; + case SSLv3: + case SSLv3_plus: + ssl_opts |= SSL_OP_NO_SSLv2; + break; + } + +#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000 */ + + SSL_CTX_set_options(ctx, ssl_opts); + + if (sslprm.cert_file != NULL && sslprm.privatekey_file != NULL) { + if (!SSL_CTX_use_certificate_file(ctx, sslprm.cert_file, SSL_FILETYPE_PEM)) { + printf("Error: could not use certificate file '%s'.\n", sslprm.cert_file); + while ((x = ERR_get_error_line_data(NULL, NULL, NULL, NULL)) != 0) { + printf("Error: could not use certificate file '%s': %s\n", sslprm.cert_file, ERR_reason_error_string(x)); + } + SSL_CTX_free(ctx); + exit(STATE_CRITICAL); + } + if (!SSL_CTX_use_PrivateKey_file(ctx, sslprm.privatekey_file, SSL_FILETYPE_PEM)) { + SSL_CTX_free(ctx); + printf("Error: could not use private key file '%s'.\n", sslprm.privatekey_file); + while ((x = ERR_get_error_line_data(NULL, NULL, NULL, NULL)) != 0) { + printf("Error: could not use private key file '%s': %s\n", sslprm.privatekey_file, ERR_reason_error_string(x)); + } + SSL_CTX_free(ctx); + exit(STATE_CRITICAL); + } + } + + if (sslprm.cacert_file != NULL) { + vrfy = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + SSL_CTX_set_verify(ctx, vrfy, verify_callback); + if (!SSL_CTX_load_verify_locations(ctx, sslprm.cacert_file, NULL)) { + printf("Error: could not use CA certificate '%s'.\n", sslprm.cacert_file); + while ((x = ERR_get_error_line_data(NULL, NULL, NULL, NULL)) != 0) { + printf("Error: could not use CA certificate '%s': %s\n", sslprm.privatekey_file, ERR_reason_error_string(x)); + } + SSL_CTX_free(ctx); + exit(STATE_CRITICAL); + } + } + + if (!sslprm.allowDH) { + if (strlen(sslprm.cipher_list) < sizeof(sslprm.cipher_list) - 6) { + strcat(sslprm.cipher_list, ":!ADH"); + if (sslprm.log_opts & SSL_LogStartup) + logit(LOG_INFO, "New SSL Cipher List: %s", sslprm.cipher_list); + } + } else { + /* use anonymous DH ciphers */ + if (sslprm.allowDH == 2) { +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + strncpy(sslprm.cipher_list, "ADH@SECLEVEL=0", MAX_FILENAME_LENGTH - 1); +#else + strncpy(sslprm.cipher_list, "ADH", MAX_FILENAME_LENGTH - 1); +#endif + } + } + + if (SSL_CTX_set_cipher_list(ctx, sslprm.cipher_list) == 0) { + printf("Error: Could not set SSL/TLS cipher list: %s\n", sslprm.cipher_list); + while ((x = ERR_get_error_line_data(NULL, NULL, NULL, NULL)) != 0) { + printf("Could not set SSL/TLS cipher list '%s': %s\n", sslprm.cipher_list, ERR_reason_error_string(x)); + } + SSL_CTX_free(ctx); + exit(STATE_CRITICAL); + } + } +#endif +} + +void set_sig_handlers() +{ +#ifdef HAVE_SIGACTION + struct sigaction sig_action; +#endif + +#ifdef HAVE_SIGACTION + sig_action.sa_sigaction = NULL; + sig_action.sa_handler = alarm_handler; + sigfillset(&sig_action.sa_mask); + sig_action.sa_flags = SA_NODEFER | SA_RESTART; + sigaction(SIGALRM, &sig_action, NULL); +#else + signal(SIGALRM, alarm_handler); +#endif /* HAVE_SIGACTION */ + + /* set socket timeout */ + alarm(socket_timeout); +} + +int connect_to_remote() +{ + struct sockaddr addr; + struct in_addr *inaddr; + socklen_t addrlen; + int result, rc, ssl_err, ern, x, nerrs = 0; + + /* try to connect to the host at the given port number */ + if ((sd = my_connect(server_name, &hostaddr, server_port, address_family, bind_address, stderr_to_stdout)) < 0) + exit(timeout_return_code); + + result = STATE_OK; + addrlen = sizeof(addr); + rc = getpeername(sd, (struct sockaddr *)&addr, &addrlen); + if (addr.sa_family == AF_INET) { + struct sockaddr_in *addrin = (struct sockaddr_in *)&addr; + inaddr = &addrin->sin_addr; + } else { + struct sockaddr_in6 *addrin = (struct sockaddr_in6 *)&addr; + inaddr = (struct in_addr *)&addrin->sin6_addr; + } + if (inet_ntop(addr.sa_family, inaddr, rem_host, sizeof(rem_host)) == NULL) + strncpy(rem_host, "Unknown", sizeof(rem_host)); + rem_host[MAX_HOST_ADDRESS_LENGTH - 1] = '\0'; + if ((sslprm.log_opts & SSL_LogIpAddr) != 0) + logit(LOG_DEBUG, "Connected to %s", rem_host); + +#ifdef HAVE_SSL + if (use_ssl == FALSE) + return result; + + /* do SSL handshake */ + if ((ssl = SSL_new(ctx)) == NULL) { + printf("CHECK_NRPE: Error - Could not create SSL connection structure.\n"); + return STATE_CRITICAL; + } + + SSL_set_fd(ssl, sd); + if ((rc = SSL_connect(ssl)) != 1) { + ern = errno; + ssl_err = SSL_get_error(ssl, rc); + + if (sslprm.log_opts & (SSL_LogCertDetails | SSL_LogIfClientCert)) { + rc = 0; + while ((x = ERR_get_error_line_data(NULL, NULL, NULL, NULL)) != 0) { + logit(LOG_ERR, "Error: (ERR_get_error_line_data = %d), Could not complete SSL handshake with %s: %s", x, rem_host, ERR_reason_error_string(x)); + ++nerrs; + } + if (nerrs == 0) { + logit(LOG_ERR, "Error: (nerrs = 0) Could not complete SSL handshake with %s: rc=%d SSL-error=%d", rem_host, rc, ssl_err); + } + } else { + while ((x = ERR_get_error_line_data(NULL, NULL, NULL, NULL)) != 0) { + logit(LOG_ERR, "Error: (!log_opts) Could not complete SSL handshake with %s: %s", rem_host, ERR_reason_error_string(x)); + ++nerrs; + } + if (nerrs == 0) { + logit(LOG_ERR, "Error: (nerrs = 0)(!log_opts) Could not complete SSL handshake with %s: rc=%d SSL-error=%d", rem_host, rc, ssl_err); + } + } + + if (ssl_err == 5) { + /* Often, errno will be zero, so print a generic message here */ + if (ern == 0) + printf("CHECK_NRPE: Error - Could not connect to %s. Check system logs on %s\n", rem_host, rem_host); + else + printf("CHECK_NRPE: Error - Could not connect to %s: %s\n", rem_host, strerror(ern)); + } else { + printf("CHECK_NRPE: (ssl_err != 5) Error - Could not complete SSL handshake with %s: %d\n", rem_host, ssl_err); + } + +# ifdef DEBUG + printf("SSL_connect=%d\n", rc); + /* + rc = SSL_get_error(ssl, rc); + printf("SSL_get_error=%d\n", rc); + printf("ERR_get_error=%lu\n", ERR_get_error()); + printf("%s\n",ERR_error_string(rc, NULL)); + */ + ERR_print_errors_fp(stdout); +# endif + result = STATE_CRITICAL; + + } else { + + if (sslprm.log_opts & SSL_LogVersion) + logit(LOG_NOTICE, "Remote %s - SSL Version: %s", rem_host, SSL_get_version(ssl)); + + if (sslprm.log_opts & SSL_LogCipher) { +# if (defined(__sun) && defined(SOLARIS_10)) || defined(_AIX) || defined(__hpux) + SSL_CIPHER *c = SSL_get_current_cipher(ssl); +# else + const SSL_CIPHER *c = SSL_get_current_cipher(ssl); +# endif + logit(LOG_NOTICE, "Remote %s - %s, Cipher is %s", rem_host, + SSL_CIPHER_get_version(c), SSL_CIPHER_get_name(c)); + } + + if ((sslprm.log_opts & SSL_LogIfClientCert) || (sslprm.log_opts & SSL_LogCertDetails)) { + char peer_cn[256], buffer[2048]; + X509 *peer = SSL_get_peer_certificate(ssl); + + if (peer) { + if (sslprm.log_opts & SSL_LogIfClientCert) + logit(LOG_NOTICE, "SSL %s has %s certificate", rem_host, SSL_get_verify_result(ssl) == X509_V_OK ? "a valid" : "an invalid"); + + if (sslprm.log_opts & SSL_LogCertDetails) { + X509_NAME_oneline(X509_get_subject_name(peer), buffer, sizeof(buffer)); + logit(LOG_NOTICE, "SSL %s Cert Name: %s", rem_host, buffer); + X509_NAME_oneline(X509_get_issuer_name(peer), buffer, sizeof(buffer)); + logit(LOG_NOTICE, "SSL %s Cert Issuer: %s", rem_host, buffer); + } + + } else + logit(LOG_NOTICE, "SSL Did not get certificate from %s", rem_host); + } + } + + /* bail if we had errors */ + if (result != STATE_OK) { + SSL_CTX_free(ctx); + close(sd); + exit(result); + } +#endif + + return result; +} + +int send_request() +{ + v2_packet *v2_send_packet = NULL; + v3_packet *v3_send_packet = NULL; + u_int32_t calculated_crc32; + int rc, bytes_to_send, pkt_size; + char *send_pkt; + + if (packet_ver == NRPE_PACKET_VERSION_2) { + pkt_size = sizeof(v2_packet); + if (payload_size > 0) + pkt_size = sizeof(v2_packet) - MAX_PACKETBUFFER_LENGTH + payload_size; + v2_send_packet = (v2_packet*)calloc(1, pkt_size); + send_pkt = (char *)v2_send_packet; + + /* fill the packet with semi-random data */ + randomize_buffer((char *)v2_send_packet, pkt_size); + + /* initialize response packet data */ + v2_send_packet->packet_version = htons(packet_ver); + v2_send_packet->packet_type = htons(QUERY_PACKET); + if (payload_size > 0) { + strncpy(&v2_send_packet->buffer[0], query, payload_size); + v2_send_packet->buffer[payload_size - 1] = '\x0'; + } else { + strncpy(&v2_send_packet->buffer[0], query, MAX_PACKETBUFFER_LENGTH); + v2_send_packet->buffer[MAX_PACKETBUFFER_LENGTH - 1] = '\x0'; + } + + /* calculate the crc 32 value of the packet */ + v2_send_packet->crc32_value = 0; + calculated_crc32 = calculate_crc32(send_pkt, pkt_size); + v2_send_packet->crc32_value = htonl(calculated_crc32); + + } else { + + pkt_size = (sizeof(v3_packet) - 1) + strlen(query) + 1; + if (pkt_size < sizeof(v2_packet)) + pkt_size = sizeof(v2_packet); + + v3_send_packet = calloc(1, pkt_size); + send_pkt = (char *)v3_send_packet; + /* initialize response packet data */ + v3_send_packet->packet_version = htons(packet_ver); + v3_send_packet->packet_type = htons(QUERY_PACKET); + v3_send_packet->alignment = 0; + v3_send_packet->buffer_length = htonl(pkt_size - sizeof(v3_packet) + 1); + strcpy(&v3_send_packet->buffer[0], query); + + /* calculate the crc 32 value of the packet */ + v3_send_packet->crc32_value = 0; + calculated_crc32 = calculate_crc32((char *)v3_send_packet, pkt_size); + v3_send_packet->crc32_value = htonl(calculated_crc32); + } + + /* send the request to the remote */ + bytes_to_send = pkt_size; + + if (use_ssl == FALSE) + rc = sendall(sd, (char *)send_pkt, &bytes_to_send); +#ifdef HAVE_SSL + else { + rc = SSL_write(ssl, send_pkt, bytes_to_send); + if (rc < 0) + rc = -1; + } +#endif + + if (v3_send_packet) + free(v3_send_packet); + if (v2_send_packet) + free(v2_send_packet); + + if (rc == -1) { + printf("CHECK_NRPE: Error sending query to host.\n"); + close(sd); + return STATE_UNKNOWN; + } + + return STATE_OK; +} + +int read_response() +{ + v2_packet *v2_receive_packet = NULL; + v3_packet *v3_receive_packet = NULL; + u_int32_t packet_crc32; + u_int32_t calculated_crc32; + int32_t pkt_size; + int rc, result; + + alarm(0); + set_sig_handlers(); + +#ifdef HAVE_SSL + rc = read_packet(sd, ssl, &v2_receive_packet, &v3_receive_packet); +#else + rc = read_packet(sd, NULL, &v2_receive_packet, &v3_receive_packet); +#endif + + alarm(0); + + /* close the connection */ +#ifdef HAVE_SSL + if (use_ssl == TRUE) { + SSL_shutdown(ssl); + SSL_free(ssl); + SSL_CTX_free(ctx); + } +#endif + graceful_close(sd, 1000); + + /* recv() error */ + if (rc < 0) { + if (packet_ver == NRPE_PACKET_VERSION_3) { + if (v3_receive_packet) + free(v3_receive_packet); + return -1; + } + if (v2_receive_packet) + free(v2_receive_packet); + return STATE_UNKNOWN; + + } else if (rc == 0) { + + /* server disconnected */ + printf("CHECK_NRPE: Received 0 bytes from daemon. Check the remote server logs for error messages.\n"); + if (packet_ver == NRPE_PACKET_VERSION_3) { + if (v3_receive_packet) { + free(v3_receive_packet); + } + } else if (v2_receive_packet) { + free(v2_receive_packet); + } + return STATE_UNKNOWN; + } + + /* check the crc 32 value */ + if (packet_ver == NRPE_PACKET_VERSION_3) { + pkt_size = (sizeof(v3_packet) - 1) + ntohl(v3_receive_packet->buffer_length); + packet_crc32 = ntohl(v3_receive_packet->crc32_value); + v3_receive_packet->crc32_value = 0L; + v3_receive_packet->alignment = 0; + calculated_crc32 = calculate_crc32((char *)v3_receive_packet, pkt_size); + } else { + pkt_size = sizeof(v2_packet); + if (payload_size > 0) { + pkt_size = sizeof(v2_packet) - MAX_PACKETBUFFER_LENGTH + payload_size; + } + packet_crc32 = ntohl(v2_receive_packet->crc32_value); + v2_receive_packet->crc32_value = 0L; + calculated_crc32 = calculate_crc32((char *)v2_receive_packet, pkt_size); + } + + if (packet_crc32 != calculated_crc32) { + printf("CHECK_NRPE: Response packet had invalid CRC32.\n"); + close(sd); + if (packet_ver == NRPE_PACKET_VERSION_3) { + if (v3_receive_packet) { + free(v3_receive_packet); + } + } else if (v2_receive_packet) { + free(v2_receive_packet); + } + return STATE_UNKNOWN; + } + + /* get the return code from the remote plugin */ + /* and print the output returned by the daemon */ + if (packet_ver == NRPE_PACKET_VERSION_3) { + result = ntohs(v3_receive_packet->result_code); + if (v3_receive_packet->buffer_length == 0) { + printf("CHECK_NRPE: No output returned from daemon.\n"); + } else { + printf("%s\n", v3_receive_packet->buffer); + } + } else { + result = ntohs(v2_receive_packet->result_code); + if (payload_size > 0) { + v2_receive_packet->buffer[payload_size - 1] = '\x0'; + } else { + v2_receive_packet->buffer[MAX_PACKETBUFFER_LENGTH - 1] = '\x0'; + } + if (!strcmp(v2_receive_packet->buffer, "")) { + printf("CHECK_NRPE: No output returned from daemon.\n"); + } else if (strstr(v2_receive_packet->buffer, "Invalid packet version.3") != NULL) { + /* NSClient++ doesn't recognize it */ + return -1; + } else { + printf("%s\n", v2_receive_packet->buffer); + } + } + + if (packet_ver == NRPE_PACKET_VERSION_3) { + if (v3_receive_packet) { + free(v3_receive_packet); + } + } else if (v2_receive_packet) { + free(v2_receive_packet); + } + + return result; +} + +int read_packet(int sock, void *ssl_ptr, v2_packet ** v2_pkt, v3_packet ** v3_pkt) +{ + v2_packet packet; + int32_t pkt_size, common_size, tot_bytes, bytes_to_recv, buffer_size, bytes_read = 0; + int rc; + char *buff_ptr; + + /* Read only the part that's common between versions 2 & 3 */ + common_size = tot_bytes = bytes_to_recv = (char *)packet.buffer - (char *)&packet; + + if (use_ssl == FALSE) { + rc = recvall(sock, (char *)&packet, &tot_bytes, socket_timeout); + + if (rc <= 0 || rc != bytes_to_recv) { + if (rc < bytes_to_recv) { + if (packet_ver != NRPE_PACKET_VERSION_3) + printf("CHECK_NRPE: Receive header underflow - only %d bytes received (%ld expected).\n", rc, sizeof(bytes_to_recv)); + } + return -1; + } + + packet_ver = ntohs(packet.packet_version); + if (packet_ver != NRPE_PACKET_VERSION_2 && packet_ver != NRPE_PACKET_VERSION_3) { + printf("CHECK_NRPE: Invalid packet version received from server.\n"); + return -1; + } + + if (ntohs(packet.packet_type) != RESPONSE_PACKET) { + printf("CHECK_NRPE: Invalid packet type received from server.\n"); + return -1; + } + + if (packet_ver == NRPE_PACKET_VERSION_2) { + pkt_size = sizeof(v2_packet); + if (payload_size > 0) { + pkt_size = common_size + payload_size; + buffer_size = payload_size; + } else { + buffer_size = pkt_size - common_size; + } + if ((*v2_pkt = calloc(1, pkt_size)) == NULL) { + logit(LOG_ERR, "Error: Could not allocate memory for packet"); + return -1; + } + memcpy(*v2_pkt, &packet, common_size); + buff_ptr = (*v2_pkt)->buffer; + memset(buff_ptr, 0, buffer_size); + } else { + pkt_size = sizeof(v3_packet) - 1; + + /* Read the alignment filler */ + bytes_to_recv = sizeof(int16_t); + rc = recvall(sock, (char *)&buffer_size, &bytes_to_recv, socket_timeout); + if (rc <= 0 || bytes_to_recv != sizeof(int16_t)) + return -1; + tot_bytes += rc; + + /* Read the buffer size */ + bytes_to_recv = sizeof(buffer_size); + rc = recvall(sock, (char *)&buffer_size, &bytes_to_recv, socket_timeout); + if (rc <= 0 || bytes_to_recv != sizeof(buffer_size)) + return -1; + tot_bytes += rc; + + buffer_size = ntohl(buffer_size); + pkt_size += buffer_size; + if ((*v3_pkt = calloc(1, pkt_size)) == NULL) { + logit(LOG_ERR, "Error: Could not allocate memory for packet"); + return -1; + } + + memcpy(*v3_pkt, &packet, common_size); + (*v3_pkt)->buffer_length = htonl(buffer_size); + buff_ptr = (*v3_pkt)->buffer; + } + + bytes_to_recv = buffer_size; + rc = recvall(sock, buff_ptr, &bytes_to_recv, socket_timeout); + + if (rc <= 0 || rc != buffer_size) { + if (packet_ver == NRPE_PACKET_VERSION_3) { + free(*v3_pkt); + *v3_pkt = NULL; + } else { + free(*v2_pkt); + *v2_pkt = NULL; + } + if (rc < buffer_size) + printf("CHECK_NRPE: Receive underflow - only %d bytes received (%ld expected).\n", rc, sizeof(buffer_size)); + return -1; + } else + tot_bytes += rc; + } +#ifdef HAVE_SSL + else { + SSL *ssl = (SSL *) ssl_ptr; + + while (((rc = SSL_read(ssl, &packet, bytes_to_recv)) <= 0) + && (SSL_get_error(ssl, rc) == SSL_ERROR_WANT_READ)) { + } + + if (rc <= 0 || rc != bytes_to_recv) { + if (rc < bytes_to_recv) { + if (packet_ver != NRPE_PACKET_VERSION_3) + printf("CHECK_NRPE: Receive header underflow - only %d bytes received (%ld expected).\n", rc, sizeof(bytes_to_recv)); + } + return -1; + } + + packet_ver = ntohs(packet.packet_version); + if (packet_ver != NRPE_PACKET_VERSION_2 && packet_ver != NRPE_PACKET_VERSION_3) { + printf("CHECK_NRPE: Invalid packet version received from server.\n"); + return -1; + } + + if (ntohs(packet.packet_type) != RESPONSE_PACKET) { + printf("CHECK_NRPE: Invalid packet type received from server.\n"); + return -1; + } + + if (packet_ver == NRPE_PACKET_VERSION_2) { + pkt_size = sizeof(v2_packet); + if (payload_size > 0) { + pkt_size = common_size + payload_size; + buffer_size = payload_size; + } else + buffer_size = pkt_size - common_size; + if ((*v2_pkt = calloc(1, pkt_size)) == NULL) { + logit(LOG_ERR, "Error: Could not allocate memory for packet"); + return -1; + } + memcpy(*v2_pkt, &packet, common_size); + buff_ptr = (*v2_pkt)->buffer; + memset(buff_ptr, 0, buffer_size); + } else { + pkt_size = sizeof(v3_packet) - 1; + + /* Read the alignment filler */ + bytes_to_recv = sizeof(int16_t); + while (((rc = SSL_read(ssl, &buffer_size, bytes_to_recv)) <= 0) + && (SSL_get_error(ssl, rc) == SSL_ERROR_WANT_READ)) { + } + + if (rc <= 0 || bytes_to_recv != sizeof(int16_t)) + return -1; + tot_bytes += rc; + + /* Read the buffer size */ + bytes_to_recv = sizeof(buffer_size); + while (((rc = SSL_read(ssl, &buffer_size, bytes_to_recv)) <= 0) + && (SSL_get_error(ssl, rc) == SSL_ERROR_WANT_READ)) { + } + + if (rc <= 0 || bytes_to_recv != sizeof(buffer_size)) + return -1; + tot_bytes += rc; + + buffer_size = ntohl(buffer_size); + pkt_size += buffer_size; + if ((*v3_pkt = calloc(1, pkt_size)) == NULL) { + logit(LOG_ERR, "Error: Could not allocate memory for packet"); + return -1; + } + + memcpy(*v3_pkt, &packet, common_size); + (*v3_pkt)->buffer_length = htonl(buffer_size); + buff_ptr = (*v3_pkt)->buffer; + } + + bytes_to_recv = buffer_size; + for (;;) { + while (((rc = SSL_read(ssl, &buff_ptr[bytes_read], bytes_to_recv)) <= 0) + && (SSL_get_error(ssl, rc) == SSL_ERROR_WANT_READ)) { + } + + if (rc <= 0) + break; + bytes_read += rc; + bytes_to_recv -= rc; + } + + buff_ptr[bytes_read] = 0; + + if (rc < 0 || bytes_read != buffer_size) { + if (packet_ver == NRPE_PACKET_VERSION_3) { + free(*v3_pkt); + *v3_pkt = NULL; + } else { + free(*v2_pkt); + *v2_pkt = NULL; + } + if (bytes_read != buffer_size) { + if (packet_ver == NRPE_PACKET_VERSION_3) { + printf("CHECK_NRPE: Receive buffer size - %ld bytes received (%ld expected).\n", (long)bytes_read, sizeof(buffer_size)); + } else { + printf("CHECK_NRPE: Receive underflow - only %ld bytes received (%ld expected).\n", (long)bytes_read, sizeof(buffer_size)); + } + } + return -1; + } else + tot_bytes += rc; + } +#endif + + return tot_bytes; +} + +#ifdef HAVE_SSL +int verify_callback(int preverify_ok, X509_STORE_CTX * ctx) +{ + char name[256], issuer[256]; + X509 *err_cert; + int err; + SSL *ssl; + + if (preverify_ok || ((sslprm.log_opts & SSL_LogCertDetails) == 0)) + return preverify_ok; + + err_cert = X509_STORE_CTX_get_current_cert(ctx); + err = X509_STORE_CTX_get_error(ctx); + + /* Get the pointer to the SSL of the current connection */ + ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + + X509_NAME_oneline(X509_get_subject_name(err_cert), name, 256); + X509_NAME_oneline(X509_get_issuer_name(err_cert), issuer, 256); + + if (!preverify_ok && sslprm.client_certs >= Ask_For_Cert + && (sslprm.log_opts & SSL_LogCertDetails)) { + + logit(LOG_ERR, "SSL Client has an invalid certificate: %s (issuer=%s) err=%d:%s", name, issuer, err, X509_verify_cert_error_string(err)); + } + + return preverify_ok; +} +#endif + +void alarm_handler(int sig) +{ + const char msg1[] = "CHECK_NRPE STATE "; + const char msg2[] = ": Socket timeout after "; + const char msg3[] = " seconds.\n"; + const char *text = state_text(timeout_return_code); + size_t lth1 = 0, lth2 = 0; + + for (lth1 = 0; lth1 < 10; ++lth1) + if (text[lth1] == 0) + break; + for (lth2 = 0; lth2 < 10; ++lth2) + if (timeout_txt[lth2] == 0) + break; + + + if ((write(STDOUT_FILENO, msg1, sizeof(msg1) - 1) == -1) + || (write(STDOUT_FILENO, text, lth1) == -1) + || (write(STDOUT_FILENO, msg2, sizeof(msg2) - 1) == -1) + || (write(STDOUT_FILENO, timeout_txt, lth2) == -1) + || (write(STDOUT_FILENO, msg3, sizeof(msg3) - 1) == -1)) { + + logit(LOG_ERR, "ERROR: alarm_handler() write(): %s", strerror(errno)); + } + + exit(timeout_return_code); +} + +/* submitted by Mark Plaksin 08/31/2006 */ +int graceful_close(int sd, int timeout) +{ + fd_set in; + struct timeval tv; + char buf[1000]; + + /* send FIN packet */ + shutdown(sd, SHUT_WR); + + for (;;) { + FD_ZERO(&in); + FD_SET(sd, &in); + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + /* timeout or error */ + if (1 != select(sd + 1, &in, NULL, NULL, &tv)) + break; + + /* no more data (FIN or RST) */ + if (0 >= recv(sd, buf, sizeof(buf), 0)) + break; + } + +#ifdef HAVE_CLOSESOCKET + closesocket(sd); +#else + close(sd); +#endif + + return OK; +} diff --git a/src/nrpe.c b/src/nrpe.c new file mode 100644 index 0000000..8e92764 --- /dev/null +++ b/src/nrpe.c @@ -0,0 +1,2907 @@ +/**************************************************************************** + * + * nrpe.c - Nagios Remote Plugin Executor + * + * License: GPLv2 + * Copyright (c) 2009-2017 Nagios Enterprises + * 1999-2008 Ethan Galstad (nagios@nagios.org) + * + * Command line: nrpe -c [--inetd | --daemon] + * + * Description: + * + * This program is designed to run as a background process and + * handle incoming requests (from the host running Nagios) for + * plugin execution. It is useful for running "local" plugins + * such as check_users, check_load, check_disk, etc. without + * having to use rsh or ssh. + * + * License Notice: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + ****************************************************************************/ + +#include "config.h" +#include "common.h" +#include "nrpe.h" +#include "utils.h" +#include "acl.h" + +#ifdef HAVE_SSL +# ifdef USE_SSL_DH +# include "../include/dh.h" +# endif +#endif +#ifndef HAVE_ASPRINTF +extern int asprintf(char **ptr, const char *format, ...); +#endif + +#ifdef HAVE_LIBWRAP +int allow_severity = LOG_INFO; +int deny_severity = LOG_WARNING; +# ifndef HAVE_RFC931_TIMEOUT +int rfc931_timeout=15; +# endif +#endif + +#ifdef HAVE_SSL +# if (defined(__sun) && defined(SOLARIS_10)) || defined(_AIX) || defined(__hpux) +SSL_METHOD *meth; +# else +const SSL_METHOD *meth; +# endif +SSL_CTX *ctx; +int use_ssl = TRUE; +#else +int use_ssl = FALSE; +#endif + +#define DEFAULT_COMMAND_TIMEOUT 60 /* default timeout for execution of plugins */ +#define MAXFD 64 +#define NASTY_METACHARS "|`&><'\\[]{};\r\n" +#define MAX_LISTEN_SOCKS 16 +#define DEFAULT_LISTEN_QUEUE_SIZE 5 +#define DEFAULT_SSL_SHUTDOWN_TIMEOUT 15 + +#define how_many(x,y) (((x)+((y)-1))/(y)) + +extern int errno; +struct addrinfo *listen_addrs = NULL; +int listen_socks[MAX_LISTEN_SOCKS]; +char remote_host[MAX_HOST_ADDRESS_LENGTH]; +char *macro_argv[MAX_COMMAND_ARGUMENTS]; +char config_file[MAX_INPUT_BUFFER] = "nrpe.cfg"; +char server_address[NI_MAXHOST] = ""; +char *command_name = NULL; +int log_facility = LOG_DAEMON; +int server_port = DEFAULT_SERVER_PORT; +int num_listen_socks = 0; +int address_family = AF_UNSPEC; +int socket_timeout = DEFAULT_SOCKET_TIMEOUT; +int command_timeout = DEFAULT_COMMAND_TIMEOUT; +int connection_timeout = DEFAULT_CONNECTION_TIMEOUT; +int ssl_shutdown_timeout = DEFAULT_SSL_SHUTDOWN_TIMEOUT; +char *command_prefix = NULL; +int packet_ver = 0; +command *command_list = NULL; +char *nrpe_user = NULL; +char *nrpe_group = NULL; +char *allowed_hosts = NULL; +char *keep_env_vars = NULL; +char *pid_file = NULL; +int wrote_pid_file = FALSE; +int allow_arguments = FALSE; +int allow_bash_cmd_subst = FALSE; +int allow_weak_random_seed = FALSE; +int sigrestart = FALSE; +int sigshutdown = FALSE; +int show_help = FALSE; +int show_license = FALSE; +int show_version = FALSE; +int use_inetd = TRUE; +int commands_running = 0; +int max_commands = 0; +int debug = FALSE; +int use_src = FALSE; /* Define parameter for SRC option */ +int no_forking = FALSE; +int listen_queue_size = DEFAULT_LISTEN_QUEUE_SIZE; +char *nasty_metachars = NULL; +extern char *log_file; + +/* SSL/TLS parameters */ +typedef enum _SSL_VER { + SSLv2 = 1, SSLv2_plus, SSLv3, SSLv3_plus, TLSv1, + TLSv1_plus, TLSv1_1, TLSv1_1_plus, TLSv1_2, TLSv1_2_plus +} SslVer; + +typedef enum _CLNT_CERTS { + ClntCerts_Unknown = 0, Ask_For_Cert = 1, Require_Cert = 2 +} ClntCerts; + +typedef enum _SSL_LOGGING { + SSL_NoLogging = 0, SSL_LogStartup = 1, SSL_LogIpAddr = 2, + SSL_LogVersion = 4, SSL_LogCipher = 8, SSL_LogIfClientCert = 16, + SSL_LogCertDetails = 32 +} SslLogging; + +struct _SSL_PARMS { + char *cert_file; + char *cacert_file; + char *privatekey_file; + char cipher_list[MAX_FILENAME_LENGTH]; + SslVer ssl_proto_ver; + int allowDH; + ClntCerts client_certs; + SslLogging log_opts; +} sslprm = { +#if OPENSSL_VERSION_NUMBER >= 0x10100000 +NULL, NULL, NULL, "ALL:!MD5:@STRENGTH:@SECLEVEL=0", TLSv1_plus, TRUE, 0, SSL_NoLogging}; +#else +NULL, NULL, NULL, "ALL:!MD5:@STRENGTH", TLSv1_plus, TRUE, 0, SSL_NoLogging}; +#endif + + +#ifdef HAVE_SSL +static int verify_callback(int ok, X509_STORE_CTX * ctx); +static void my_disconnect_sighandler(int sig); +static void complete_SSL_shutdown(SSL *); +#endif + +int main(int argc, char **argv) +{ + int result = OK; + int x; + uint32_t y; + char buffer[MAX_INPUT_BUFFER]; + + init(); + + /* process command-line args */ + result = process_arguments(argc, argv); + if (result != OK || show_help == TRUE || show_license == TRUE || show_version == TRUE) + usage(result); + + /* make sure the config file uses an absolute path */ + if (config_file[0] != '/') { + + /* save the name of the config file */ + strncpy(buffer, config_file, sizeof(buffer)); + buffer[sizeof(buffer) - 1] = '\x0'; + + /* get absolute path of current working directory */ + strcpy(config_file, ""); + if (getcwd(config_file, sizeof(config_file)) == NULL) { + printf("ERROR: getcwd(): %s, bailing out...\n", strerror(errno)); + exit(STATE_CRITICAL); + } + + /* append a forward slash */ + strncat(config_file, "/", sizeof(config_file) - 2); + config_file[sizeof(config_file) - 1] = '\x0'; + + /* append the config file to the path */ + strncat(config_file, buffer, sizeof(config_file) - strlen(config_file) - 1); + config_file[sizeof(config_file) - 1] = '\x0'; + } + + /* read the config file */ + result = read_config_file(config_file); + /* exit if there are errors... */ + if (result == ERROR) { + logit(LOG_ERR, "Config file '%s' contained errors, aborting...", config_file); + return STATE_CRITICAL; + } + + if (!nasty_metachars) + nasty_metachars = strdup(NASTY_METACHARS); + + /* initialize macros */ + for (x = 0; x < MAX_COMMAND_ARGUMENTS; x++) + macro_argv[x] = NULL; + + init_ssl(); + + /* if we're running under inetd... */ + if (use_inetd == TRUE) + run_inetd(); + + else if (use_src == TRUE || no_forking == TRUE) + run_src(); + + else + run_daemon(); + +#ifdef HAVE_SSL + if (use_ssl == TRUE) + SSL_CTX_free(ctx); +#endif + + /* We are now running in daemon mode, or the connection handed over by inetd has + been completed, so the parent process exits */ + return STATE_OK; +} + +int init(void) +{ + char *env_string = NULL; + int result = OK; + + /* set some environment variables */ + asprintf(&env_string, "NRPE_MULTILINESUPPORT=1"); + putenv(env_string); + asprintf(&env_string, "NRPE_PROGRAMVERSION=%s", PROGRAM_VERSION); + putenv(env_string); + + /* open a connection to the syslog facility */ + /* facility name may be overridden later */ + get_log_facility(NRPE_LOG_FACILITY); + openlog("nrpe", LOG_PID, log_facility); + + /* generate the CRC 32 table */ + generate_crc32_table(); + + return result; +} + +void init_ssl(void) +{ +#ifdef HAVE_SSL + DH *dh; + char seedfile[FILENAME_MAX]; + char errstr[120] = { "" }; + int i, c, x, vrfy; + unsigned long ssl_opts = SSL_OP_ALL | SSL_OP_SINGLE_DH_USE; + + if (use_ssl == FALSE) { + if (debug == TRUE) + logit(LOG_INFO, "INFO: SSL/TLS NOT initialized. Network encryption DISABLED."); + return; + } + +#ifndef USE_SSL_DH + ssl_opts = SSL_OP_ALL; + sslprm.allowDH = 0; +#endif + + if (sslprm.log_opts & SSL_LogStartup) + log_ssl_startup(); + + /* initialize SSL */ + SSL_load_error_strings(); + SSL_library_init(); + ENGINE_load_builtin_engines(); + RAND_set_rand_engine(NULL); + ENGINE_register_all_complete(); + + meth = SSLv23_server_method(); + + /* use week random seed if necessary */ + if (allow_weak_random_seed && (RAND_status() == 0)) { + if (RAND_file_name(seedfile, sizeof(seedfile) - 1)) + if (RAND_load_file(seedfile, -1)) + RAND_write_file(seedfile); + + if (RAND_status() == 0) { + logit(LOG_ERR, + "Warning: SSL/TLS uses a weak random seed which is highly discouraged"); + srand(time(NULL)); + for (i = 0; i < 500 && RAND_status() == 0; i++) { + for (c = 0; c < sizeof(seedfile); c += sizeof(int)) { + *((int *)(seedfile + c)) = rand(); + } + RAND_seed(seedfile, sizeof(seedfile)); + } + } + } + +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + + meth = TLS_method(); + +#else /* OPENSSL_VERSION_NUMBER >= 0x10100000 */ + +# ifndef OPENSSL_NO_SSL2 + if (sslprm.ssl_proto_ver == SSLv2) + meth = SSLv2_server_method(); +# endif +# ifndef OPENSSL_NO_SSL3 + if (sslprm.ssl_proto_ver == SSLv3) + meth = SSLv3_server_method(); +# endif + if (sslprm.ssl_proto_ver == TLSv1) + meth = TLSv1_server_method(); +# ifdef SSL_TXT_TLSV1_1 + if (sslprm.ssl_proto_ver == TLSv1_1) + meth = TLSv1_1_server_method(); +# ifdef SSL_TXT_TLSV1_2 + if (sslprm.ssl_proto_ver == TLSv1_2) + meth = TLSv1_2_server_method(); +# endif /* ifdef SSL_TXT_TLSV1_2 */ +# endif /* SSL_TXT_TLSV1_1 */ + +#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000 */ + + ctx = SSL_CTX_new(meth); + if (ctx == NULL) { + while ((x = ERR_get_error()) != 0) { + ERR_error_string(x, errstr); + logit(LOG_ERR, "Error: could not create SSL context : %s", errstr); + } + SSL_CTX_free(ctx); + exit(STATE_CRITICAL); + } + +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + + SSL_CTX_set_max_proto_version(ctx, 0); + + switch(sslprm.ssl_proto_ver) { + + case TLSv1_2: + SSL_CTX_set_max_proto_version(ctx, TLS1_2_VERSION); + case TLSv1_2_plus: + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + break; + + case TLSv1_1: + SSL_CTX_set_max_proto_version(ctx, TLS1_1_VERSION); + case TLSv1_1_plus: + SSL_CTX_set_min_proto_version(ctx, TLS1_1_VERSION); + break; + + case TLSv1: + SSL_CTX_set_max_proto_version(ctx, TLS1_VERSION); + case TLSv1_plus: + SSL_CTX_set_min_proto_version(ctx, TLS1_VERSION); + break; + + case SSLv3: + SSL_CTX_set_max_proto_version(ctx, SSL3_VERSION); + case SSLv3_plus: + SSL_CTX_set_min_proto_version(ctx, SSL3_VERSION); + break; + } + +#else /* OPENSSL_VERSION_NUMBER >= 0x10100000 */ + + switch(sslprm.ssl_proto_ver) { + case SSLv2: + case SSLv2_plus: + break; + case TLSv1_2: + case TLSv1_2_plus: +#ifdef SSL_OP_NO_TLSv1_1 + ssl_opts |= SSL_OP_NO_TLSv1_1; +#endif + case TLSv1_1: + case TLSv1_1_plus: + ssl_opts |= SSL_OP_NO_TLSv1; + case TLSv1: + case TLSv1_plus: + ssl_opts |= SSL_OP_NO_SSLv3; + case SSLv3: + case SSLv3_plus: + ssl_opts |= SSL_OP_NO_SSLv2; + break; + } + +#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000 */ + + SSL_CTX_set_options(ctx, ssl_opts); + + if (sslprm.cert_file != NULL) { + if (!SSL_CTX_use_certificate_file(ctx, sslprm.cert_file, SSL_FILETYPE_PEM)) { + SSL_CTX_free(ctx); + while ((x = ERR_get_error()) != 0) { + ERR_error_string(x, errstr); + logit(LOG_ERR, "Error: could not use certificate file %s : %s", + sslprm.cert_file, errstr); + } + exit(STATE_CRITICAL); + } + if (!SSL_CTX_use_PrivateKey_file(ctx, sslprm.privatekey_file, SSL_FILETYPE_PEM)) { + while ((x = ERR_get_error()) != 0) { + ERR_error_string(x, errstr); + logit(LOG_ERR, "Error: could not use private key file '%s' : %s", + sslprm.privatekey_file, errstr); + } + SSL_CTX_free(ctx); + exit(STATE_CRITICAL); + } + } + + if (sslprm.client_certs != 0) { + vrfy = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE; + if ((sslprm.client_certs & Require_Cert) != 0) + vrfy |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + SSL_CTX_set_verify(ctx, vrfy, verify_callback); + if (!SSL_CTX_load_verify_locations(ctx, sslprm.cacert_file, NULL)) { + while ((x = ERR_get_error_line_data(NULL, NULL, NULL, NULL)) != 0) { + logit(LOG_ERR, "Error: could not use CA certificate file '%s': %s\n", + sslprm.cacert_file, ERR_reason_error_string(x)); + } + SSL_CTX_free(ctx); + logit(LOG_ERR, "Error: could not use CA certificate '%s'", sslprm.cacert_file); + exit(STATE_CRITICAL); + } + } + + if (!sslprm.allowDH) { + if (strlen(sslprm.cipher_list) < sizeof(sslprm.cipher_list) - 6) + strcat(sslprm.cipher_list, ":!ADH"); + } else { + /* use anonymous DH ciphers */ + if (sslprm.allowDH == 2) { +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + strncpy(sslprm.cipher_list, "ADH@SECLEVEL=0", MAX_FILENAME_LENGTH - 1); +#else + strncpy(sslprm.cipher_list, "ADH", MAX_FILENAME_LENGTH - 1); +#endif + } + +#ifdef USE_SSL_DH + dh = get_dh2048(); + SSL_CTX_set_tmp_dh(ctx, dh); + DH_free(dh); +#endif + } + + if (SSL_CTX_set_cipher_list(ctx, sslprm.cipher_list) == 0) { + SSL_CTX_free(ctx); + logit(LOG_ERR, "Error: Could not set SSL/TLS cipher list"); + exit(STATE_CRITICAL); + } + + if (debug == TRUE) + logit(LOG_INFO, "INFO: SSL/TLS initialized. All network traffic will be encrypted."); +#endif +} + +void log_ssl_startup(void) +{ +#ifdef HAVE_SSL + char *vers; + + logit(LOG_INFO, "SSL Certificate File: %s", sslprm.cert_file ? sslprm.cert_file : "None"); + logit(LOG_INFO, "SSL Private Key File: %s", + sslprm.privatekey_file ? sslprm.privatekey_file : "None"); + logit(LOG_INFO, "SSL CA Certificate File: %s", + sslprm.cacert_file ? sslprm.cacert_file : "None"); + logit(LOG_INFO, "SSL Cipher List: %s", sslprm.cipher_list); + logit(LOG_INFO, "SSL Allow ADH: %d", sslprm.allowDH == 0); + logit(LOG_INFO, "SSL Client Certs: %s", + sslprm.client_certs == 0 ? "Don't Ask" : (sslprm.client_certs == + 1 ? "Accept" : "Require")); + logit(LOG_INFO, "SSL Log Options: 0x%02x", sslprm.log_opts); + switch (sslprm.ssl_proto_ver) { + case SSLv2: + vers = "SSLv2"; + break; + case SSLv2_plus: + vers = "SSLv2 And Above"; + break; + case SSLv3: + vers = "SSLv3"; + break; + case SSLv3_plus: + vers = "SSLv3 And Above"; + break; + case TLSv1: + vers = "TLSv1"; + break; + case TLSv1_plus: + vers = "TLSv1 And Above"; + break; + case TLSv1_1: + vers = "TLSv1_1"; + break; + case TLSv1_1_plus: + vers = "TLSv1_1 And Above"; + break; + case TLSv1_2: + vers = "TLSv1_2"; + break; + case TLSv1_2_plus: + vers = "TLSv1_2 And Above"; + break; + default: + vers = "INVALID VALUE!"; + break; + } + logit(LOG_INFO, "SSL Version: %s", vers); +#endif +} + +void usage(int result) +{ + if (result != OK) { + printf("\n"); + printf("Incorrect command line arguments supplied\n"); + printf("\n"); + } + printf("NRPE - Nagios Remote Plugin Executor\n"); + printf("Version: %s\n", PROGRAM_VERSION); + printf("\n"); + if (result != OK || show_help == TRUE) { + printf("Copyright (c) 2009-2017 Nagios Enterprises\n"); + printf(" 1999-2008 Ethan Galstad (nagios@nagios.org)\n"); + printf("\n"); + printf("Last Modified: %s\n", MODIFICATION_DATE); + printf("\n"); + printf("License: GPL v2 with exemptions (-l for more info)\n"); + printf("\n"); +#ifdef HAVE_SSL + printf("SSL/TLS Available, OpenSSL 0.9.6 or higher required\n"); + printf("\n"); +#endif +#ifdef HAVE_LIBWRAP + printf("TCP Wrappers Available\n"); + printf("\n"); +#endif +#ifdef ENABLE_COMMAND_ARGUMENTS + printf("***************************************************************\n"); + printf("** POSSIBLE SECURITY RISK - COMMAND ARGUMENTS ARE SUPPORTED! **\n"); + printf("** Read the NRPE SECURITY file for more information **\n"); + printf("***************************************************************\n"); + printf("\n"); +#endif +#ifndef HAVE_LIBWRAP + printf("***************************************************************\n"); + printf("** POSSIBLE SECURITY RISK - TCP WRAPPERS ARE NOT AVAILABLE! **\n"); + printf("** Read the NRPE SECURITY file for more information **\n"); + printf("***************************************************************\n"); + printf("\n"); +#endif + printf("Usage: nrpe [-V] [-n] -c [-4|-6] \n"); + printf("\n"); + printf("Options:\n"); + printf(" -V, --version Print version info and quit\n"); + printf(" -n, --no-ssl Do not use SSL\n"); + printf(" -c, --config=FILE Name of config file to use\n"); + printf(" -4, --ipv4 Use ipv4 only\n"); + printf(" -6, --ipv6 Use ipv6 only\n"); + printf(" (One of the following operating modes)\n"); + printf(" -i, --inetd Run as a service under inetd or xinetd\n"); + printf(" -d, --daemon Run as a standalone daemon\n"); + printf(" -s, --src Run as a subsystem under AIX\n"); + printf(" -f, --no-forking Don't fork() (for systemd, launchd, etc.)\n"); + printf("\n"); + printf("Notes:\n"); + printf("This program is designed to process requests from the check_nrpe\n"); + printf("plugin on the host(s) running Nagios. It can run as a service\n"); + printf("under inetd or xinetd (read the docs for info on this), or as a\n"); + printf("standalone daemon. Once a request is received from an authorized\n"); + printf("host, NRPE will execute the command/plugin (as defined in the\n"); + printf("config file) and return the plugin output and return code to the\n"); + printf("check_nrpe plugin.\n"); + printf("\n"); + } + + if (show_license == TRUE) + display_license(); + + exit(STATE_UNKNOWN); +} + +void run_inetd(void) +{ + check_privileges(); /* make sure we're not root */ + close(2); /* redirect STDERR to /dev/null */ + open("/dev/null", O_WRONLY); + handle_connection(0); /* handle the connection */ +} + +void run_src(void) +{ + /* if we're running under SRC we don't fork but does drop-privileges */ + + set_stdio_sigs(); + + do { + /* reset flags */ + sigrestart = FALSE; + sigshutdown = FALSE; + + wait_for_connections(); /* wait for connections */ + cleanup(); + } while (sigrestart == TRUE && sigshutdown == FALSE); +} + +/* daemonize and start listening for requests... */ +void run_daemon(void) +{ + pid_t pid; + + pid = fork(); + + if (pid != 0) { + if (pid == -1) { + logit(LOG_ERR, "fork() failed with error %d, bailing out...", errno); + exit(STATE_CRITICAL); + } + + return; + } + + setsid(); /* we're a daemon - set up a new process group */ + set_stdio_sigs(); + + do { + /* reset flags */ + sigrestart = FALSE; + sigshutdown = FALSE; + + wait_for_connections(); /* wait for connections */ + cleanup(); + } while (sigrestart == TRUE && sigshutdown == FALSE); +} + +void set_stdio_sigs(void) +{ +#ifdef HAVE_SIGACTION + struct sigaction sig_action; +#endif + + if (chdir("/") == -1) { + printf("ERROR: chdir(): %s, bailing out...\n", strerror(errno)); + exit(STATE_CRITICAL); + } + + close(0); /* close standard file descriptors */ + close(1); + close(2); + open("/dev/null", O_RDONLY); /* redirect standard descriptors to /dev/null */ + open("/dev/null", O_WRONLY); + open("/dev/null", O_WRONLY); + + /* handle signals */ +#ifdef HAVE_SIGACTION + sig_action.sa_sigaction = NULL; + sig_action.sa_handler = sighandler; + sigfillset(&sig_action.sa_mask); + sig_action.sa_flags = SA_NODEFER | SA_RESTART; + sigaction(SIGQUIT, &sig_action, NULL); + sigaction(SIGTERM, &sig_action, NULL); + sigaction(SIGHUP, &sig_action, NULL); +#else /* HAVE_SIGACTION */ + signal(SIGQUIT, sighandler); + signal(SIGTERM, sighandler); + signal(SIGHUP, sighandler); +#endif /* HAVE_SIGACTION */ + + logit(LOG_NOTICE, "Starting up daemon"); /* log info */ + if (write_pid_file() == ERROR) /* write pid file */ + exit(STATE_CRITICAL); + + clean_environ(keep_env_vars, nrpe_user); + + /* drop and then check privileges */ + drop_privileges(nrpe_user, nrpe_group, 0); + check_privileges(); +} + +void cleanup(void) +{ + int result; + + free_memory(); /* free all memory we allocated */ + + if (sigrestart == TRUE && sigshutdown == FALSE) { + close_log_file(); + result = read_config_file(config_file); /* read the config file */ + + if (result == ERROR) { /* exit if there are errors... */ + logit(LOG_ERR, "Config file '%s' contained errors, bailing out...", config_file); + exit(STATE_CRITICAL); + } + return; + } + + remove_pid_file(); /* remove pid file */ + logit(LOG_NOTICE, "Daemon shutdown\n"); + + close_log_file(); /* close the log file */ +} + +#ifdef HAVE_SSL +int verify_callback(int preverify_ok, X509_STORE_CTX * ctx) +{ + char name[256], issuer[256]; + X509 *err_cert; + int err; + SSL *ssl; + + if (preverify_ok || ((sslprm.log_opts & SSL_LogCertDetails) == 0)) + return preverify_ok; + + err_cert = X509_STORE_CTX_get_current_cert(ctx); + err = X509_STORE_CTX_get_error(ctx); + + /* Get the pointer to the SSL of the current connection */ + ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + + X509_NAME_oneline(X509_get_subject_name(err_cert), name, 256); + X509_NAME_oneline(X509_get_issuer_name(err_cert), issuer, 256); + + if (!preverify_ok && (sslprm.log_opts & SSL_LogCertDetails)) { + logit(LOG_ERR, "SSL Client has an invalid certificate: %s (issuer=%s) err=%d:%s", + name, issuer, err, X509_verify_cert_error_string(err)); + } + + return preverify_ok; +} +#endif + +/* read in the configuration file */ +int read_config_file(char *filename) +{ + struct stat st; + FILE *fp; + char config_file[MAX_FILENAME_LENGTH]; + char input_buffer[MAX_INPUT_BUFFER]; + char *input_line; + char *temp_buffer; + char *varname; + char *varvalue; + int line = 0; + int len = 0; + int x = 0; + + fp = fopen(filename, "r"); /* open the config file for reading */ + + /* exit if we couldn't open the config file */ + if (fp == NULL) { + logit(LOG_ERR, "Unable to open config file '%s' for reading\n", filename); + return ERROR; + } + + while (fgets(input_buffer, MAX_INPUT_BUFFER - 1, fp)) { + line++; + input_line = input_buffer; + + /* skip leading whitespace */ + while (isspace(*input_line)) + ++input_line; + + /* trim trailing whitespace */ + len = strlen(input_line); + for (x = len - 1; x >= 0; x--) { + if (isspace(input_line[x])) + input_line[x] = '\x0'; + else + break; + } + + /* skip comments and blank lines */ + if (input_line[0] == '#' || input_line[0] == '\x0' || input_line[0] == '\n') + continue; + + /* get the variable name */ + varname = strtok(input_line, "="); + if (varname == NULL) { + logit(LOG_ERR, "No variable name specified in config file '%s' - Line %d\n", + filename, line); + return ERROR; + } + + /* get the variable value */ + varvalue = strtok(NULL, "\n"); + if (varvalue == NULL) { + logit(LOG_ERR, "No variable value specified in config file '%s' - Line %d\n", + filename, line); + return ERROR; + + } else if (!strcmp(varname, "include_dir")) { + /* allow users to specify directories to recurse into for config files */ + + strncpy(config_file, varvalue, sizeof(config_file) - 1); + config_file[sizeof(config_file) - 1] = '\x0'; + + /* strip trailing / if necessary */ + if (config_file[strlen(config_file) - 1] == '/') + config_file[strlen(config_file) - 1] = '\x0'; + + /* process the config directory... */ + if (read_config_dir(config_file) == ERROR) + logit(LOG_ERR, "Continuing with errors..."); + + } else if (!strcmp(varname, "include") || !strcmp(varname, "include_file")) { + /* allow users to specify individual config files to include */ + + /* process the config file... */ + if (read_config_file(varvalue) == ERROR) + logit(LOG_ERR, "Continuing with errors..."); + + } else if (!strcmp(varname, "max_commands")) { + + max_commands = atoi(varvalue); + if (max_commands < 0) { + logit(LOG_WARNING, "max_commands set too low, setting to 0\n"); + max_commands = 0; + } + + } else if (!strcmp(varname, "server_port")) { + server_port = atoi(varvalue); + if (server_port < 1024) { + logit(LOG_ERR, + "Invalid port number specified in config file '%s' - Line %d\n", + filename, line); + return ERROR; + } + + } else if (!strcmp(varname, "command_prefix")) + command_prefix = strdup(varvalue); + + else if (!strcmp(varname, "server_address")) { + strncpy(server_address, varvalue, sizeof(server_address) - 1); + server_address[sizeof(server_address) - 1] = '\0'; + + } else if (!strcmp(varname, "allowed_hosts")) { + allowed_hosts = strdup(varvalue); + parse_allowed_hosts(allowed_hosts); + if (debug == TRUE) + show_acl_lists(); + + } else if (strstr(input_line, "command[")) { + temp_buffer = strtok(varname, "["); + temp_buffer = strtok(NULL, "]"); + if (temp_buffer == NULL) { + logit(LOG_ERR, "Invalid command specified in config file '%s' - Line %d\n", + filename, line); + return ERROR; + } + add_command(temp_buffer, varvalue); + + } else if (strstr(input_buffer, "debug")) { + debug = atoi(varvalue); + if (debug > 0) + debug = TRUE; + else + debug = FALSE; + + } else if (!strcmp(varname, "nrpe_user")) + nrpe_user = strdup(varvalue); + + else if (!strcmp(varname, "nrpe_group")) + nrpe_group = strdup(varvalue); + + else if (!strcmp(varname, "dont_blame_nrpe")) + allow_arguments = (atoi(varvalue) == 1) ? TRUE : FALSE; + + else if (!strcmp(varname, "allow_bash_command_substitution")) + allow_bash_cmd_subst = (atoi(varvalue) == 1) ? TRUE : FALSE; + + else if (!strcmp(varname, "command_timeout")) { + command_timeout = atoi(varvalue); + if (command_timeout < 1) { + logit(LOG_ERR, + "Invalid command_timeout specified in config file '%s' - Line %d\n", + filename, line); + return ERROR; + } + } else if (!strcmp(varname, "connection_timeout")) { + connection_timeout = atoi(varvalue); + if (connection_timeout < 1) { + logit(LOG_ERR, + "Invalid connection_timeout specified in config file '%s' - Line %d\n", + filename, line); + return ERROR; + } + + } else if (!strcmp(varname, "ssl_shutdown_timeout")) { + ssl_shutdown_timeout = atoi(varvalue); + if (ssl_shutdown_timeout < 1) { + logit(LOG_ERR, + "Invalid ssl_shutdown_timeout specified in config file '%s' - Line %d\n", + filename, line); + return ERROR; + } + + } else if (!strcmp(varname, "allow_weak_random_seed")) + allow_weak_random_seed = (atoi(varvalue) == 1) ? TRUE : FALSE; + + else if (!strcmp(varname, "pid_file")) + pid_file = strdup(varvalue); + + else if (!strcmp(varname, "listen_queue_size")) { + listen_queue_size = atoi(varvalue); + if (listen_queue_size == 0) { + logit(LOG_ERR, + "Invalid listen queue size specified in config file '%s' - Line %d\n", + filename, line); + return ERROR; + } + + } else if (!strcmp(varname, "ssl_version")) { + if (!strcmp(varvalue, "TLSv1.2")) + sslprm.ssl_proto_ver = TLSv1_2; + else if (!strcmp(varvalue, "TLSv1.2+")) + sslprm.ssl_proto_ver = TLSv1_2_plus; + else if (!strcmp(varvalue, "TLSv1.1")) + sslprm.ssl_proto_ver = TLSv1_1; + else if (!strcmp(varvalue, "TLSv1.1+")) + sslprm.ssl_proto_ver = TLSv1_1_plus; + else if (!strcmp(varvalue, "TLSv1")) + sslprm.ssl_proto_ver = TLSv1; + else if (!strcmp(varvalue, "TLSv1+")) + sslprm.ssl_proto_ver = TLSv1_plus; + else if (!strcmp(varvalue, "SSLv3")) + sslprm.ssl_proto_ver = SSLv3; + else if (!strcmp(varvalue, "SSLv3+")) + sslprm.ssl_proto_ver = SSLv3_plus; +#if OPENSSL_VERSION_NUMBER < 0x10100000 + else if (!strcmp(varvalue, "SSLv2")) + sslprm.ssl_proto_ver = SSLv2; + else if (!strcmp(varvalue, "SSLv2+")) + sslprm.ssl_proto_ver = SSLv2_plus; +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + else { + logit(LOG_ERR, "Invalid ssl version specified in config file '%s' - Line %d", + filename, line); + return ERROR; + } + + } else if (!strcmp(varname, "ssl_use_adh")) { + sslprm.allowDH = atoi(varvalue); + if (sslprm.allowDH < 0 || sslprm.allowDH > 2) { + logit(LOG_ERR, + "Invalid use adh value specified in config file '%s' - Line %d", + filename, line); + return ERROR; + } + + } else if (!strcmp(varname, "ssl_logging")) + sslprm.log_opts = strtoul(varvalue, NULL, 0); + + else if (!strcmp(varname, "ssl_cipher_list")) { + strncpy(sslprm.cipher_list, varvalue, sizeof(sslprm.cipher_list) - 1); + sslprm.cipher_list[sizeof(sslprm.cipher_list) - 1] = '\0'; + + } else if (!strcmp(varname, "ssl_cert_file")) + sslprm.cert_file = strdup(varvalue); + + else if (!strcmp(varname, "ssl_cacert_file")) + sslprm.cacert_file = strdup(varvalue); + + else if (!strcmp(varname, "ssl_privatekey_file")) + sslprm.privatekey_file = strdup(varvalue); + + else if (!strcmp(varname, "ssl_client_certs")) { + sslprm.client_certs = atoi(varvalue); + if ((int)sslprm.client_certs < 0 || sslprm.client_certs > Require_Cert) { + logit(LOG_ERR, + "Invalid client certs value specified in config file '%s' - Line %d", + filename, line); + return ERROR; + } + /* if requiring or logging client certs, make sure "Ask" is turned on */ + if (sslprm.client_certs & Require_Cert) + sslprm.client_certs |= Ask_For_Cert; + + } else if (!strcmp(varname, "log_facility")) { + if ((get_log_facility(varvalue)) == OK) { + /* re-open log using new facility */ + closelog(); + openlog("nrpe", LOG_PID, log_facility); + } else + logit(LOG_WARNING, + "Invalid log_facility specified in config file '%s' - Line %d\n", + filename, line); + + } else if (!strcmp(varname, "keep_env_vars")) + keep_env_vars = strdup(varvalue); + + else if (!strcmp(varname, "nasty_metachars")) + nasty_metachars = strdup(varvalue); + + else if (!strcmp(varname, "log_file")) { + log_file = strdup(varvalue); + open_log_file(); + + } else { + logit(LOG_WARNING, "Unknown option specified in config file '%s' - Line %d\n", + filename, line); + continue; + } + } + + fclose(fp); /* close the config file */ + return OK; +} + +/* process all config files in a specific config directory (with directory recursion) */ +int read_config_dir(char *dirname) +{ + struct dirent *dirfile; +#ifdef HAVE_SCANDIR + struct dirent **dirfiles; + int x, i, n; +#else + DIR *dirp; + int x; +#endif + struct stat buf; + char config_file[MAX_FILENAME_LENGTH]; + int result = OK; + +#ifdef HAVE_SCANDIR + /* read and sort the directory contents */ + n = scandir(dirname, &dirfiles, 0, alphasort); + if (n < 0) { + logit(LOG_ERR, "Could not open config directory '%s' for reading.\n", dirname); + return ERROR; + } + + for (i = 0; i < n; i++) { + dirfile = dirfiles[i]; +#else + /* open the directory for reading */ + dirp = opendir(dirname); + if (dirp == NULL) { + logit(LOG_ERR, "Could not open config directory '%s' for reading.\n", dirname); + return ERROR; + } + + while ((dirfile = readdir(dirp)) != NULL) { +#endif + + /* process all files in the directory... */ + + /* create the full path to the config file or subdirectory */ + snprintf(config_file, sizeof(config_file) - 1, "%s/%s", dirname, dirfile->d_name); + config_file[sizeof(config_file) - 1] = '\x0'; + stat(config_file, &buf); + + /* process this if it's a config file... */ + x = strlen(dirfile->d_name); + if (x > 4 && !strcmp(dirfile->d_name + (x - 4), ".cfg")) { + + /* only process normal files */ + if (!S_ISREG(buf.st_mode)) + continue; + + /* process the config file */ + result = read_config_file(config_file); + + /* break out if we encountered an error */ + if (result == ERROR) + break; + } + + /* recurse into subdirectories... */ + if (S_ISDIR(buf.st_mode)) { + + /* ignore current, parent and hidden directory entries */ + if (dirfile->d_name[0] == '.') + continue; + + /* process the config directory */ + result = read_config_dir(config_file); + + /* break out if we encountered an error */ + if (result == ERROR) + break; + + } + } + +#ifdef HAVE_SCANDIR + for (i = 0; i < n; i++) + free(dirfiles[i]); + free(dirfiles); +#else + closedir(dirp); +#endif + + return result; +} + +/* determines facility to use with syslog */ +int get_log_facility(char *varvalue) +{ + if (!strcmp(varvalue, "kern")) + log_facility = LOG_KERN; + else if (!strcmp(varvalue, "user")) + log_facility = LOG_USER; + else if (!strcmp(varvalue, "mail")) + log_facility = LOG_MAIL; + else if (!strcmp(varvalue, "daemon")) + log_facility = LOG_DAEMON; + else if (!strcmp(varvalue, "auth")) + log_facility = LOG_AUTH; + else if (!strcmp(varvalue, "syslog")) + log_facility = LOG_SYSLOG; + else if (!strcmp(varvalue, "lrp")) + log_facility = LOG_LPR; + else if (!strcmp(varvalue, "news")) + log_facility = LOG_NEWS; + else if (!strcmp(varvalue, "uucp")) + log_facility = LOG_UUCP; + else if (!strcmp(varvalue, "cron")) + log_facility = LOG_CRON; + else if (!strcmp(varvalue, "authpriv")) + log_facility = LOG_AUTHPRIV; + else if (!strcmp(varvalue, "ftp")) + log_facility = LOG_FTP; + else if (!strcmp(varvalue, "local0")) + log_facility = LOG_LOCAL0; + else if (!strcmp(varvalue, "local1")) + log_facility = LOG_LOCAL1; + else if (!strcmp(varvalue, "local2")) + log_facility = LOG_LOCAL2; + else if (!strcmp(varvalue, "local3")) + log_facility = LOG_LOCAL3; + else if (!strcmp(varvalue, "local4")) + log_facility = LOG_LOCAL4; + else if (!strcmp(varvalue, "local5")) + log_facility = LOG_LOCAL5; + else if (!strcmp(varvalue, "local6")) + log_facility = LOG_LOCAL6; + else if (!strcmp(varvalue, "local7")) + log_facility = LOG_LOCAL7; + else { + log_facility = LOG_DAEMON; + return ERROR; + } + + return OK; +} + +/* adds a new command definition from the config file to the list in memory */ +int add_command(char *command_name, char *command_line) +{ + command *new_command; + + if (command_name == NULL || command_line == NULL) + return ERROR; + + /* allocate memory for the new command */ + new_command = (command *) malloc(sizeof(command)); + if (new_command == NULL) + return ERROR; + + new_command->command_name = strdup(command_name); + if (new_command->command_name == NULL) { + free(new_command); + return ERROR; + } + new_command->command_line = strdup(command_line); + if (new_command->command_line == NULL) { + free(new_command->command_name); + free(new_command); + return ERROR; + } + + /* add new command to head of list in memory */ + new_command->next = command_list; + command_list = new_command; + + if (debug == TRUE) + logit(LOG_DEBUG, "Added command[%s]=%s\n", command_name, command_line); + + return OK; +} + +/* given a command name, find the structure in memory */ +command *find_command(char *command_name) +{ + command *temp_command; + + for (temp_command = command_list; temp_command != NULL; temp_command = temp_command->next) + if (!strcmp(command_name, temp_command->command_name)) + return temp_command; + + return NULL; +} + +/* Start listen on a particular port */ +void create_listener(struct addrinfo *ai) +{ + int ret; + char ntop[NI_MAXHOST], strport[NI_MAXSERV]; + int listen_sock; + int flag = 1; + + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + return; + + if (num_listen_socks >= MAX_LISTEN_SOCKS) { + logit(LOG_ERR, "Too many listen sockets. Enlarge MAX_LISTEN_SOCKS"); + exit(1); + } + + if ((ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), + strport, sizeof(strport), NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { + logit(LOG_ERR, "getnameinfo failed: %.100s", gai_strerror(ret)); + return; + } + + /* Create socket for listening. */ + listen_sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (listen_sock < 0) { + /* kernel may not support ipv6 */ + logit(LOG_ERR, "socket: %.100s", strerror(errno)); + return; + } + + /* socket should be non-blocking */ + fcntl(listen_sock, F_SETFL, O_NONBLOCK); + + /* set the reuse address flag so we don't get errors when restarting */ + if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) < 0) { + logit(LOG_ERR, "setsockopt SO_REUSEADDR: %s", strerror(errno)); + return; + } +#ifdef IPV6_V6ONLY + /* Only communicate in IPv6 over AF_INET6 sockets. */ + if (ai->ai_family == AF_INET6) { + if (setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag)) == -1) { + fprintf(stderr, "setsockopt IPV6_V6ONLY: %s", strerror(errno)); + } + } +#endif + + /* Bind the socket to the desired port. */ + if (bind(listen_sock, ai->ai_addr, ai->ai_addrlen) < 0) { + logit(LOG_ERR, "Bind to port %s on %s failed: %.200s.", + strport, ntop, strerror(errno)); + close(listen_sock); + return; + } + listen_socks[num_listen_socks] = listen_sock; + num_listen_socks++; + + /* Start listening on the port. */ + if (listen(listen_sock, listen_queue_size) < 0) { + logit(LOG_ERR, "listen on [%s]:%s: %.100s", ntop, strport, strerror(errno)); + exit(1); + } + + logit(LOG_INFO, "Server listening on %s port %s.", ntop, strport); +} + +/* Close all listening sockets */ +static void close_listen_socks(void) +{ + int i; + + for (i = 0; i <= num_listen_socks; i++) { + close(listen_socks[i]); + num_listen_socks--; + } +} + +/* wait for incoming connection requests */ +void wait_for_connections(void) +{ +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE + struct sockaddr_storage from; +#else + struct sockaddr from; +#endif + socklen_t fromlen; + fd_set *fdset = NULL; + int maxfd = 0, new_sd = 0, i, rc, retval; + + setup_wait_conn(); + + /* listen for connection requests - fork() if we get one */ + while (1) { + /* bail out if necessary */ + if (sigrestart == TRUE || sigshutdown == TRUE) + break; + + for (i = 0; i < num_listen_socks; i++) { + if (listen_socks[i] > maxfd) + maxfd = listen_socks[i]; + } + + if (fdset != NULL) + free(fdset); + fdset = (fd_set *) calloc(how_many(maxfd + 1, NFDBITS), sizeof(fd_mask)); + + for (i = 0; i < num_listen_socks; i++) + FD_SET(listen_socks[i], fdset); + + /* Wait in select until there is a connection. */ + retval = select(maxfd + 1, fdset, NULL, NULL, NULL); + + /* bail out if necessary */ + if (sigrestart == TRUE || sigshutdown == TRUE) + break; + + /* error */ + if (retval < 0) + continue; + + for (i = 0; i < num_listen_socks; i++) { + if (!FD_ISSET(listen_socks[i], fdset)) + continue; + fromlen = (socklen_t)sizeof(from); + + /* accept a new connection request */ + new_sd = accept(listen_socks[i], (struct sockaddr *)&from, &fromlen); + + /* some kind of error occurred... */ + if (new_sd < 0) { + /* bail out if necessary */ + if (sigrestart == TRUE || sigshutdown == TRUE) + break; + if (errno == EWOULDBLOCK || errno == EINTR) /* retry */ + continue; + /* socket is nonblocking and we don't have a connection yet */ + if (errno == EAGAIN) + continue; + if (errno == ENOBUFS) /* fix for HP-UX 11.0 - just retry */ + continue; + + break; /* else handle the error later */ + } + + + rc = wait_conn_fork(new_sd); + if (rc == TRUE) + continue; /* Continue if this is the parent returning */ + + /* grandchild running here */ + conn_check_peer(new_sd); + + /* handle the client connection */ + handle_connection(new_sd); + + /* log info */ + if (debug == TRUE) + logit(LOG_DEBUG, "Connection from %s closed.", remote_host); + + /* close socket prior to exiting */ + close(new_sd); + + exit(STATE_OK); + + } + } + + /* close the sockets we're listening on */ + close_listen_socks(); + freeaddrinfo(listen_addrs); + listen_addrs = NULL; + + return; +} + +void setup_wait_conn(void) +{ + struct addrinfo *ai; + char addrstr[100]; + void *ptr; + + add_listen_addr(&listen_addrs, address_family, + (strcmp(server_address, "") == 0) ? NULL : server_address, server_port); + + for (ai = listen_addrs; ai; ai = ai->ai_next) { + if (debug == TRUE) { + inet_ntop (ai->ai_family, ai->ai_addr->sa_data, addrstr, 100); + ptr = &((struct sockaddr_in *) ai->ai_addr)->sin_addr; + inet_ntop (ai->ai_family, ptr, addrstr, 100); + logit(LOG_INFO, "SETUP_WAIT_CONN FOR: IPv4 address: %s (%s)\n", addrstr, ai->ai_canonname); + } + create_listener(ai); + } + + if (!num_listen_socks) { + logit(LOG_ERR, "Cannot bind to any address."); + exit(1); + } + + /* log warning about command arguments */ +#ifdef ENABLE_COMMAND_ARGUMENTS + if (allow_arguments == TRUE) + logit(LOG_NOTICE, + "Warning: Daemon is configured to accept command arguments from clients!"); +# ifdef ENABLE_BASH_COMMAND_SUBSTITUTION + if (TRUE == allow_bash_cmd_subst) { + if (TRUE == allow_arguments) + logit(LOG_NOTICE, + "Warning: Daemon is configured to accept command arguments with bash command substitutions!"); + else + logit(LOG_NOTICE, + "Warning: Daemon is configured to accept command arguments with bash command substitutions, but is not configured to accept command arguments from clients. Enable command arguments if you wish to allow command arguments with bash command substitutions."); + } +# endif +#endif + + logit(LOG_INFO, "Listening for connections on port %d", server_port); + + if (allowed_hosts) + logit(LOG_INFO, "Allowing connections from: %s\n", allowed_hosts); +} + +int wait_conn_fork(int sock) +{ +#ifdef HAVE_SIGACTION + struct sigaction sig_action; +#endif + pid_t pid; + + /* child process should handle the connection */ + pid = fork(); + + if (pid > 0) { + close(sock); /* parent doesn't need the new connection */ + waitpid(pid, NULL, 0); /* parent waits for first child to exit */ + return TRUE; /* tell caller this is the parent process */ + } + + if (pid < 0) { + logit(LOG_ERR, "fork() failed with error %d, bailing out...", errno); + exit(STATE_CRITICAL); + } + + /* fork again so we don't create zombies */ + pid = fork(); + + if (pid < 0) { + logit(LOG_ERR, "Second fork() failed with error %d, bailing out...", errno); + exit(STATE_CRITICAL); + } + + if (pid > 0) { + /* first child returns immediately, grandchild is inherited by + INIT process -> no zombies... */ + exit(STATE_OK); + } + + /* hey, there was an error... */ + if (sock < 0) { + /* log error */ + logit(LOG_ERR, "Network server accept failure (%d: %s)", + errno, strerror(errno)); + exit(STATE_OK); + } + + /* all good - handle signals */ +#ifdef HAVE_SIGACTION + sig_action.sa_sigaction = NULL; + sig_action.sa_handler = child_sighandler; + sigfillset(&sig_action.sa_mask); + sig_action.sa_flags = SA_NODEFER | SA_RESTART; + sigaction(SIGQUIT, &sig_action, NULL); + sigaction(SIGTERM, &sig_action, NULL); + sigaction(SIGHUP, &sig_action, NULL); +#else /* HAVE_SIGACTION */ + signal(SIGQUIT, child_sighandler); + signal(SIGTERM, child_sighandler); + signal(SIGHUP, child_sighandler); +#endif /* HAVE_SIGACTION */ + + close_listen_socks(); /* grandchild does not need to listen */ + + return FALSE; /* tell caller this isn't the parent process */ +} + +void conn_check_peer(int sock) +{ +#ifdef HAVE_LIBWRAP + struct request_info req; +#endif +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE + struct sockaddr_storage addr; +#else + struct sockaddr addr; +#endif + struct sockaddr_in *nptr; + struct sockaddr_in6 *nptr6; + + char ipstr[INET6_ADDRSTRLEN]; + socklen_t addrlen; + int rc; + + /* find out who just connected... */ + addrlen = sizeof(addr); + rc = getpeername(sock, (struct sockaddr *)&addr, &addrlen); + + if (rc < 0) { + /* log error */ + logit(LOG_ERR, "Error: Network server getpeername() failure (%d: %s)", + errno, strerror(errno)); + + /* close socket prior to exiting */ + close(sock); + return; + } + +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE + switch (addr.ss_family) { +#else + switch (addr.sa_family) { +#endif + + case AF_INET: + nptr = (struct sockaddr_in *)&addr; + strncpy(remote_host, inet_ntoa(nptr->sin_addr), sizeof(remote_host) - 1); + remote_host[MAX_HOST_ADDRESS_LENGTH - 1] = '\0'; + break; + + case AF_INET6: + nptr6 = (struct sockaddr_in6 *)&addr; + if (inet_ntop(AF_INET6, (const void *)&(nptr6->sin6_addr), + ipstr, sizeof(ipstr)) == NULL) { + strncpy(ipstr, "Unknown", sizeof(ipstr)); + } + strncpy(remote_host, ipstr, sizeof(remote_host) - 1); + remote_host[MAX_HOST_ADDRESS_LENGTH - 1] = '\0'; + break; + } + + if (debug == TRUE) + logit(LOG_INFO, "CONN_CHECK_PEER: checking if host is allowed: %s port %d\n", + remote_host, nptr->sin_port); + + /* is this host allowed? */ + if (allowed_hosts) { +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE + switch (addr.ss_family) { +#else + switch (addr.sa_family) { +#endif + + case AF_INET: + /* log info */ + if (debug == TRUE || (sslprm.log_opts & SSL_LogIpAddr)) + logit(LOG_DEBUG, "Connection from %s port %d", remote_host, nptr->sin_port); + + if (!is_an_allowed_host(AF_INET, (void *)&(nptr->sin_addr))) { + /* log error */ + logit(LOG_ERR, "Host %s is not allowed to talk to us!", remote_host); + + /* log info */ + if (debug == TRUE) + logit(LOG_DEBUG, "Connection from %s closed.", remote_host); + + /* close socket prior to exiting */ + close(sock); + exit(STATE_OK); + + } else { + + /* log info */ + if (debug == TRUE) { + logit(LOG_DEBUG, "Host address is in allowed_hosts"); + } + + } + break; + + case AF_INET6: + /* log info */ + strcpy(remote_host, ipstr); + if (debug == TRUE || (sslprm.log_opts & SSL_LogIpAddr)) { + logit(LOG_DEBUG, "Connection from %s port %d", ipstr, nptr6->sin6_port); + } + + if (!is_an_allowed_host(AF_INET6, (void *)&(nptr6->sin6_addr))) { + /* log error */ + logit(LOG_ERR, "Host %s is not allowed to talk to us!", ipstr); + + /* log info */ + if (debug == TRUE) + logit(LOG_DEBUG, "Connection from %s closed.", ipstr); + + /* close socket prior to exiting */ + close(sock); + exit(STATE_OK); + + } else { + /* log info */ + if (debug == TRUE) + logit(LOG_DEBUG, "Host address is in allowed_hosts"); + } + break; + } + } + +#ifdef HAVE_LIBWRAP + /* Check whether or not connections are allowed from this host */ + request_init(&req, RQ_DAEMON, "nrpe", RQ_FILE, sock, 0); + fromhost(&req); + + if (!hosts_access(&req)) { + logit(LOG_DEBUG, "Connection refused by TCP wrapper"); + refuse(&req); /* refuse the connection */ + /* should not be reached */ + logit(LOG_ERR, "libwrap refuse() returns!"); + close(sock); + exit(STATE_CRITICAL); + } +#endif +} + +/* handles a client connection */ +void handle_connection(int sock) +{ + u_int32_t calculated_crc32; + command *temp_command; + v2_packet receive_packet, send_packet; + v3_packet *v3_receive_packet = NULL, *v3_send_packet = NULL; + int bytes_to_send; + char buffer[MAX_INPUT_BUFFER], *send_buff = NULL, *send_pkt; + char raw_command[MAX_INPUT_BUFFER]; + char processed_command[MAX_INPUT_BUFFER]; + int result = STATE_OK; + int early_timeout = FALSE; + int rc; + int x; + int32_t pkt_size; +#ifdef DEBUG + FILE *errfp; +#endif +#ifdef HAVE_SSL + SSL *ssl = NULL; +#endif + + /* do SSL handshake */ +#ifdef HAVE_SSL + if (use_ssl == TRUE) { + if ((ssl = SSL_new(ctx)) == NULL) { + logit(LOG_ERR, "Error: Could not create SSL connection structure."); +# ifdef DEBUG + errfp = fopen("/tmp/err.log", "a"); + ERR_print_errors_fp(errfp); + fclose(errfp); +# endif + return; + } + + if (handle_conn_ssl(sock, ssl) != OK) + return; + } +#endif + +#ifdef HAVE_SSL + rc = read_packet(sock, ssl, &receive_packet, &v3_receive_packet); +#else + rc = read_packet(sock, NULL, &receive_packet, &v3_receive_packet); +#endif + + /* disable connection alarm - a new alarm will be setup during my_system */ + alarm(0); + + /* recv() error or client disconnect */ + if (rc <= 0) { + /* log error */ + logit(LOG_ERR, "Could not read request from client %s, bailing out...", remote_host); + if (v3_receive_packet) + free(v3_receive_packet); +#ifdef HAVE_SSL + if (ssl) { + complete_SSL_shutdown(ssl); + SSL_free(ssl); + logit(LOG_INFO, "INFO: SSL Socket Shutdown.\n"); + } +#endif + return; + } + + /* make sure the request is valid */ + if (validate_request(&receive_packet, v3_receive_packet) == ERROR) { + /* log an error */ + logit(LOG_ERR, "Client request from %s was invalid, bailing out...", remote_host); + + /* free memory */ + free(command_name); + command_name = NULL; + for (x = 0; x < MAX_COMMAND_ARGUMENTS; x++) { + free(macro_argv[x]); + macro_argv[x] = NULL; + } + if (v3_receive_packet) + free(v3_receive_packet); + +#ifdef HAVE_SSL + if (ssl) { + complete_SSL_shutdown(ssl); + SSL_free(ssl); + } +#endif + + return; + } + + /* log info */ + if (debug == TRUE) + logit(LOG_DEBUG, "Host %s is asking for command '%s' to be run...", + remote_host, command_name); + + /* if this is the version check command, just spew it out */ + if (!strcmp(command_name, NRPE_HELLO_COMMAND)) { + snprintf(buffer, sizeof(buffer), "NRPE v%s", PROGRAM_VERSION); + buffer[sizeof(buffer) - 1] = '\x0'; + if (debug == TRUE) /* log info */ + logit(LOG_DEBUG, "Response to %s: %s", remote_host, buffer); + if (v3_receive_packet) + send_buff = strdup(buffer); + else { + send_buff = calloc(1, sizeof(buffer)); + strcpy(send_buff, buffer); + } + result = STATE_OK; + + } else { + + /* find the command we're supposed to run */ + temp_command = find_command(command_name); + if (temp_command == NULL) { + snprintf(buffer, sizeof(buffer), "NRPE: Command '%s' not defined", command_name); + buffer[sizeof(buffer) - 1] = '\x0'; + if (debug == TRUE) /* log error */ + logit(LOG_DEBUG, "%s", buffer); + if (v3_receive_packet) + send_buff = strdup(buffer); + else { + send_buff = calloc(1, sizeof(buffer)); + strcpy(send_buff, buffer); + } + result = STATE_UNKNOWN; + + } else { + + /* process command line */ + if (command_prefix == NULL) + strncpy(raw_command, temp_command->command_line, sizeof(raw_command) - 1); + else + snprintf(raw_command, sizeof(raw_command) - 1, "%s %s", command_prefix, + temp_command->command_line); + raw_command[sizeof(raw_command) - 1] = '\x0'; + process_macros(raw_command, processed_command, sizeof(processed_command)); + + if (debug == TRUE) /* log info */ + logit(LOG_DEBUG, "Running command: %s", processed_command); + + /* run the command */ + strcpy(buffer, ""); + result = my_system(processed_command, command_timeout, &early_timeout, &send_buff); + + if (debug == TRUE) /* log debug info */ + logit(LOG_DEBUG, "Command completed with return code %d and output: %s", + result, send_buff); + + /* see if the command timed out */ + if (early_timeout == TRUE) { + sprintf(send_buff, "NRPE: Command timed out after %d seconds\n", + command_timeout); + result = STATE_UNKNOWN; + } else if (!strcmp(send_buff, "")) { + sprintf(send_buff, "NRPE: Unable to read output\n"); + result = STATE_UNKNOWN; + } + + /* check return code bounds */ + if ((result < 0) || (result > 3)) { + /* log error */ + logit(LOG_ERR, "Bad return code for [%s]: %d", send_buff, result); + result = STATE_UNKNOWN; + } + } + } + + /* free memory */ + free(command_name); + command_name = NULL; + for (x = 0; x < MAX_COMMAND_ARGUMENTS; x++) { + free(macro_argv[x]); + macro_argv[x] = NULL; + } + if (v3_receive_packet) + free(v3_receive_packet); + pkt_size = strlen(send_buff); + /* strip newline character from end of output buffer */ + if (send_buff[strlen(send_buff) - 1] == '\n') + send_buff[strlen(send_buff) - 1] = '\x0'; + + if (packet_ver == NRPE_PACKET_VERSION_2) { + pkt_size = sizeof(v2_packet); + send_pkt = (char *)&send_packet; + + /* clear the response packet buffer */ + memset(&send_packet, 0, sizeof(send_packet)); + /* fill the packet with semi-random data */ + randomize_buffer((char *)&send_packet, sizeof(send_packet)); + + /* initialize response packet data */ + send_packet.packet_version = htons(packet_ver); + send_packet.packet_type = htons(RESPONSE_PACKET); + send_packet.result_code = htons(result); + strncpy(&send_packet.buffer[0], send_buff, MAX_PACKETBUFFER_LENGTH); + send_packet.buffer[MAX_PACKETBUFFER_LENGTH - 1] = '\x0'; + + /* calculate the crc 32 value of the packet */ + send_packet.crc32_value = 0; + calculated_crc32 = calculate_crc32((char *)&send_packet, sizeof(send_packet)); + send_packet.crc32_value = htonl(calculated_crc32); + + } else { + + pkt_size = (sizeof(v3_packet) - 1) + strlen(send_buff); + v3_send_packet = calloc(1, pkt_size); + send_pkt = (char *)v3_send_packet; + /* initialize response packet data */ + v3_send_packet->packet_version = htons(packet_ver); + v3_send_packet->packet_type = htons(RESPONSE_PACKET); + v3_send_packet->result_code = htons(result); + v3_send_packet->alignment = 0; + v3_send_packet->buffer_length = htonl(strlen(send_buff)); + strcpy(&v3_send_packet->buffer[0], send_buff); + + /* calculate the crc 32 value of the packet */ + v3_send_packet->crc32_value = 0; + calculated_crc32 = calculate_crc32((char *)v3_send_packet, pkt_size); + v3_send_packet->crc32_value = htonl(calculated_crc32); + } + + /* send the response back to the client */ + bytes_to_send = pkt_size; + if (use_ssl == FALSE) + sendall(sock, send_pkt, &bytes_to_send); +#ifdef HAVE_SSL + else + SSL_write(ssl, send_pkt, bytes_to_send); +#endif + +#ifdef HAVE_SSL + if (ssl) { + complete_SSL_shutdown(ssl); + SSL_free(ssl); + } +#endif + + if (v3_send_packet) + free(v3_send_packet); + + /* log info */ + if (debug == TRUE) + logit(LOG_DEBUG, "Return Code: %d, Output: %s", result, send_buff); + + free(send_buff); + + return; +} + +void init_handle_conn(void) +{ +#ifdef HAVE_SIGACTION + struct sigaction sig_action; +#endif + + /* log info */ + if (debug == TRUE) + logit(LOG_DEBUG, "Handling the connection..."); + + /* set connection handler */ +#ifdef HAVE_SIGACTION + sig_action.sa_sigaction = NULL; + sig_action.sa_handler = my_connection_sighandler; + sigfillset(&sig_action.sa_mask); + sig_action.sa_flags = SA_NODEFER | SA_RESTART; + sigaction(SIGALRM, &sig_action, NULL); +#else + signal(SIGALRM, my_connection_sighandler); +#endif /* HAVE_SIGACTION */ + alarm(connection_timeout); +} + +int handle_conn_ssl(int sock, void *ssl_ptr) +{ +#ifdef HAVE_SSL +# if (defined(__sun) && defined(SOLARIS_10)) || defined(_AIX) || defined(__hpux) + SSL_CIPHER *c; +#else + const SSL_CIPHER *c; +#endif + const char *errmsg = NULL; + char buffer[MAX_INPUT_BUFFER]; + SSL *ssl = (SSL*)ssl_ptr; + X509 *peer; + int rc, x; + + SSL_set_fd(ssl, sock); + + /* keep attempting the request if needed */ + while (((rc = SSL_accept(ssl)) != 1) + && (SSL_get_error(ssl, rc) == SSL_ERROR_WANT_READ)); + + if (rc != 1) { + /* oops, got an unrecoverable error -- get out */ + if (sslprm.log_opts & (SSL_LogCertDetails | SSL_LogIfClientCert)) { + int nerrs = 0; + rc = 0; + while ((x = ERR_get_error_line_data(NULL, NULL, NULL, NULL)) != 0) { + errmsg = ERR_reason_error_string(x); + logit(LOG_ERR, "Error: (ERR_get_error_line_data = %d), Could not complete SSL handshake with %s: %s", x, remote_host, errmsg); + + if (errmsg && !strcmp(errmsg, "no shared cipher") && (sslprm.cert_file == NULL || sslprm.cacert_file == NULL)) + logit(LOG_ERR, "Error: This could be because you have not specified certificate or ca-certificate files"); + + ++nerrs; + } + + if (nerrs == 0) { + logit(LOG_ERR, "Error: (nerrs = 0) Could not complete SSL handshake with %s: %d", remote_host, SSL_get_error(ssl, rc)); + } + } else { + logit(LOG_ERR, "Error: (!log_opts) Could not complete SSL handshake with %s: %d", remote_host, SSL_get_error(ssl, rc)); + } +# ifdef DEBUG + errfp = fopen("/tmp/err.log", "a"); + ERR_print_errors_fp(errfp); + fclose(errfp); +# endif + return ERROR; + } + + /* successful handshake */ + if (sslprm.log_opts & SSL_LogVersion) + logit(LOG_NOTICE, "Remote %s - SSL Version: %s", remote_host, SSL_get_version(ssl)); + + if (sslprm.log_opts & SSL_LogCipher) { + c = SSL_get_current_cipher(ssl); + logit(LOG_NOTICE, "Remote %s - %s, Cipher is %s", remote_host, SSL_CIPHER_get_version(c), SSL_CIPHER_get_name(c)); + } + + if ((sslprm.log_opts & SSL_LogIfClientCert) + || (sslprm.log_opts & SSL_LogCertDetails)) { + + + peer = SSL_get_peer_certificate(ssl); + + if (peer) { + if (sslprm.log_opts & SSL_LogIfClientCert) + logit(LOG_NOTICE, "SSL Client %s has %s certificate", + remote_host, SSL_get_verify_result(ssl) == X509_V_OK ? "a valid" : "an invalid"); + + if (sslprm.log_opts & SSL_LogCertDetails) { + + X509_NAME_oneline(X509_get_subject_name(peer), buffer, sizeof(buffer)); + logit(LOG_NOTICE, "SSL Client %s Cert Name: %s", + remote_host, buffer); + + X509_NAME_oneline(X509_get_issuer_name(peer), buffer, sizeof(buffer)); + logit(LOG_NOTICE, "SSL Client %s Cert Issuer: %s", + remote_host, buffer); + } + + } else if (sslprm.client_certs == 0) + logit(LOG_NOTICE, "SSL Not asking for client certification"); + + else + logit(LOG_NOTICE, "SSL Client %s did not present a certificate", + remote_host); + } +#endif + + return OK; +} + +int read_packet(int sock, void *ssl_ptr, v2_packet * v2_pkt, v3_packet ** v3_pkt) +{ + int32_t common_size, tot_bytes, bytes_to_recv, buffer_size; + int rc; + char *buff_ptr; + + /* Read only the part that's common between versions 2 & 3 */ + common_size = tot_bytes = bytes_to_recv = (char *)&v2_pkt->buffer - (char *)v2_pkt; + + if (use_ssl == FALSE) { + rc = recvall(sock, (char *)v2_pkt, &tot_bytes, socket_timeout); + + if (rc <= 0 || rc != bytes_to_recv) + return -1; + + packet_ver = ntohs(v2_pkt->packet_version); + if (packet_ver != NRPE_PACKET_VERSION_2 && packet_ver != NRPE_PACKET_VERSION_3) { + logit(LOG_ERR, "Error: (use_ssl == false): Request packet version was invalid!"); + return -1; + } + + if (packet_ver == NRPE_PACKET_VERSION_2) { + buffer_size = sizeof(v2_packet) - common_size; + buff_ptr = (char *)v2_pkt + common_size; + + } else { + int32_t pkt_size = sizeof(v3_packet) - 1; + + /* Read the alignment filler */ + bytes_to_recv = sizeof(int16_t); + rc = recvall(sock, (char *)&buffer_size, &bytes_to_recv, socket_timeout); + if (rc <= 0 || bytes_to_recv != sizeof(int16_t)) + return -1; + tot_bytes += rc; + + /* Read the buffer size */ + bytes_to_recv = sizeof(buffer_size); + rc = recvall(sock, (char *)&buffer_size, &bytes_to_recv, socket_timeout); + if (rc <= 0 || bytes_to_recv != sizeof(buffer_size)) + return -1; + tot_bytes += rc; + + buffer_size = ntohl(buffer_size); + pkt_size += buffer_size; + if ((*v3_pkt = calloc(1, pkt_size)) == NULL) { + logit(LOG_ERR, "Error: (use_ssl == false): Could not allocate memory for packet"); + return -1; + } + + memcpy(*v3_pkt, v2_pkt, common_size); + (*v3_pkt)->buffer_length = htonl(buffer_size); + buff_ptr = (*v3_pkt)->buffer; + } + + bytes_to_recv = buffer_size; + rc = recvall(sock, buff_ptr, &bytes_to_recv, socket_timeout); + + if (rc <= 0 || rc != buffer_size) { + if (packet_ver == NRPE_PACKET_VERSION_3) { + free(*v3_pkt); + *v3_pkt = NULL; + } + return -1; + } else + tot_bytes += rc; + } +#ifdef HAVE_SSL + else { + SSL *ssl = (SSL *) ssl_ptr; + + while (((rc = SSL_read(ssl, v2_pkt, bytes_to_recv)) <= 0) + && (SSL_get_error(ssl, rc) == SSL_ERROR_WANT_READ)) { + } + + if (rc <= 0 || rc != bytes_to_recv) + return -1; + + packet_ver = ntohs(v2_pkt->packet_version); + if (packet_ver != NRPE_PACKET_VERSION_2 && packet_ver != NRPE_PACKET_VERSION_3) { + logit(LOG_ERR, "Error: (use_ssl == true): Request packet version was invalid!"); + return -1; + } + + if (packet_ver == NRPE_PACKET_VERSION_2) { + buffer_size = sizeof(v2_packet) - common_size; + buff_ptr = (char *)v2_pkt + common_size; + } else { + int32_t pkt_size = sizeof(v3_packet) - 1; + + /* Read the alignment filler */ + bytes_to_recv = sizeof(int16_t); + while (((rc = SSL_read(ssl, &buffer_size, bytes_to_recv)) <= 0) + && (SSL_get_error(ssl, rc) == SSL_ERROR_WANT_READ)) { + } + + if (rc <= 0 || bytes_to_recv != sizeof(int16_t)) + return -1; + tot_bytes += rc; + + /* Read the buffer size */ + bytes_to_recv = sizeof(buffer_size); + while (((rc = SSL_read(ssl, &buffer_size, bytes_to_recv)) <= 0) + && (SSL_get_error(ssl, rc) == SSL_ERROR_WANT_READ)) { + } + + if (rc <= 0 || bytes_to_recv != sizeof(buffer_size)) + return -1; + tot_bytes += rc; + + buffer_size = ntohl(buffer_size); + pkt_size += buffer_size; + if ((*v3_pkt = calloc(1, pkt_size)) == NULL) { + logit(LOG_ERR, "Error: (use_ssl == true): Could not allocate memory for packet"); + return -1; + } + + memcpy(*v3_pkt, v2_pkt, common_size); + (*v3_pkt)->buffer_length = htonl(buffer_size); + buff_ptr = (*v3_pkt)->buffer; + } + + bytes_to_recv = buffer_size; + while (((rc = SSL_read(ssl, buff_ptr, bytes_to_recv)) <= 0) + && (SSL_get_error(ssl, rc) == SSL_ERROR_WANT_READ)) { + } + + if (rc <= 0 || rc != buffer_size) { + if (packet_ver == NRPE_PACKET_VERSION_3) { + free(*v3_pkt); + *v3_pkt = NULL; + } + return -1; + } else + tot_bytes += rc; + } +#endif + + return tot_bytes; +} + +/* free all allocated memory */ +void free_memory(void) +{ + command *this_command; + command *next_command; + + /* free memory for the command list */ + this_command = command_list; + while (this_command != NULL) { + next_command = this_command->next; + if (this_command->command_name) + free(this_command->command_name); + if (this_command->command_line) + free(this_command->command_line); + free(this_command); + this_command = next_command; + } + + command_list = NULL; + return; +} + +/* executes a system command via popen(), but protects against timeouts */ +int my_system(char *command, int timeout, int *early_timeout, char **output) +{ + FILE *fp; + pid_t pid; + time_t start_time, end_time; + int status; + int result; + char buffer[MAX_INPUT_BUFFER]; + int fd[2]; + int bytes_read = 0, tot_bytes = 0; + int output_size; +#ifdef HAVE_SIGACTION + struct sigaction sig_action; +#endif + + *early_timeout = FALSE; /* initialize return variables */ + + if (command == NULL) /* if no command was passed, return with no error */ + return STATE_OK; + + /* make sure that we are within max_commands boundaries before attempting */ + if (max_commands != 0) { + while (commands_running >= max_commands) { + logit(LOG_WARNING, "Commands choked. Sleeping 1s - commands_running: %d, max_commands: %d", commands_running, max_commands); + sleep(1); + } + } + + /* create a pipe */ + if (pipe(fd) == -1) { + logit(LOG_ERR, "ERROR: pipe(): %s, bailing out...", strerror(errno)); + exit(STATE_CRITICAL); + } + + /* make the pipe non-blocking */ + fcntl(fd[0], F_SETFL, O_NONBLOCK); + fcntl(fd[1], F_SETFL, O_NONBLOCK); + + time(&start_time); /* get the command start time */ + + pid = fork(); /* fork */ + + /* return an error if we couldn't fork */ + if (pid == -1) { + snprintf(buffer, sizeof(buffer) - 1, "NRPE: Call to fork() failed\n"); + buffer[sizeof(buffer) - 1] = '\x0'; + + if (packet_ver == NRPE_PACKET_VERSION_2) { + int output_size = sizeof(v2_packet); + *output = calloc(1, output_size); + strncpy(*output, buffer, output_size - 1); + *output[output_size - 1] = '\0'; + } else + *output = strdup(buffer); + + /* close both ends of the pipe */ + close(fd[0]); + close(fd[1]); + + return STATE_UNKNOWN; + } + + /* execute the command in the child process */ + if (pid == 0) { + + /* get root back so the next call works correctly */ + if (SETEUID(0) == -1 && debug) + logit(LOG_WARNING, "WARNING: my_system() seteuid(0): %s", strerror(errno)); + + drop_privileges(nrpe_user, nrpe_group, 1); /* drop privileges */ + close(fd[0]); /* close pipe for reading */ + setpgid(0, 0); /* become process group leader */ + + /* trap commands that timeout */ +#ifdef HAVE_SIGACTION + sig_action.sa_sigaction = NULL; + sig_action.sa_handler = my_system_sighandler; + sigfillset(&sig_action.sa_mask); + sig_action.sa_flags = SA_NODEFER | SA_RESTART; + sigaction(SIGALRM, &sig_action, NULL); +#else + signal(SIGALRM, my_system_sighandler); +#endif /* HAVE_SIGACTION */ + alarm(timeout); + + fp = popen(command, "r"); /* run the command */ + + /* report an error if we couldn't run the command */ + if (fp == NULL) { + strncpy(buffer, "NRPE: Call to popen() failed\n", sizeof(buffer) - 1); + buffer[sizeof(buffer) - 1] = '\x0'; + + /* write the error back to the parent process */ + if (write(fd[1], buffer, strlen(buffer) + 1) == -1) + logit(LOG_ERR, "ERROR: my_system() write(fd, buffer)-1 failed..."); + + result = STATE_CRITICAL; + + } else { + + /* read all lines of output - supports Nagios 3.x multiline output */ + while ((bytes_read = fread(buffer, 1, sizeof(buffer) - 1, fp)) > 0) { + /* write the output back to the parent process */ + if (write(fd[1], buffer, bytes_read) == -1) + logit(LOG_ERR, "ERROR: my_system() write(fd, buffer)-2 failed..."); + } + + if (write(fd[1], "\0", 1) == -1) + logit(LOG_ERR, "ERROR: my_system() write(fd, NULL) failed..."); + + status = pclose(fp); /* close the command and get termination status */ + + /* report an error if we couldn't close the command */ + if (status == -1) + result = STATE_CRITICAL; + else if (!WIFEXITED(status)) + /* report an error if child died due to signal (Klas Lindfors) */ + result = STATE_CRITICAL; + else + result = WEXITSTATUS(status); + } + + close(fd[1]); /* close pipe for writing */ + alarm(0); /* reset the alarm */ + exit(result); /* return plugin exit code to parent process */ + + } else { + /* parent waits for child to finish executing command */ + + commands_running++; + + close(fd[1]); /* close pipe for writing */ + waitpid(pid, &status, 0); /* wait for child to exit */ + time(&end_time); /* get the end time for running the command */ + result = WEXITSTATUS(status); /* get the exit code returned from the program */ + + /* because of my idiotic idea of having UNKNOWN states be equivalent to -1, I must hack things a bit... */ + if (result == 255) + result = STATE_UNKNOWN; + + /* check bounds on the return value */ + if (result < 0 || result > 3) + result = STATE_UNKNOWN; + + if (packet_ver == NRPE_PACKET_VERSION_2) { + output_size = sizeof(v2_packet); + *output = calloc(1, output_size); + } else { + output_size = 1024 * 64; /* Maximum buffer is 64K */ + *output = calloc(1, output_size); + } + + /* try and read the results from the command output (retry if we encountered a signal) */ + for (;;) { + bytes_read = read(fd[0], buffer, sizeof(buffer) - 1); + if (bytes_read == 0) + break; + if (bytes_read == -1) { + if (errno == EINTR) + continue; + else + break; + } + if (tot_bytes < output_size) /* If buffer is full, discard the rest */ + strncat(*output, buffer, output_size - tot_bytes - 1); + tot_bytes += bytes_read; + } + + (*output)[output_size - 1] = '\0'; + + /* if there was a critical return code and no output AND the + * command time exceeded the timeout thresholds, assume a timeout */ + if (result == STATE_CRITICAL && bytes_read == -1 && (end_time - start_time) >= timeout) { + *early_timeout = TRUE; + + /* send termination signal to child process group */ + kill((pid_t) (-pid), SIGTERM); + kill((pid_t) (-pid), SIGKILL); + } + + close(fd[0]); /* close the pipe for reading */ + + commands_running--; + } + +#ifdef DEBUG + printf("my_system() end\n"); +#endif + + return result; +} + +/* handle timeouts when executing commands via my_system() */ +void my_system_sighandler(int sig) +{ + exit(STATE_CRITICAL); /* force the child process to exit... */ +} + +/* handle errors where connection takes too long */ +void my_connection_sighandler(int sig) +{ + logit(LOG_ERR, "Connection has taken too long to establish. Exiting..."); + exit(STATE_CRITICAL); +} + +/* drops privileges */ +int drop_privileges(char *user, char *group, int full_drop) +{ + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + struct group *grp; + struct passwd *pw; + + if (use_inetd == TRUE) + return OK; + + /* set effective group ID */ + if (group != NULL) { + + /* see if this is a group name */ + if (strspn(group, "0123456789") < strlen(group)) { + grp = (struct group *)getgrnam(group); + if (grp != NULL) + gid = (gid_t) (grp->gr_gid); + else + logit(LOG_ERR, "Warning: Could not get group entry for '%s'", group); + endgrent(); + + } else + /* else we were passed the GID */ + gid = (gid_t) atoi(group); + + /* set effective group ID if other than current EGID */ + if (gid != getegid()) { + if (setgid(gid) == -1) + logit(LOG_ERR, "Warning: Could not set effective GID=%d", (int)gid); + } + } + + + /* set effective user ID */ + if (user != NULL) { + + /* see if this is a user name */ + if (strspn(user, "0123456789") < strlen(user)) { + pw = (struct passwd *)getpwnam(user); + if (pw != NULL) + uid = (uid_t) (pw->pw_uid); + else + logit(LOG_ERR, "Warning: Could not get passwd entry for '%s'", user); + endpwent(); + + } else + /* else we were passed the UID */ + uid = (uid_t) atoi(user); + + if (uid != geteuid()) { + /* set effective user ID if other than current EUID */ +#ifdef HAVE_INITGROUPS + /* initialize supplementary groups */ + if (initgroups(user, gid) == -1) { + if (errno == EPERM) + logit(LOG_ERR, "Warning: Unable to change supplementary groups using initgroups()"); + else { + logit(LOG_ERR, "Warning: Possibly root user failed dropping privileges with initgroups()"); + return ERROR; + } + } +#endif + + if (full_drop) { + if (setuid(uid) == -1) + logit(LOG_ERR, "Warning: Could not set UID=%d", (int)uid); + } else if (SETEUID(uid) == -1) + logit(LOG_ERR, "Warning: Could not set effective UID=%d", (int)uid); + } + } + + return OK; +} + +/* write an optional pid file */ +int write_pid_file(void) +{ + int fd; + int result = 0; + pid_t pid = 0; + char pbuf[16]; + + /* no pid file was specified */ + if (pid_file == NULL) + return OK; + + /* read existing pid file */ + if ((fd = open(pid_file, O_RDONLY)) >= 0) { + + result = read(fd, pbuf, (sizeof pbuf) - 1); + close(fd); + + if (result > 0) { + pbuf[result] = '\x0'; + pid = (pid_t) atoi(pbuf); + + /* if previous process is no longer running running, remove the old pid file */ + if (pid && (pid == getpid() || kill(pid, 0) < 0)) + unlink(pid_file); + + else { + /* previous process is still running */ + logit(LOG_ERR, "There's already an NRPE server running (PID %lu). Bailing out...", (unsigned long)pid); + return ERROR; + } + } + } + + /* write new pid file */ + if ((fd = open(pid_file, O_WRONLY | O_CREAT, 0644)) >= 0) { + sprintf(pbuf, "%d\n", (int)getpid()); + + if (write(fd, pbuf, strlen(pbuf)) == -1) + logit(LOG_ERR, "ERROR: write_pid_file() write(fd, pbuf) failed..."); + + close(fd); + wrote_pid_file = TRUE; + } else { + logit(LOG_ERR, "Cannot write to pidfile '%s' - check your privileges.", pid_file); + return ERROR; + } + + return OK; +} + +/* remove pid file */ +int remove_pid_file(void) +{ + if (pid_file == NULL) + return OK; /* no pid file was specified */ + if (wrote_pid_file == FALSE) + return OK; /* pid file was not written */ + + /* get root back so we can delete the pid file */ + if (SETEUID(0) == -1 && debug) + logit(LOG_WARNING, "WARNING: remove_pid_file() seteuid(0): %s", strerror(errno)); + + if (unlink(pid_file) == -1) { + logit(LOG_ERR, "Cannot remove pidfile '%s' - check your privileges.", pid_file); + return ERROR; + } + + return OK; +} + +#ifdef HAVE_SSL + +void my_disconnect_sighandler(int sig) +{ + logit(LOG_ERR, "SSL_shutdown() has taken too long to complete. Exiting now.."); + exit(STATE_CRITICAL); +} + +void complete_SSL_shutdown(SSL * ssl) +{ + /* Thanks to Jari Takkala (jtakkala@gmail.com) for the following information. + + We need to call SSL_shutdown() at least twice, otherwise we'll + be left with data in the socket receive buffer, and the + subsequent process termination will cause TCP RST's to be sent + to the client. + + See http://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/32219/diff + for more information. + */ + + int x; + + /* set disconnection handler */ + signal(SIGALRM, my_disconnect_sighandler); + alarm(ssl_shutdown_timeout); + + for (x = 0; x < 4; x++) { + if (SSL_shutdown(ssl)) + break; + } + + alarm(0); +} +#endif /*HAVE_SSL */ + +/* bail if daemon is running as root */ +int check_privileges(void) +{ + uid_t uid = geteuid(); + gid_t gid = getegid(); + + if (uid == 0 || gid == 0) { + logit(LOG_ERR, "Error: NRPE daemon cannot be run as user/group root!"); + exit(STATE_CRITICAL); + } + + return OK; +} + +/* handle signals (parent process) */ +void sighandler(int sig) +{ + static char *sigs[] = { + "EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS", "FPE", + "KILL", "USR1", "SEGV", "USR2", "PIPE", "ALRM", "TERM", "STKFLT", + "CHLD", "CONT", "STOP", "TSTP", "TTIN", "TTOU", "URG", "XCPU", + "XFSZ", "VTALRM", "PROF", "WINCH", "IO", "PWR", "UNUSED", "ZERR", + "DEBUG", (char *)NULL + }; + int i; + + if (sig < 0) + sig = -sig; + + for (i = 0; sigs[i] != (char *)NULL; i++) ; + sig %= i; + + /* we received a SIGHUP, so restart... */ + if (sig == SIGHUP) { + sigrestart = TRUE; + logit(LOG_NOTICE, "Caught SIGHUP - restarting...\n"); + } + + /* else begin shutting down... */ + if (sig == SIGTERM) { + /* if shutdown is already true, we're in a signal trap loop! */ + if (sigshutdown == TRUE) + exit(STATE_CRITICAL); + sigshutdown = TRUE; + logit(LOG_NOTICE, "Caught SIG%s - shutting down...\n", sigs[sig]); + } + + return; +} + +/* handle signals (child processes) */ +void child_sighandler(int sig) +{ + exit(0); /* terminate */ +} + +/* tests whether or not a client request is valid */ +int validate_request(v2_packet * v2pkt, v3_packet * v3pkt) +{ + u_int32_t packet_crc32; + u_int32_t calculated_crc32; + char *buff, *ptr; + int rc; +#ifdef ENABLE_COMMAND_ARGUMENTS + int x; +#endif + + /* check the crc 32 value */ + if (packet_ver == NRPE_PACKET_VERSION_3) { + int32_t pkt_size = (sizeof(v3_packet) - 1) + ntohl(v3pkt->buffer_length); + packet_crc32 = ntohl(v3pkt->crc32_value); + v3pkt->crc32_value = 0L; + v3pkt->alignment = 0; + calculated_crc32 = calculate_crc32((char *)v3pkt, pkt_size); + } else { + packet_crc32 = ntohl(v2pkt->crc32_value); + v2pkt->crc32_value = 0L; + calculated_crc32 = calculate_crc32((char *)v2pkt, sizeof(v2_packet)); + } + + if (packet_crc32 != calculated_crc32) { + logit(LOG_ERR, "Error: Request packet had invalid CRC32."); + return ERROR; + } + + /* make sure this is the right type of packet */ + if (ntohs(v2pkt->packet_type) != QUERY_PACKET) { + logit(LOG_ERR, "Error: Request packet type was invalid!"); + return ERROR; + } + + /* make sure buffer is terminated */ + if (packet_ver == NRPE_PACKET_VERSION_3) { + int32_t l = ntohs(v3pkt->buffer_length); + v3pkt->buffer[l - 1] = '\x0'; + buff = v3pkt->buffer; + } else { + v2pkt->buffer[MAX_PACKETBUFFER_LENGTH - 1] = '\x0'; + buff = v2pkt->buffer; + } + + /* client must send some kind of request */ + if (buff[0] == '\0') { + logit(LOG_ERR, "Error: Request contained no query!"); + return ERROR; + } + + /* make sure request doesn't contain nasties */ + if (packet_ver == NRPE_PACKET_VERSION_3) + rc = contains_nasty_metachars(v3pkt->buffer); + else + rc = contains_nasty_metachars(v2pkt->buffer); + if (rc == TRUE) { + logit(LOG_ERR, "Error: Request contained illegal metachars!"); + return ERROR; + } + + /* make sure the request doesn't contain arguments */ + if (strchr(v2pkt->buffer, '!')) { +#ifdef ENABLE_COMMAND_ARGUMENTS + if (allow_arguments == FALSE) { + logit(LOG_ERR, "Error: Request contained command arguments, but argument option is not enabled!"); + return ERROR; + } +#else + logit(LOG_ERR, "Error: Request contained command arguments!"); + return ERROR; +#endif + } + + /* get command name */ +#ifdef ENABLE_COMMAND_ARGUMENTS + ptr = strtok(buff, "!"); +#else + ptr = buff; +#endif + command_name = strdup(ptr); + if (command_name == NULL) { + logit(LOG_ERR, "Error: Memory allocation failed"); + return ERROR; + } +#ifdef ENABLE_COMMAND_ARGUMENTS + /* get command arguments */ + if (allow_arguments == TRUE) { + + for (x = 0; x < MAX_COMMAND_ARGUMENTS; x++) { + ptr = strtok(NULL, "!"); + if (ptr == NULL) + break; + macro_argv[x] = strdup(ptr); + if (macro_argv[x] == NULL) { + logit(LOG_ERR, "Error: Memory allocation failed"); + return ERROR; + } + if (!strcmp(macro_argv[x], "")) { + logit(LOG_ERR, "Error: Request contained an empty command argument"); + return ERROR; + } + if (strstr(macro_argv[x], "$(")) { +# ifndef ENABLE_BASH_COMMAND_SUBSTITUTION + logit(LOG_ERR, "Error: Request contained a bash command substitution!"); + return ERROR; +# else + if (FALSE == allow_bash_cmd_subst) { + logit(LOG_ERR, "Error: Request contained a bash command substitution, but they are disallowed!"); + return ERROR; + } +# endif + } + } + } +#endif + return OK; +} + +/* tests whether a buffer contains illegal metachars */ +int contains_nasty_metachars(char *str) +{ + int result; + + if (str == NULL) + return FALSE; + + result = strcspn(str, nasty_metachars); + if (result != strlen(str)) + return TRUE; + + return FALSE; +} + +/* replace macros in buffer */ +int process_macros(char *input_buffer, char *output_buffer, int buffer_length) +{ + char *temp_buffer; + int in_macro; + int arg_index = 0; + char *selected_macro = NULL; + + strcpy(output_buffer, ""); + + in_macro = FALSE; + + for (temp_buffer = my_strsep(&input_buffer, "$"); temp_buffer != NULL; + temp_buffer = my_strsep(&input_buffer, "$")) { + + selected_macro = NULL; + + if (in_macro == FALSE) { + if (strlen(output_buffer) + strlen(temp_buffer) < buffer_length - 1) { + strncat(output_buffer, temp_buffer, buffer_length - strlen(output_buffer) - 1); + output_buffer[buffer_length - 1] = '\x0'; + } + in_macro = TRUE; + + } else { + + if (strlen(output_buffer) + strlen(temp_buffer) < buffer_length - 1) { + + /* argument macro */ + if (strstr(temp_buffer, "ARG") == temp_buffer) { + arg_index = atoi(temp_buffer + 3); + if (arg_index >= 1 && arg_index <= MAX_COMMAND_ARGUMENTS) + selected_macro = macro_argv[arg_index - 1]; + + } else if (!strcmp(temp_buffer, "")) { + /* an escaped $ is done by specifying two $$ next to each other */ + strncat(output_buffer, "$", buffer_length - strlen(output_buffer) - 1); + + } else { + /* a non-macro, just some user-defined string between two $s */ + strncat(output_buffer, "$", buffer_length - strlen(output_buffer) - 1); + output_buffer[buffer_length - 1] = '\x0'; + strncat(output_buffer, temp_buffer, + buffer_length - strlen(output_buffer) - 1); + output_buffer[buffer_length - 1] = '\x0'; + strncat(output_buffer, "$", buffer_length - strlen(output_buffer) - 1); + } + + + /* insert macro */ + if (selected_macro != NULL) + strncat(output_buffer, (selected_macro == NULL) ? "" : selected_macro, + buffer_length - strlen(output_buffer) - 1); + + output_buffer[buffer_length - 1] = '\x0'; + } + + in_macro = FALSE; + } + } + + return OK; +} + +/* process command line arguments */ +int process_arguments(int argc, char **argv) +{ + char optchars[MAX_INPUT_BUFFER]; + int c = 1; + int have_mode = FALSE; +#ifdef HAVE_GETOPT_LONG + int option_index = 0; + static struct option long_options[] = { + {"config", required_argument, 0, 'c'}, + {"inetd", no_argument, 0, 'i'}, + /* To compatibility between short and long options but not used on AIX */ + {"src", no_argument, 0, 's'}, + {"no-forking", no_argument, 0, 'f'}, + {"4", no_argument, 0, '4'}, + {"ipv6", no_argument, 0, '6'}, + {"daemon", no_argument, 0, 'd'}, + {"no-ssl", no_argument, 0, 'n'}, + {"help", no_argument, 0, 'h'}, + {"license", no_argument, 0, 'l'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0} + }; +#endif + + /* no options were supplied */ + if (argc < 2) + return ERROR; + + snprintf(optchars, MAX_INPUT_BUFFER, "c:hVldi46nsf"); + + while (1) { +#ifdef HAVE_GETOPT_LONG + c = getopt_long(argc, argv, optchars, long_options, &option_index); +#else + c = getopt(argc, argv, optchars); +#endif + if (c == -1 || c == EOF) + break; + + /* process all arguments */ + switch (c) { + + case '?': + case 'h': + show_help = TRUE; + break; + + case 'V': + show_version = TRUE; + have_mode = TRUE; + break; + + case 'l': + show_license = TRUE; + break; + + case 'c': + strncpy(config_file, optarg, sizeof(config_file)); + config_file[sizeof(config_file) - 1] = '\x0'; + break; + + case 'd': + use_inetd = FALSE; + have_mode = TRUE; + break; + + case 'i': + use_inetd = TRUE; + have_mode = TRUE; + break; + + case '4': + address_family = AF_INET; + break; + + case '6': + address_family = AF_INET6; + break; + + case 'n': + use_ssl = FALSE; + break; + + case 's': /* Argument s to indicate SRC option */ + use_src = TRUE; + have_mode = TRUE; + break; + + case 'f': + use_inetd = FALSE; + no_forking = TRUE; + have_mode = TRUE; + break; + + default: + return ERROR; + } + } + + /* bail if we didn't get required args */ + if (have_mode == FALSE) + return ERROR; + + return OK; +} diff --git a/src/snprintf.c b/src/snprintf.c new file mode 100644 index 0000000..3a5d672 --- /dev/null +++ b/src/snprintf.c @@ -0,0 +1,1452 @@ +/* + * NOTE: If you change this file, please merge it into rsync, samba, etc. + */ + +/* + * Copyright Patrick Powell 1995 + * This code is based on code written by Patrick Powell (papowell@astart.com) + * It may be used for any purpose as long as this notice remains intact + * on all source code distributions + */ + +/************************************************************** + * Original: + * Patrick Powell Tue Apr 11 09:48:21 PDT 1995 + * A bombproof version of doprnt (dopr) included. + * Sigh. This sort of thing is always nasty do deal with. Note that + * the version here does not include floating point... + * + * snprintf() is used instead of sprintf() as it does limit checks + * for string length. This covers a nasty loophole. + * + * The other functions are there to prevent NULL pointers from + * causing nast effects. + * + * More Recently: + * Brandon Long 9/15/96 for mutt 0.43 + * This was ugly. It is still ugly. I opted out of floating point + * numbers, but the formatter understands just about everything + * from the normal C string format, at least as far as I can tell from + * the Solaris 2.5 printf(3S) man page. + * + * Brandon Long 10/22/97 for mutt 0.87.1 + * Ok, added some minimal floating point support, which means this + * probably requires libm on most operating systems. Don't yet + * support the exponent (e,E) and sigfig (g,G). Also, fmtint() + * was pretty badly broken, it just wasn't being exercised in ways + * which showed it, so that's been fixed. Also, formated the code + * to mutt conventions, and removed dead code left over from the + * original. Also, there is now a builtin-test, just compile with: + * gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm + * and run snprintf for results. + * + * Thomas Roessler 01/27/98 for mutt 0.89i + * The PGP code was using unsigned hexadecimal formats. + * Unfortunately, unsigned formats simply didn't work. + * + * Michael Elkins 03/05/98 for mutt 0.90.8 + * The original code assumed that both snprintf() and vsnprintf() were + * missing. Some systems only have snprintf() but not vsnprintf(), so + * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF. + * + * Andrew Tridgell (tridge@samba.org) Oct 1998 + * fixed handling of %.0f + * added test for HAVE_LONG_DOUBLE + * + * tridge@samba.org, idra@samba.org, April 2001 + * got rid of fcvt code (twas buggy and made testing harder) + * added C99 semantics + * + * date: 2002/12/19 19:56:31; author: herb; state: Exp; lines: +2 -0 + * actually print args for %g and %e + * + * date: 2002/06/03 13:37:52; author: jmcd; state: Exp; lines: +8 -0 + * Since includes.h isn't included here, VA_COPY has to be defined here. I don't + * see any include file that is guaranteed to be here, so I'm defining it + * locally. Fixes AIX and Solaris builds. + * + * date: 2002/06/03 03:07:24; author: tridge; state: Exp; lines: +5 -13 + * put the ifdef for HAVE_VA_COPY in one place rather than in lots of + * functions + * + * date: 2002/05/17 14:51:22; author: jmcd; state: Exp; lines: +21 -4 + * Fix usage of va_list passed as an arg. Use __va_copy before using it + * when it exists. + * + * date: 2002/04/16 22:38:04; author: idra; state: Exp; lines: +20 -14 + * Fix incorrect zpadlen handling in fmtfp. + * Thanks to Ollie Oldham for spotting it. + * few mods to make it easier to compile the tests. + * added the "Ollie" test to the floating point ones. + * + * Martin Pool (mbp@samba.org) April 2003 + * Remove NO_CONFIG_H so that the test case can be built within a source + * tree with less trouble. + * Remove unnecessary SAFE_FREE() definition. + * + * Martin Pool (mbp@samba.org) May 2003 + * Put in a prototype for dummy_snprintf() to quiet compiler warnings. + * + * Move #endif to make sure VA_COPY, LDOUBLE, etc are defined even + * if the C library has some snprintf functions already. + * + * Darren Tucker (dtucker@zip.com.au) 2005 + * Fix bug allowing read overruns of the source string with "%.*s" + * Usually harmless unless the read runs outside the process' allocation + * (eg if your malloc does guard pages) in which case it will segfault. + * From OpenSSH. Also added test for same. + * + * Simo Sorce (idra@samba.org) Jan 2006 + * + * Add support for position independent parameters + * fix fmtstr now it conforms to sprintf wrt min.max + * + **************************************************************/ + +#ifndef NO_CONFIG_H +/* 08/13/2007 EG changed path to config.h to match NRPE distro */ +#include "../include/config.h" +#else +#define NULL 0 +#endif + +#ifdef TEST_SNPRINTF /* need math library headers for testing */ + +/* In test mode, we pretend that this system doesn't have any snprintf + * functions, regardless of what config.h says. */ +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +# undef HAVE_C99_VSNPRINTF +# undef HAVE_ASPRINTF +# undef HAVE_VASPRINTF +# include +#endif /* TEST_SNPRINTF */ + +#ifdef HAVE_STRING_H +#include +#endif + +#ifdef HAVE_STRINGS_H +#include +#endif +#ifdef HAVE_CTYPE_H +#include +#endif +#include +#include +#ifdef HAVE_STDLIB_H +#include +#endif + +#if defined(HAVE_SNPRINTF) && defined(HAVE_VSNPRINTF) && defined(HAVE_C99_VSNPRINTF) +/* only include stdio.h if we are not re-defining snprintf or vsnprintf */ +#include + /* make the compiler happy with an empty file */ + void dummy_snprintf(void); + void dummy_snprintf(void) {} +#endif /* HAVE_SNPRINTF, etc */ + +#ifdef HAVE_LONG_DOUBLE +#define LDOUBLE long double +#else +#define LDOUBLE double +#endif + +#ifdef HAVE_LONG_LONG +#define LLONG long long +#else +#define LLONG long +#endif + +#ifndef VA_COPY +#ifdef HAVE_VA_COPY +#define VA_COPY(dest, src) va_copy(dest, src) +#else +#ifdef HAVE___VA_COPY +#define VA_COPY(dest, src) __va_copy(dest, src) +#else +#define VA_COPY(dest, src) (dest) = (src) +#endif +#endif + +/* + * dopr(): poor man's version of doprintf + */ + +/* format read states */ +#define DP_S_DEFAULT 0 +#define DP_S_FLAGS 1 +#define DP_S_MIN 2 +#define DP_S_DOT 3 +#define DP_S_MAX 4 +#define DP_S_MOD 5 +#define DP_S_CONV 6 +#define DP_S_DONE 7 + +/* format flags - Bits */ +#define DP_F_MINUS (1 << 0) +#define DP_F_PLUS (1 << 1) +#define DP_F_SPACE (1 << 2) +#define DP_F_NUM (1 << 3) +#define DP_F_ZERO (1 << 4) +#define DP_F_UP (1 << 5) +#define DP_F_UNSIGNED (1 << 6) + +/* Conversion Flags */ +#define DP_C_CHAR 1 +#define DP_C_SHORT 2 +#define DP_C_LONG 3 +#define DP_C_LDOUBLE 4 +#define DP_C_LLONG 5 + +/* Chunk types */ +#define CNK_FMT_STR 0 +#define CNK_INT 1 +#define CNK_OCTAL 2 +#define CNK_UINT 3 +#define CNK_HEX 4 +#define CNK_FLOAT 5 +#define CNK_CHAR 6 +#define CNK_STRING 7 +#define CNK_PTR 8 +#define CNK_NUM 9 +#define CNK_PRCNT 10 + +#define char_to_int(p) ((p)- '0') +#ifndef MAX +#define MAX(p,q) (((p) >= (q)) ? (p) : (q)) +#endif + +/* yes this really must be a ||. Don't muck with this (tridge) */ +#if !defined(HAVE_VSNPRINTF) || !defined(HAVE_C99_VSNPRINTF) + +struct pr_chunk { + int type; /* chunk type */ + int num; /* parameter number */ + int min; + int max; + int flags; + int cflags; + int start; + int len; + LLONG value; + LDOUBLE fvalue; + char *strvalue; + void *pnum; + struct pr_chunk *min_star; + struct pr_chunk *max_star; + struct pr_chunk *next; +}; + +struct pr_chunk_x { + struct pr_chunk **chunks; + int num; +}; + +static size_t dopr(char *buffer, size_t maxlen, const char *format, + va_list args_in); +static void fmtstr(char *buffer, size_t *currlen, size_t maxlen, + char *value, int flags, int min, int max); +static void fmtint(char *buffer, size_t *currlen, size_t maxlen, + long value, int base, int min, int max, int flags); +static void fmtfp(char *buffer, size_t *currlen, size_t maxlen, + LDOUBLE fvalue, int min, int max, int flags); +static void dopr_outch(char *buffer, size_t *currlen, size_t maxlen, char c); +static struct pr_chunk *new_chunk(void); +static int add_cnk_list_entry(struct pr_chunk_x **list, + int max_num, struct pr_chunk *chunk); + +static size_t dopr(char *buffer, size_t maxlen, const char *format, va_list args_in) +{ + char ch; + int state; + int pflag; + int pnum; + int pfirst; + size_t currlen; + va_list args; + const char *base; + struct pr_chunk *chunks = NULL; + struct pr_chunk *cnk = NULL; + struct pr_chunk_x *clist = NULL; + int max_pos; + size_t ret = (size_t)-1; + + VA_COPY(args, args_in); + + state = DP_S_DEFAULT; + pfirst = 1; + pflag = 0; + pnum = 0; + + max_pos = 0; + base = format; + ch = *format++; + + /* retrieve the string structure as chunks */ + while (state != DP_S_DONE) { + if (ch == '\0') + state = DP_S_DONE; + + switch(state) { + case DP_S_DEFAULT: + + if (cnk) { + cnk->next = new_chunk(); + cnk = cnk->next; + } else { + cnk = new_chunk(); + } + if (!cnk) goto done; + if (!chunks) chunks = cnk; + + if (ch == '%') { + state = DP_S_FLAGS; + ch = *format++; + } else { + cnk->type = CNK_FMT_STR; + cnk->start = format - base -1; + while ((ch != '\0') && (ch != '%')) ch = *format++; + cnk->len = format - base - cnk->start -1; + } + break; + case DP_S_FLAGS: + switch (ch) { + case '-': + cnk->flags |= DP_F_MINUS; + ch = *format++; + break; + case '+': + cnk->flags |= DP_F_PLUS; + ch = *format++; + break; + case ' ': + cnk->flags |= DP_F_SPACE; + ch = *format++; + break; + case '#': + cnk->flags |= DP_F_NUM; + ch = *format++; + break; + case '0': + cnk->flags |= DP_F_ZERO; + ch = *format++; + break; + case 'I': + /* internationalization not supported yet */ + ch = *format++; + break; + default: + state = DP_S_MIN; + break; + } + break; + case DP_S_MIN: + if (isdigit((unsigned char)ch)) { + cnk->min = 10 * cnk->min + char_to_int (ch); + ch = *format++; + } else if (ch == '$') { + if (!pfirst && !pflag) { + /* parameters must be all positioned or none */ + goto done; + } + if (pfirst) { + pfirst = 0; + pflag = 1; + } + if (cnk->min == 0) /* what ?? */ + goto done; + cnk->num = cnk->min; + cnk->min = 0; + ch = *format++; + } else if (ch == '*') { + if (pfirst) pfirst = 0; + cnk->min_star = new_chunk(); + if (!cnk->min_star) /* out of memory :-( */ + goto done; + cnk->min_star->type = CNK_INT; + if (pflag) { + int num; + ch = *format++; + if (!isdigit((unsigned char)ch)) { + /* parameters must be all positioned or none */ + goto done; + } + for (num = 0; isdigit((unsigned char)ch); ch = *format++) { + num = 10 * num + char_to_int(ch); + } + cnk->min_star->num = num; + if (ch != '$') /* what ?? */ + goto done; + } else { + cnk->min_star->num = ++pnum; + } + max_pos = add_cnk_list_entry(&clist, max_pos, cnk->min_star); + if (max_pos == 0) /* out of memory :-( */ + goto done; + ch = *format++; + state = DP_S_DOT; + } else { + if (pfirst) pfirst = 0; + state = DP_S_DOT; + } + break; + case DP_S_DOT: + if (ch == '.') { + state = DP_S_MAX; + ch = *format++; + } else { + state = DP_S_MOD; + } + break; + case DP_S_MAX: + if (isdigit((unsigned char)ch)) { + if (cnk->max < 0) + cnk->max = 0; + cnk->max = 10 * cnk->max + char_to_int (ch); + ch = *format++; + } else if (ch == '$') { + if (!pfirst && !pflag) { + /* parameters must be all positioned or none */ + goto done; + } + if (cnk->max <= 0) /* what ?? */ + goto done; + cnk->num = cnk->max; + cnk->max = -1; + ch = *format++; + } else if (ch == '*') { + cnk->max_star = new_chunk(); + if (!cnk->max_star) /* out of memory :-( */ + goto done; + cnk->max_star->type = CNK_INT; + if (pflag) { + int num; + ch = *format++; + if (!isdigit((unsigned char)ch)) { + /* parameters must be all positioned or none */ + goto done; + } + for (num = 0; isdigit((unsigned char)ch); ch = *format++) { + num = 10 * num + char_to_int(ch); + } + cnk->max_star->num = num; + if (ch != '$') /* what ?? */ + goto done; + } else { + cnk->max_star->num = ++pnum; + } + max_pos = add_cnk_list_entry(&clist, max_pos, cnk->max_star); + if (max_pos == 0) /* out of memory :-( */ + goto done; + + ch = *format++; + state = DP_S_MOD; + } else { + state = DP_S_MOD; + } + break; + case DP_S_MOD: + switch (ch) { + case 'h': + cnk->cflags = DP_C_SHORT; + ch = *format++; + if (ch == 'h') { + cnk->cflags = DP_C_CHAR; + ch = *format++; + } + break; + case 'l': + cnk->cflags = DP_C_LONG; + ch = *format++; + if (ch == 'l') { /* It's a long long */ + cnk->cflags = DP_C_LLONG; + ch = *format++; + } + break; + case 'L': + cnk->cflags = DP_C_LDOUBLE; + ch = *format++; + break; + default: + break; + } + state = DP_S_CONV; + break; + case DP_S_CONV: + if (cnk->num == 0) cnk->num = ++pnum; + max_pos = add_cnk_list_entry(&clist, max_pos, cnk); + if (max_pos == 0) /* out of memory :-( */ + goto done; + + switch (ch) { + case 'd': + case 'i': + cnk->type = CNK_INT; + break; + case 'o': + cnk->type = CNK_OCTAL; + cnk->flags |= DP_F_UNSIGNED; + break; + case 'u': + cnk->type = CNK_UINT; + cnk->flags |= DP_F_UNSIGNED; + break; + case 'X': + cnk->flags |= DP_F_UP; + case 'x': + cnk->type = CNK_HEX; + cnk->flags |= DP_F_UNSIGNED; + break; + case 'A': + /* hex float not supported yet */ + case 'E': + case 'G': + case 'F': + cnk->flags |= DP_F_UP; + case 'a': + /* hex float not supported yet */ + case 'e': + case 'f': + case 'g': + cnk->type = CNK_FLOAT; + break; + case 'c': + cnk->type = CNK_CHAR; + break; + case 's': + cnk->type = CNK_STRING; + break; + case 'p': + cnk->type = CNK_PTR; + break; + case 'n': + cnk->type = CNK_NUM; + break; + case '%': + cnk->type = CNK_PRCNT; + break; + default: + /* Unknown, bail out*/ + goto done; + } + ch = *format++; + state = DP_S_DEFAULT; + break; + case DP_S_DONE: + break; + default: + /* hmm? */ + break; /* some picky compilers need this */ + } + } + + /* retieve the format arguments */ + for (pnum = 0; pnum < max_pos; pnum++) { + int i; + + if (clist[pnum].num == 0) { + /* ignoring a parameter should not be permitted + * all parameters must be matched at least once + * BUT seem some system ignore this rule ... + * at least my glibc based system does --SSS + */ +#ifdef DEBUG_SNPRINTF + printf("parameter at position %d not used\n", pnum+1); +#endif + /* eat the parameter */ + va_arg (args, int); + continue; + } + for (i = 1; i < clist[pnum].num; i++) { + if (clist[pnum].chunks[0]->type != clist[pnum].chunks[i]->type) { + /* nooo noo no! + * all the references to a parameter + * must be of the same type + */ + goto done; + } + } + cnk = clist[pnum].chunks[0]; + switch (cnk->type) { + case CNK_INT: + if (cnk->cflags == DP_C_SHORT) + cnk->value = va_arg (args, int); + else if (cnk->cflags == DP_C_LONG) + cnk->value = va_arg (args, long int); + else if (cnk->cflags == DP_C_LLONG) + cnk->value = va_arg (args, LLONG); + else + cnk->value = va_arg (args, int); + + for (i = 1; i < clist[pnum].num; i++) { + clist[pnum].chunks[i]->value = cnk->value; + } + break; + + case CNK_OCTAL: + case CNK_UINT: + case CNK_HEX: + if (cnk->cflags == DP_C_SHORT) + cnk->value = va_arg (args, unsigned int); + else if (cnk->cflags == DP_C_LONG) + cnk->value = (long)va_arg (args, unsigned long int); + else if (cnk->cflags == DP_C_LLONG) + cnk->value = (LLONG)va_arg (args, unsigned LLONG); + else + cnk->value = (long)va_arg (args, unsigned int); + + for (i = 1; i < clist[pnum].num; i++) { + clist[pnum].chunks[i]->value = cnk->value; + } + break; + + case CNK_FLOAT: + if (cnk->cflags == DP_C_LDOUBLE) + cnk->fvalue = va_arg (args, LDOUBLE); + else + cnk->fvalue = va_arg (args, double); + + for (i = 1; i < clist[pnum].num; i++) { + clist[pnum].chunks[i]->fvalue = cnk->fvalue; + } + break; + + case CNK_CHAR: + cnk->value = va_arg (args, int); + + for (i = 1; i < clist[pnum].num; i++) { + clist[pnum].chunks[i]->value = cnk->value; + } + break; + + case CNK_STRING: + cnk->strvalue = va_arg (args, char *); + if (!cnk->strvalue) cnk->strvalue = "(NULL)"; + + for (i = 1; i < clist[pnum].num; i++) { + clist[pnum].chunks[i]->strvalue = cnk->strvalue; + } + break; + + case CNK_PTR: + cnk->strvalue = va_arg (args, void *); + for (i = 1; i < clist[pnum].num; i++) { + clist[pnum].chunks[i]->strvalue = cnk->strvalue; + } + break; + + case CNK_NUM: + if (cnk->cflags == DP_C_CHAR) + cnk->pnum = va_arg (args, char *); + else if (cnk->cflags == DP_C_SHORT) + cnk->pnum = va_arg (args, short int *); + else if (cnk->cflags == DP_C_LONG) + cnk->pnum = va_arg (args, long int *); + else if (cnk->cflags == DP_C_LLONG) + cnk->pnum = va_arg (args, LLONG *); + else + cnk->pnum = va_arg (args, int *); + + for (i = 1; i < clist[pnum].num; i++) { + clist[pnum].chunks[i]->pnum = cnk->pnum; + } + break; + + case CNK_PRCNT: + break; + + default: + /* what ?? */ + goto done; + } + } + /* print out the actual string from chunks */ + currlen = 0; + cnk = chunks; + while (cnk) { + int len, min, max; + + if (cnk->min_star) min = cnk->min_star->value; + else min = cnk->min; + if (cnk->max_star) max = cnk->max_star->value; + else max = cnk->max; + + switch (cnk->type) { + + case CNK_FMT_STR: + if (maxlen != 0 && maxlen > currlen) { + if (maxlen > (currlen + cnk->len)) len = cnk->len; + else len = maxlen - currlen; + + memcpy(&(buffer[currlen]), &(base[cnk->start]), len); + } + currlen += cnk->len; + + break; + + case CNK_INT: + case CNK_UINT: + fmtint (buffer, &currlen, maxlen, cnk->value, 10, min, max, cnk->flags); + break; + + case CNK_OCTAL: + fmtint (buffer, &currlen, maxlen, cnk->value, 8, min, max, cnk->flags); + break; + + case CNK_HEX: + fmtint (buffer, &currlen, maxlen, cnk->value, 16, min, max, cnk->flags); + break; + + case CNK_FLOAT: + fmtfp (buffer, &currlen, maxlen, cnk->fvalue, min, max, cnk->flags); + break; + + case CNK_CHAR: + dopr_outch (buffer, &currlen, maxlen, cnk->value); + break; + + case CNK_STRING: + if (max == -1) { + max = strlen(cnk->strvalue); + } + fmtstr (buffer, &currlen, maxlen, cnk->strvalue, cnk->flags, min, max); + break; + + case CNK_PTR: + fmtint (buffer, &currlen, maxlen, (long)(cnk->strvalue), 16, min, max, cnk->flags); + break; + + case CNK_NUM: + if (cnk->cflags == DP_C_CHAR) + *((char *)(cnk->pnum)) = (char)currlen; + else if (cnk->cflags == DP_C_SHORT) + *((short int *)(cnk->pnum)) = (short int)currlen; + else if (cnk->cflags == DP_C_LONG) + *((long int *)(cnk->pnum)) = (long int)currlen; + else if (cnk->cflags == DP_C_LLONG) + *((LLONG *)(cnk->pnum)) = (LLONG)currlen; + else + *((int *)(cnk->pnum)) = (int)currlen; + break; + + case CNK_PRCNT: + dopr_outch (buffer, &currlen, maxlen, '%'); + break; + + default: + /* what ?? */ + goto done; + } + cnk = cnk->next; + } + if (maxlen != 0) { + if (currlen < maxlen - 1) + buffer[currlen] = '\0'; + else if (maxlen > 0) + buffer[maxlen - 1] = '\0'; + } + ret = currlen; + +done: + while (chunks) { + cnk = chunks->next; + free(chunks); + chunks = cnk; + } + if (clist) { + for (pnum = 0; pnum < max_pos; pnum++) { + if (clist[pnum].chunks) free(clist[pnum].chunks); + } + free(clist); + } + return ret; +} + +static void fmtstr(char *buffer, size_t *currlen, size_t maxlen, + char *value, int flags, int min, int max) +{ + int padlen, strln; /* amount to pad */ + int cnt = 0; + +#ifdef DEBUG_SNPRINTF + printf("fmtstr min=%d max=%d s=[%s]\n", min, max, value); +#endif + if (value == 0) { + value = ""; + } + + for (strln = 0; strln < max && value[strln]; ++strln); /* strlen */ + padlen = min - strln; + if (padlen < 0) + padlen = 0; + if (flags & DP_F_MINUS) + padlen = -padlen; /* Left Justify */ + + while (padlen > 0) { + dopr_outch (buffer, currlen, maxlen, ' '); + --padlen; + } + while (*value && (cnt < max)) { + dopr_outch (buffer, currlen, maxlen, *value++); + ++cnt; + } + while (padlen < 0) { + dopr_outch (buffer, currlen, maxlen, ' '); + ++padlen; + } +} + +/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */ + +static void fmtint(char *buffer, size_t *currlen, size_t maxlen, + long value, int base, int min, int max, int flags) +{ + int signvalue = 0; + unsigned long uvalue; + char convert[20]; + int place = 0; + int spadlen = 0; /* amount to space pad */ + int zpadlen = 0; /* amount to zero pad */ + int caps = 0; + + if (max < 0) + max = 0; + + uvalue = value; + + if(!(flags & DP_F_UNSIGNED)) { + if( value < 0 ) { + signvalue = '-'; + uvalue = -value; + } else { + if (flags & DP_F_PLUS) /* Do a sign (+/i) */ + signvalue = '+'; + else if (flags & DP_F_SPACE) + signvalue = ' '; + } + } + + if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */ + + do { + convert[place++] = + (caps? "0123456789ABCDEF":"0123456789abcdef") + [uvalue % (unsigned)base ]; + uvalue = (uvalue / (unsigned)base ); + } while(uvalue && (place < 20)); + if (place == 20) place--; + convert[place] = 0; + + zpadlen = max - place; + spadlen = min - MAX (max, place) - (signvalue ? 1 : 0); + if (zpadlen < 0) zpadlen = 0; + if (spadlen < 0) spadlen = 0; + if (flags & DP_F_ZERO) { + zpadlen = MAX(zpadlen, spadlen); + spadlen = 0; + } + if (flags & DP_F_MINUS) + spadlen = -spadlen; /* Left Justify */ + +#ifdef DEBUG_SNPRINTF + printf("zpad: %d, spad: %d, min: %d, max: %d, place: %d\n", + zpadlen, spadlen, min, max, place); +#endif + + /* Spaces */ + while (spadlen > 0) { + dopr_outch (buffer, currlen, maxlen, ' '); + --spadlen; + } + + /* Sign */ + if (signvalue) + dopr_outch (buffer, currlen, maxlen, signvalue); + + /* Zeros */ + if (zpadlen > 0) { + while (zpadlen > 0) { + dopr_outch (buffer, currlen, maxlen, '0'); + --zpadlen; + } + } + + /* Digits */ + while (place > 0) + dopr_outch (buffer, currlen, maxlen, convert[--place]); + + /* Left Justified spaces */ + while (spadlen < 0) { + dopr_outch (buffer, currlen, maxlen, ' '); + ++spadlen; + } +} + +static LDOUBLE abs_val(LDOUBLE value) +{ + LDOUBLE result = value; + + if (value < 0) + result = -value; + + return result; +} + +static LDOUBLE POW10(int exp) +{ + LDOUBLE result = 1; + + while (exp) { + result *= 10; + exp--; + } + + return result; +} + +static LLONG ROUND(LDOUBLE value) +{ + LLONG intpart; + + intpart = (LLONG)value; + value = value - intpart; + if (value >= 0.5) intpart++; + + return intpart; +} + +/* a replacement for modf that doesn't need the math library. Should + be portable, but slow */ +static double my_modf(double x0, double *iptr) +{ + int i; + long l; + double x = x0; + double f = 1.0; + + for (i=0;i<100;i++) { + l = (long)x; + if (l <= (x+1) && l >= (x-1)) break; + x *= 0.1; + f *= 10.0; + } + + if (i == 100) { + /* yikes! the number is beyond what we can handle. What do we do? */ + (*iptr) = 0; + return 0; + } + + if (i != 0) { + double i2; + double ret; + + ret = my_modf(x0-l*f, &i2); + (*iptr) = l*f + i2; + return ret; + } + + (*iptr) = l; + return x - (*iptr); +} + + +static void fmtfp (char *buffer, size_t *currlen, size_t maxlen, + LDOUBLE fvalue, int min, int max, int flags) +{ + int signvalue = 0; + double ufvalue; + char iconvert[311]; + char fconvert[311]; + int iplace = 0; + int fplace = 0; + int padlen = 0; /* amount to pad */ + int zpadlen = 0; + int caps = 0; + int idx; + double intpart; + double fracpart; + double temp; + + /* + * AIX manpage says the default is 0, but Solaris says the default + * is 6, and sprintf on AIX defaults to 6 + */ + if (max < 0) + max = 6; + + ufvalue = abs_val (fvalue); + + if (fvalue < 0) { + signvalue = '-'; + } else { + if (flags & DP_F_PLUS) { /* Do a sign (+/i) */ + signvalue = '+'; + } else { + if (flags & DP_F_SPACE) + signvalue = ' '; + } + } + +#if 0 + if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */ +#endif + +#if 0 + if (max == 0) ufvalue += 0.5; /* if max = 0 we must round */ +#endif + + /* + * Sorry, we only support 9 digits past the decimal because of our + * conversion method + */ + if (max > 9) + max = 9; + + /* We "cheat" by converting the fractional part to integer by + * multiplying by a factor of 10 + */ + + temp = ufvalue; + my_modf(temp, &intpart); + + fracpart = ROUND((POW10(max)) * (ufvalue - intpart)); + + if (fracpart >= POW10(max)) { + intpart++; + fracpart -= POW10(max); + } + + + /* Convert integer part */ + do { + temp = intpart*0.1; + my_modf(temp, &intpart); + idx = (int) ((temp -intpart +0.05)* 10.0); + /* idx = (int) (((double)(temp*0.1) -intpart +0.05) *10.0); */ + /* printf ("%llf, %f, %x\n", temp, intpart, idx); */ + iconvert[iplace++] = + (caps? "0123456789ABCDEF":"0123456789abcdef")[idx]; + } while (intpart && (iplace < 311)); + if (iplace == 311) iplace--; + iconvert[iplace] = 0; + + /* Convert fractional part */ + if (fracpart) + { + do { + temp = fracpart*0.1; + my_modf(temp, &fracpart); + idx = (int) ((temp -fracpart +0.05)* 10.0); + /* idx = (int) ((((temp/10) -fracpart) +0.05) *10); */ + /* printf ("%lf, %lf, %ld\n", temp, fracpart, idx ); */ + fconvert[fplace++] = + (caps? "0123456789ABCDEF":"0123456789abcdef")[idx]; + } while(fracpart && (fplace < 311)); + if (fplace == 311) fplace--; + } + fconvert[fplace] = 0; + + /* -1 for decimal point, another -1 if we are printing a sign */ + padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0); + zpadlen = max - fplace; + if (zpadlen < 0) zpadlen = 0; + if (padlen < 0) + padlen = 0; + if (flags & DP_F_MINUS) + padlen = -padlen; /* Left Justify */ + + if ((flags & DP_F_ZERO) && (padlen > 0)) { + if (signvalue) { + dopr_outch (buffer, currlen, maxlen, signvalue); + --padlen; + signvalue = 0; + } + while (padlen > 0) { + dopr_outch (buffer, currlen, maxlen, '0'); + --padlen; + } + } + while (padlen > 0) { + dopr_outch (buffer, currlen, maxlen, ' '); + --padlen; + } + if (signvalue) + dopr_outch (buffer, currlen, maxlen, signvalue); + + while (iplace > 0) + dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]); + +#ifdef DEBUG_SNPRINTF + printf("fmtfp: fplace=%d zpadlen=%d\n", fplace, zpadlen); +#endif + + /* + * Decimal point. This should probably use locale to find the correct + * char to print out. + */ + if (max > 0) { + dopr_outch (buffer, currlen, maxlen, '.'); + + while (zpadlen > 0) { + dopr_outch (buffer, currlen, maxlen, '0'); + --zpadlen; + } + + while (fplace > 0) + dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]); + } + + while (padlen < 0) { + dopr_outch (buffer, currlen, maxlen, ' '); + ++padlen; + } +} + +static void dopr_outch(char *buffer, size_t *currlen, size_t maxlen, char c) +{ + if (*currlen < maxlen) { + buffer[(*currlen)] = c; + } + (*currlen)++; +} + +static struct pr_chunk *new_chunk(void) { + struct pr_chunk *new_c = (struct pr_chunk *)malloc(sizeof(struct pr_chunk)); + + if ( !new_c ) + return NULL; + + new_c->type = 0; + new_c->num = 0; + new_c->min = 0; + new_c->min_star = NULL; + new_c->max = -1; + new_c->max_star = NULL; + new_c->flags = 0; + new_c->cflags = 0; + new_c->start = 0; + new_c->len = 0; + new_c->value = 0; + new_c->fvalue = 0; + new_c->strvalue = NULL; + new_c->pnum = NULL; + new_c->next = NULL; + + return new_c; +} + +static int add_cnk_list_entry(struct pr_chunk_x **list, + int max_num, struct pr_chunk *chunk) { + struct pr_chunk_x *l; + struct pr_chunk **c; + int max; + int cnum; + int i, pos; + + if (chunk->num > max_num) { + max = chunk->num; + + if (*list == NULL) { + l = (struct pr_chunk_x *)malloc(sizeof(struct pr_chunk_x) * max); + pos = 0; + } else { + l = (struct pr_chunk_x *)realloc(*list, sizeof(struct pr_chunk_x) * max); + pos = max_num; + } + if (l == NULL) { + for (i = 0; i < max; i++) { + if ((*list)[i].chunks) free((*list)[i].chunks); + } + return 0; + } + for (i = pos; i < max; i++) { + l[i].chunks = NULL; + l[i].num = 0; + } + } else { + l = *list; + max = max_num; + } + + i = chunk->num - 1; + cnum = l[i].num + 1; + if (l[i].chunks == NULL) { + c = (struct pr_chunk **)malloc(sizeof(struct pr_chunk *) * cnum); + } else { + c = (struct pr_chunk **)realloc(l[i].chunks, sizeof(struct pr_chunk *) * cnum); + } + if (c == NULL) { + for (i = 0; i < max; i++) { + if (l[i].chunks) free(l[i].chunks); + } + return 0; + } + c[l[i].num] = chunk; + l[i].chunks = c; + l[i].num = cnum; + + *list = l; + return max; +} + + int smb_vsnprintf (char *str, size_t count, const char *fmt, va_list args) +{ + return dopr(str, count, fmt, args); +} +#define vsnprintf smb_vsnprintf +#endif + +/* yes this really must be a ||. Don't muck with this (tridge) + * + * The logic for these two is that we need our own definition if the + * OS *either* has no definition of *sprintf, or if it does have one + * that doesn't work properly according to the autoconf test. + */ +#if !defined(HAVE_SNPRINTF) || !defined(HAVE_C99_VSNPRINTF) +int smb_snprintf(char *str,size_t count,const char *fmt,...) +{ + size_t ret; + va_list ap; + + va_start(ap, fmt); + ret = vsnprintf(str, count, fmt, ap); + va_end(ap); + return ret; +} +#define snprintf smb_snprintf +#endif + +#endif + +#ifndef HAVE_VASPRINTF + int vasprintf(char **ptr, const char *format, va_list ap) +{ + int ret; + va_list ap2; + + VA_COPY(ap2, ap); + + ret = vsnprintf(NULL, 0, format, ap2); + if (ret <= 0) return ret; + + (*ptr) = (char *)malloc(ret+1); + if (!*ptr) return -1; + + VA_COPY(ap2, ap); + + ret = vsnprintf(*ptr, ret+1, format, ap2); + + return ret; +} +#endif + + +#ifndef HAVE_ASPRINTF + int asprintf(char **ptr, const char *format, ...) +{ + va_list ap; + int ret; + + *ptr = NULL; + va_start(ap, format); + ret = vasprintf(ptr, format, ap); + va_end(ap); + + return ret; +} +#endif + +#ifdef TEST_SNPRINTF + + int sprintf(char *str,const char *fmt,...); + + int main (void) +{ + char buf1[1024]; + char buf2[1024]; + char *buf3; + char *fp_fmt[] = { + "%1.1f", + "%-1.5f", + "%1.5f", + "%123.9f", + "%10.5f", + "% 10.5f", + "%+22.9f", + "%+4.9f", + "%01.3f", + "%4f", + "%3.1f", + "%3.2f", + "%.0f", + "%f", + "%-8.8f", + "%-9.9f", + NULL + }; + double fp_nums[] = { 6442452944.1234, -1.5, 134.21, 91340.2, 341.1234, 203.9, 0.96, 0.996, + 0.9996, 1.996, 4.136, 5.030201, 0.00205, + /* END LIST */ 0}; + char *int_fmt[] = { + "%-1.5d", + "%1.5d", + "%123.9d", + "%5.5d", + "%10.5d", + "% 10.5d", + "%+22.33d", + "%01.3d", + "%4d", + "%d", + NULL + }; + long int_nums[] = { -1, 134, 91340, 341, 0203, 0, 1234567890}; + char *str_fmt[] = { + "%10.5s", + "%-10.5s", + "%5.10s", + "%-5.10s", + "%10.1s", + "%0.10s", + "%10.0s", + "%1.10s", + "%s", + "%.1s", + "%.10s", + "%10s", + NULL + }; + char *str_vals[] = {"hello", "a", "", "a longer string", NULL}; + int x, y; + int fail = 0; + int num = 0; + int l1, l2; + + printf ("Testing snprintf format codes against system sprintf...\n"); + + for (x = 0; fp_fmt[x] ; x++) { + for (y = 0; fp_nums[y] != 0 ; y++) { + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(NULL, 0, fp_fmt[x], fp_nums[y]); + l2 = snprintf(buf1, sizeof(buf1), fp_fmt[x], fp_nums[y]); + sprintf (buf2, fp_fmt[x], fp_nums[y]); + buf1[1023] = buf1[1023] = '\0'; + if (strcmp (buf1, buf2) || (l1 != l2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + fp_fmt[x], l1, buf1, l2, buf2); + fail++; + } + num++; + } + } + + for (x = 0; int_fmt[x] ; x++) { + for (y = 0; int_nums[y] != 0 ; y++) { + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(NULL, 0, int_fmt[x], int_nums[y]); + l2 = snprintf(buf1, sizeof(buf1), int_fmt[x], int_nums[y]); + sprintf (buf2, int_fmt[x], int_nums[y]); + buf1[1023] = buf1[1023] = '\0'; + if (strcmp (buf1, buf2) || (l1 != l2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + int_fmt[x], l1, buf1, l2, buf2); + fail++; + } + num++; + } + } + + for (x = 0; str_fmt[x] ; x++) { + for (y = 0; str_vals[y] != 0 ; y++) { + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(NULL, 0, str_fmt[x], str_vals[y]); + l2 = snprintf(buf1, sizeof(buf1), str_fmt[x], str_vals[y]); + sprintf (buf2, str_fmt[x], str_vals[y]); + buf1[1023] = buf1[1023] = '\0'; + if (strcmp (buf1, buf2) || (l1 != l2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + str_fmt[x], l1, buf1, l2, buf2); + fail++; + } + num++; + } + } + +#define BUFSZ 2048 + + buf1[0] = buf2[0] = '\0'; + if ((buf3 = malloc(BUFSZ)) == NULL) { + fail++; + } else { + num++; + memset(buf3, 'a', BUFSZ); + snprintf(buf1, sizeof(buf1), "%.*s", 1, buf3); + buf1[1023] = '\0'; + if (strcmp(buf1, "a") != 0) { + printf("length limit buf1 '%s' expected 'a'\n", buf1); + fail++; + } + } + + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(buf1, sizeof(buf1), "%4$*1$d %2$s %3$*1$.*1$f", 3, "pos test", 12.3456, 9); + l2 = sprintf(buf2, "%4$*1$d %2$s %3$*1$.*1$f", 3, "pos test", 12.3456, 9); + buf1[1023] = buf1[1023] = '\0'; + if (strcmp(buf1, buf2) || (l1 != l2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + "%4$*1$d %2$s %3$*1$.*1$f", l1, buf1, l2, buf2); + fail++; + } + + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(buf1, sizeof(buf1), "%4$*4$d %2$s %3$*4$.*4$f", 3, "pos test", 12.3456, 9); + l2 = sprintf(buf2, "%4$*4$d %2$s %3$*4$.*4$f", 3, "pos test", 12.3456, 9); + buf1[1023] = buf1[1023] = '\0'; + if (strcmp(buf1, buf2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + "%4$*1$d %2$s %3$*1$.*1$f", l1, buf1, l2, buf2); + fail++; + } +#if 0 + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(buf1, sizeof(buf1), "%lld", (LLONG)1234567890); + l2 = sprintf(buf2, "%lld", (LLONG)1234567890); + buf1[1023] = buf1[1023] = '\0'; + if (strcmp(buf1, buf2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + "%lld", l1, buf1, l2, buf2); + fail++; + } + + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(buf1, sizeof(buf1), "%Lf", (LDOUBLE)890.1234567890123); + l2 = sprintf(buf2, "%Lf", (LDOUBLE)890.1234567890123); + buf1[1023] = buf1[1023] = '\0'; + if (strcmp(buf1, buf2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + "%Lf", l1, buf1, l2, buf2); + fail++; + } +#endif + printf ("%d tests failed out of %d.\n", fail, num); + + printf("seeing how many digits we support\n"); + { + double v0 = 0.12345678901234567890123456789012345678901; + for (x=0; x<100; x++) { + double p = pow(10, x); + double r = v0*p; + snprintf(buf1, sizeof(buf1), "%1.1f", r); + sprintf(buf2, "%1.1f", r); + if (strcmp(buf1, buf2)) { + printf("we seem to support %d digits\n", x-1); + break; + } + } + } + + return 0; +} +#endif /* TEST_SNPRINTF */ diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..a5aa519 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,590 @@ +/**************************************************************************** + * + * utils.c - NRPE Utility Functions + * + * License: GPLv2 + * Copyright (c) 2009-2017 Nagios Enterprises + * 1999-2008 Ethan Galstad (nagios@nagios.org) + * + * Description: + * + * This file contains common network functions used in nrpe and check_nrpe. + * + * License Notice: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + ****************************************************************************/ + +#include "../include/common.h" +#include "../include/utils.h" +#include +#ifdef HAVE_PATHS_H +#include +#endif + +#ifndef HAVE_ASPRINTF +extern int asprintf(char **ptr, const char *format, ...); +#endif +#ifndef HAVE_VASPRINTF +extern int vasprintf(char **ptr, const char *format, va_list ap); +#endif + +#ifndef NI_MAXSERV +# define NI_MAXSERV 32 +#endif + +#ifndef NI_MAXHOST +# define NI_MAXHOST 1025 +#endif + +extern char **environ; + +static unsigned long crc32_table[256]; + +char *log_file = NULL; +FILE *log_fp = NULL; + +static int my_create_socket(struct addrinfo *ai, const char *bind_address, int redirect_stderr); + + +/* build the crc table - must be called before calculating the crc value */ +void generate_crc32_table(void) +{ + unsigned long crc, poly; + int i, j; + + poly = 0xEDB88320L; + for (i = 0; i < 256; i++) { + crc = i; + for (j = 8; j > 0; j--) { + if (crc & 1) + crc = (crc >> 1) ^ poly; + else + crc >>= 1; + } + crc32_table[i] = crc; + } + + return; +} + +/* calculates the CRC 32 value for a buffer */ +unsigned long calculate_crc32(char *buffer, int buffer_size) +{ + register unsigned long crc = 0xFFFFFFFF; + int this_char; + int current_index; + + for (current_index = 0; current_index < buffer_size; current_index++) { + this_char = (int)buffer[current_index]; + crc = ((crc >> 8) & 0x00FFFFFF) ^ crc32_table[(crc ^ this_char) & 0xFF]; + } + + return (crc ^ 0xFFFFFFFF); +} + +/* fill a buffer with semi-random data */ +void randomize_buffer(char *buffer, int buffer_size) +{ + FILE *fp; + int x; + int seed; + + /**** FILL BUFFER WITH RANDOM ALPHA-NUMERIC CHARACTERS ****/ + + /*************************************************************** + Only use alpha-numeric characters because plugins usually + only generate numbers and letters in their output. We + want the buffer to contain the same set of characters as + plugins, so its harder to distinguish where the real output + ends and the rest of the buffer (padded randomly) starts. + ***************************************************************/ + + /* try to get seed value from /dev/urandom, as its a better source of entropy */ + fp = fopen("/dev/urandom", "r"); + if (fp != NULL) { + seed = fgetc(fp); + fclose(fp); + } + /* else fallback to using the current time as the seed */ + else + seed = (int)time(NULL); + + srand(seed); + for (x = 0; x < buffer_size; x++) + buffer[x] = (int)'0' + (int)(72.0 * rand() / (RAND_MAX + 1.0)); + + return; +} + +/* opens a connection to a remote host */ +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE +int my_connect(const char *host, struct sockaddr_storage *hostaddr, u_short port, + int address_family, const char *bind_address, int redirect_stderr) +#else +int my_connect(const char *host, struct sockaddr *hostaddr, u_short port, + int address_family, const char *bind_address, int redirect_stderr) +#endif +{ + struct addrinfo hints, *ai, *aitop; + char ntop[NI_MAXHOST], strport[NI_MAXSERV]; + int gaierr; + int sock = -1; + + FILE *output = stderr; + if (redirect_stderr) + output = stdout; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = address_family; + hints.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof strport, "%u", port); + if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) { + fprintf(output, "Could not resolve hostname %.100s: %s\n", host, gai_strerror(gaierr)); + exit(1); + } + + /* + * Loop through addresses for this host, and try each one in + * sequence until the connection succeeds. + */ + for (ai = aitop; ai; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + continue; + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), + strport, sizeof(strport), NI_NUMERICHOST | NI_NUMERICSERV) != 0) { + fprintf(output, "my_connect: getnameinfo failed\n"); + continue; + } + + /* Create a socket for connecting. */ + sock = my_create_socket(ai, bind_address, redirect_stderr); + if (sock < 0) + continue; /* Any error is already output */ + + if (connect(sock, ai->ai_addr, ai->ai_addrlen) >= 0) { + /* Successful connection. */ + memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); + break; + } else { + fprintf(output, "connect to address %s port %s: %s\n", ntop, strport, + strerror(errno)); + close(sock); + sock = -1; + } + } + + freeaddrinfo(aitop); + + /* Return failure if we didn't get a successful connection. */ + if (sock == -1) { + fprintf(output, "connect to host %s port %s: %s\n", host, strport, strerror(errno)); + return -1; + } + return sock; +} + +/* Creates a socket for the connection. */ +int my_create_socket(struct addrinfo *ai, const char *bind_address, int redirect_stderr) +{ + int sock, gaierr; + struct addrinfo hints, *res; + + FILE *output = stderr; + if (redirect_stderr) + output = stdout; + + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) + fprintf(output, "socket: %.100s\n", strerror(errno)); + + /* Bind the socket to an alternative local IP address */ + if (bind_address == NULL) + return sock; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = ai->ai_family; + hints.ai_socktype = ai->ai_socktype; + hints.ai_protocol = ai->ai_protocol; + hints.ai_flags = AI_PASSIVE; + gaierr = getaddrinfo(bind_address, NULL, &hints, &res); + if (gaierr) { + fprintf(output, "getaddrinfo: %s: %s\n", bind_address, gai_strerror(gaierr)); + close(sock); + return -1; + } + if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { + fprintf(output, "bind: %s: %s\n", bind_address, strerror(errno)); + close(sock); + freeaddrinfo(res); + return -1; + } + freeaddrinfo(res); + return sock; +} + +void add_listen_addr(struct addrinfo **listen_addrs, int address_family, char *addr, int port) +{ + struct addrinfo hints, *ai, *aitop; + char strport[NI_MAXSERV]; + int gaierr; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = address_family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = (addr == NULL) ? AI_PASSIVE : 0; + snprintf(strport, sizeof strport, "%d", port); + if ((gaierr = getaddrinfo(addr, strport, &hints, &aitop)) != 0) { + logit(LOG_ERR, "bad addr or host: %s (%s)\n", addr ? addr : "", + gai_strerror(gaierr)); + exit(1); + } + for (ai = aitop; ai->ai_next; ai = ai->ai_next) ; + ai->ai_next = *listen_addrs; + *listen_addrs = aitop; +} + +int clean_environ(const char *keep_env_vars, const char *nrpe_user) +{ +#if defined(HAVE_PATHS_H) && defined(_PATH_STDPATH) + static char *path = _PATH_STDPATH; +#else + static char *path = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"; +#endif + struct passwd *pw; + size_t len, var_sz = 0; + char **kept = NULL, *value, *var, *keep = NULL; + int i, j, keepcnt = 0; + + if (keep_env_vars && *keep_env_vars) + asprintf(&keep, "%s,NRPE_MULTILINESUPPORT,NRPE_PROGRAMVERSION", keep_env_vars); + else + asprintf(&keep, "NRPE_MULTILINESUPPORT,NRPE_PROGRAMVERSION"); + if (keep == NULL) { + logit(LOG_ERR, "Could not sanitize the environment. Aborting!"); + return ERROR; + } + + ++keepcnt; + i = strlen(keep); + while (i--) { + if (keep[i] == ',') + ++keepcnt; + } + + if ((kept = calloc(keepcnt + 1, sizeof(char *))) == NULL) { + logit(LOG_ERR, "Could not sanitize the environment. Aborting!"); + return ERROR; + } + for (i = 0, var = my_strsep(&keep, ","); var != NULL; var = my_strsep(&keep, ",")) + kept[i++] = strip(var); + + var = NULL; + i = 0; + while (environ[i]) { + value = environ[i]; + if ((len = strcspn(value, "=")) == 0) { + free(keep); + free(kept); + free(var); + logit(LOG_ERR, "Could not sanitize the environment. Aborting!"); + return ERROR; + } + if (len >= var_sz) { + var_sz = len + 1; + var = realloc(var, var_sz); + } + strncpy(var, environ[i], var_sz); + var[len] = 0; + + for (j = 0; kept[j]; ++j) { + if (!strncmp(var, kept[j], strlen(kept[j]))) + break; + } + if (kept[j]) { + ++i; + continue; + } + + unsetenv(var); + } + + free(var); + free(keep); + free(kept); + + + char * user = NULL; + + if (nrpe_user != NULL) { + user = strdup(nrpe_user); + pw = (struct passwd *)getpwnam(nrpe_user); + } + + if (nrpe_user == NULL || pw == NULL) { + pw = (struct passwd *)getpwuid(getuid()); + if (pw != NULL) { + user = strdup(pw->pw_name); + } + } + + if (pw == NULL) { + free(user); + return OK; + } + + setenv("PATH", path, 1); + setenv("IFS", " \t\n", 1); + setenv("LOGNAME", user, 0); + setenv("USER", user, 0); + setenv("HOME", pw->pw_dir, 0); + setenv("SHELL", pw->pw_shell, 0); + + free(user); + + return OK; +} + +char *strip(char *buffer) +{ + int x; + int index; + char *buf = buffer; + + for (x = strlen(buffer); x >= 1; x--) { + index = x - 1; + if (buffer[index] == ' ' || buffer[index] == '\r' || buffer[index] == '\n' + || buffer[index] == '\t') + buffer[index] = '\x0'; + else + break; + } + + while (*buf == ' ' || *buf == '\r' || *buf == '\n' || *buf == '\t') { + ++buf; + --x; + } + if (buf != buffer) { + memmove(buffer, buf, x); + buffer[x] = '\x0'; + } + + return buffer; +} + +/* sends all data - thanks to Beej's Guide to Network Programming */ +int sendall(int s, char *buf, int *len) +{ + int total = 0; + int bytesleft = *len; + int n = 0; + + /* send all the data */ + while (total < *len) { + n = send(s, buf + total, bytesleft, 0); /* send some data */ + if (n == -1) /* break on error */ + break; + /* apply bytes we sent */ + total += n; + bytesleft -= n; + } + + *len = total; /* return number of bytes actually sent here */ + return n == -1 ? -1 : 0; /* return -1 on failure, 0 on success */ +} + +/* receives all data - modelled after sendall() */ +int recvall(int s, char *buf, int *len, int timeout) +{ + time_t start_time; + time_t current_time; + int total = 0; + int bytesleft = *len; + int n = 0; + + bzero(buf, *len); /* clear the receive buffer */ + time(&start_time); + + /* receive all data */ + while (total < *len) { + n = recv(s, buf + total, bytesleft, 0); /* receive some data */ + + if (n == -1 && errno == EAGAIN) { + /* no data has arrived yet (non-blocking socket) */ + time(¤t_time); + if (current_time - start_time > timeout) + break; + sleep(1); + continue; + } else if (n <= 0) + break; /* receive error or client disconnect */ + + /* apply bytes we received */ + total += n; + bytesleft -= n; + } + + /* return number of bytes actually received here */ + *len = total; + + /* return <=0 on failure, bytes received on success */ + return (n <= 0) ? n : total; +} + + +/* fixes compiler problems under Solaris, since strsep() isn't included */ + +/* this code is taken from the glibc source */ +char *my_strsep(char **stringp, const char *delim) +{ + char *begin, *end; + + begin = *stringp; + if (begin == NULL) + return NULL; + + /* A frequent case is when the delimiter string contains only one + character. Here we don't need to call the expensive `strpbrk' + function and instead work using `strchr'. */ + if (delim[0] == '\0' || delim[1] == '\0') { + char ch = delim[0]; + + if (ch == '\0') + end = NULL; + else { + if (*begin == ch) + end = begin; + else + end = strchr(begin + 1, ch); + } + + } else + end = strpbrk(begin, delim); /* Find the end of the token. */ + + if (end) { + /* Terminate the token and set *STRINGP past NUL character. */ + *end++ = '\0'; + *stringp = end; + } else + /* No more delimiters; this is the last token. */ + *stringp = NULL; + + return begin; +} + +void open_log_file() +{ + int fh; + int flags = O_RDWR|O_APPEND|O_CREAT; + struct stat st; + + close_log_file(); + + if (!log_file) + return; + +#ifdef O_NOFOLLOW + flags |= O_NOFOLLOW; +#endif + if ((fh = open(log_file, flags, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1) { + printf("Warning: Cannot open log file '%s' for writing\n", log_file); + logit(LOG_WARNING, "Warning: Cannot open log file '%s' for writing", log_file); + return; + } + log_fp = fdopen(fh, "a+"); + if(log_fp == NULL) { + printf("Warning: Cannot open log file '%s' for writing\n", log_file); + logit(LOG_WARNING, "Warning: Cannot open log file '%s' for writing", log_file); + return; + } + + if ((fstat(fh, &st)) == -1) { + log_fp = NULL; + close(fh); + printf("Warning: Cannot fstat log file '%s'\n", log_file); + logit(LOG_WARNING, "Warning: Cannot fstat log file '%s'", log_file); + return; + } + if (st.st_nlink != 1 || (st.st_mode & S_IFMT) != S_IFREG) { + log_fp = NULL; + close(fh); + printf("Warning: log file '%s' has an invalid mode\n", log_file); + logit(LOG_WARNING, "Warning: log file '%s' has an invalid mode", log_file); + return; + } + + (void)fcntl(fileno(log_fp), F_SETFD, FD_CLOEXEC); +} + +void logit(int priority, const char *format, ...) +{ + time_t log_time = 0L; + va_list ap; + char *buffer = NULL; + + if (!format || !*format) + return; + + va_start(ap, format); + if(vasprintf(&buffer, format, ap) > 0) { + if (log_fp) { + time(&log_time); + /* strip any newlines from the end of the buffer */ + strip(buffer); + + /* write the buffer to the log file */ + fprintf(log_fp, "[%llu] %s\n", (unsigned long long)log_time, buffer); + fflush(log_fp); + + } else + syslog(priority, "%s", buffer); + + free(buffer); + } + va_end(ap); +} + +void close_log_file() +{ + if(!log_fp) + return; + + fflush(log_fp); + fclose(log_fp); + log_fp = NULL; + return; +} + +/* show license */ +void display_license(void) +{ + printf("This program is released under the GPL (see below) with the additional\n"); + printf("exemption that compiling, linking, and/or using OpenSSL is allowed.\n\n"); + + printf("This program is free software; you can redistribute it and/or modify\n"); + printf("it under the terms of the GNU General Public License as published by\n"); + printf("the Free Software Foundation; either version 2 of the License, or\n"); + printf("(at your option) any later version.\n\n"); + printf("This program is distributed in the hope that it will be useful,\n"); + printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n"); + printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"); + printf("GNU General Public License for more details.\n\n"); + printf("You should have received a copy of the GNU General Public License\n"); + printf("along with this program; if not, write to the Free Software\n"); + printf("Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n\n"); + + return; +} -- cgit v1.2.3