nagios-nrpe/src/acl.c

682 lines
19 KiB
C

/*-
* acl.c - a small library for nrpe.c. It adds IPv4 subnets support to ACL in nrpe.
* Copyright (c) 2011 Kaspersky Lab ZAO
* Last Modified: 08-10-2011 by Konstantin Malov with Oleg Koreshkov's help
*
* 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.
*
* Some notes:
* 1) IPv6 isn't supported in ACL.
* 2) Only ANCII names are supported in ACL.
*
* License: GPL
*
* 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 <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) {
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;
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) {
if (!add_ipv4_to_acl(trimmed_tok) && !add_ipv6_to_acl(trimmed_tok)
&& !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;
}
}