summaryrefslogtreecommitdiffstats
path: root/src/acl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/acl.c')
-rw-r--r--src/acl.c719
1 files changed, 719 insertions, 0 deletions
diff --git a/src/acl.c b/src/acl.c
new file mode 100644
index 0000000..12827f8
--- /dev/null
+++ b/src/acl.c
@@ -0,0 +1,719 @@
+/****************************************************************************
+ *
+ * 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 <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <stdarg.h>
+
+#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) {
+ if (ai->ai_family == family) {
+ 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): test match host >%s< "
+ "for 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:
+ if (debug == TRUE) {
+ char formattedStr[INET6_ADDRSTRLEN];
+ inet_ntop(ai->ai_family, (void *) &(((struct sockaddr_in6 *) (ai->ai_addr))->sin6_addr),
+ formattedStr, INET6_ADDRSTRLEN);
+ logit(LOG_INFO, "is_an_allowed_host (AF_INET6): test match host against >%s< "
+ "for allowed host >%s<\n",
+ formattedStr, dns_acl_curr->domain);
+ }
+ struct in6_addr *resolved = &(((struct sockaddr_in6 *) (ai->ai_addr))->sin6_addr);
+ memcpy((char *) &addr6, ai->ai_addr, sizeof(addr6));
+ if (!memcmp(&addr6.sin6_addr, host, sizeof(addr6.sin6_addr))) {
+ if (debug == TRUE)
+ logit(LOG_INFO, "is_an_allowed_host (AF_INET6): "
+ "host is in allowed host list!");
+ 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;
+ }
+}