Back to index

nagios-nrpe  2.13
acl.c
Go to the documentation of this file.
00001 /*-
00002  * acl.c - a small library for nrpe.c. It adds IPv4 subnets support to ACL in nrpe.
00003  * Copyright (c) 2011 Kaspersky Lab ZAO
00004  * Last Modified: 08-10-2011 by Konstantin Malov with Oleg Koreshkov's help 
00005  *
00006  * Description:
00007  * acl.c creates two linked lists. One is for IPv4 hosts and networks, another is for domain names.
00008  * All connecting hosts (if allowed_hosts is defined) are checked in these two lists.
00009  *
00010  * Some notes:
00011  * 1) IPv6 isn't supported in ACL.
00012  * 2) Only ANCII names are supported in ACL.
00013  *
00014  * License: GPL
00015  *
00016  * This program is free software; you can redistribute it and/or modify
00017  * it under the terms of the GNU General Public License as published by
00018  * the Free Software Foundation; either version 2 of the License, or
00019  * (at your option) any later version.
00020  *
00021  * This program is distributed in the hope that it will be useful,
00022  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00023  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00024  * GNU General Public License for more details.
00025  *
00026  * You should have received a copy of the GNU General Public License
00027  * along with this program; if not, write to the Free Software
00028  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00029  */
00030 
00031 #include <sys/types.h>
00032 #include <sys/socket.h>
00033 
00034 #include <netinet/in.h>
00035 #include <arpa/inet.h>
00036 
00037 #include <stdio.h>
00038 #include <stdlib.h>
00039 #include <string.h>
00040 #include <ctype.h>
00041 #include <netdb.h>
00042 #include <syslog.h>
00043 #include <stdarg.h>
00044 
00045 #include "../include/acl.h"
00046 
00047 /* This function checks if a char argumnet from valid char range.
00048  * Valid range is: ASCII only, a number or a letter, a space, a dot, a slash, a dash, a comma.
00049  *
00050  * Returns:
00051  *      0 - char isn't from valid group
00052  *  1 - char is a number
00053  *  2 - char is a letter
00054  *  3 - char is a space(' ')
00055  *  4 - char is a dot('.')
00056  *  5 - char is a slash('/')
00057  *  6 - char is a dash('-')
00058  *  7 - char is a comma(',')
00059  */
00060 
00061 int isvalidchar(int c) {
00062         if (!isascii(c))
00063                 return 0;
00064 
00065         if (isdigit(c))
00066                 return 1;
00067 
00068         if (isalpha(c))
00069                 return 2;
00070 
00071         if (isspace(c))
00072                 return 3;
00073 
00074         switch (c) {
00075         case '.':
00076                 return 4;
00077                 break;
00078         case '/':
00079                 return 5;
00080                 break;
00081         case '-':
00082                 return 6;
00083                 break;
00084         case ',':
00085                 return 7;
00086                 break;
00087         default:
00088                 return 0;
00089         }
00090 }
00091 
00092 /*
00093  * Get substring from allowed_hosts from s position to e position.
00094  */
00095 
00096 char * acl_substring(char *string, int s, int e) {
00097     char *substring;
00098     int len = e - s;
00099 
00100         if (len < 0)
00101                 return NULL;
00102 
00103     if ( (substring = malloc(len + 1)) == NULL)
00104         return NULL;
00105 
00106     memmove(substring, string + s, len + 1);
00107     return substring;
00108 }
00109 
00110 /*
00111  * Add IPv4 host or network to IP ACL. IPv4 format is X.X.X.X[/X].
00112  * Host will be added to ACL only if it has passed IPv4 format check.
00113  *
00114  * Returns:
00115  * 1 - on success
00116  * 0 - on failure
00117  *
00118  * States for IPv4 format check:
00119  *  0 - numbers(-> 1), dot(-> -1), slash(-> -1), other(-> -1)
00120  *  1 - numbers(-> 1), dot(-> 2),  slash(-> -1), other(-> -1)
00121  *  2 - numbers(-> 3), dot(-> -1), slash(-> -1), other(-> -1)
00122  *  3 - numbers(-> 3), dot(-> 4),  slash(-> -1), other(-> -1)
00123  *  4 - numbers(-> 5), dot(-> -1), slash(-> -1), other(-> -1)
00124  *  5 - numbers(-> 5), dot(-> 6),  slash(-> -1), other(-> -1)
00125  *  6 - numbers(-> 7), dot(-> -1), slash(-> -1), other(-> -1)
00126  *  7 - numbers(-> 7), dor(-> -1), slash(-> 8),  other(-> -1)
00127  *  8 - numbers(-> 9), dor(-> -1), slash(-> -1), other(-> -1)
00128  *  9 - numbers(-> 9), dot(-> -1), slash(-> -1), other(-> -1)
00129  *
00130  *  Good states are 7(IPv4 host) and 9(IPv4 network)
00131  */
00132 
00133 int add_ipv4_to_acl(char *ipv4) {
00134         int state = 0;
00135         int octet = 0;
00136         int index = 0;  /* position in data array */
00137         int data[5];    /* array to store ip octets and mask */
00138         int len = strlen(ipv4);
00139         int i, c;
00140         unsigned long ip, mask;
00141         struct ip_acl *ip_acl_curr;
00142 
00143         /* Check for min and max IPv4 valid length */
00144         if (len < 7 || len > 18)
00145                 return 0;
00146 
00147         /* default mask for ipv4 */
00148         data[4] = 32;
00149 
00150         /* Basic IPv4 format check */
00151         for (i = 0; i < len; i++) {
00152                 /* Return 0 on error state */
00153                 if (state == -1)
00154                         return 0;
00155 
00156                 c = ipv4[i];
00157 
00158                 switch (c) {
00159                 case '0': case '1': case '2': case '3': case '4':
00160                 case '5': case '6': case '7': case '8': case '9':
00161                         octet = octet * 10 + CHAR_TO_NUMBER(c);
00162                         switch (state) {
00163                         case 0: case 2: case 4: case 6: case 8:
00164                                 state++;
00165                                 break;
00166                         }
00167                         break;
00168                 case '.':
00169                         switch (state) {
00170                         case 1: case 3: case 5:
00171                                 data[index++] = octet;
00172                                 octet = 0;
00173                                 state++;
00174                                 break;
00175                         default:
00176                                 state = -1;
00177                         }
00178                         break;
00179                 case '/':
00180                         switch (state) {
00181                         case 7:
00182                                 data[index++] = octet;
00183                                 octet = 0;
00184                                 state++;
00185                                 break;
00186                         default:
00187                                 state = -1;
00188                         }
00189                         break;
00190                 default:
00191                         state = -1;
00192                 }
00193         }
00194 
00195         /* Exit state handling */
00196         switch (state) {
00197         case 7: case 9:
00198                 data[index] = octet;
00199                 break;
00200         default:
00201                 /* Bad states */
00202                 return 0;
00203         }
00204 
00205         /*
00206          * Final IPv4 format check.
00207          */
00208         for (i=0; i < 4; i++) {
00209                 if (data[i] < 0 || data[i] > 255) {
00210                         syslog(LOG_ERR,"Invalid IPv4 address/network format(%s) in allowed_hosts option\n",ipv4);
00211                         return 0;
00212                 }
00213         }
00214 
00215         if (data[4] < 0 || data[4] > 32) {
00216                 syslog(LOG_ERR,"Invalid IPv4 network mask format(%s) in allowed_hosts option\n",ipv4);
00217                 return 0;
00218         }
00219 
00220         /* Conver ip and mask to unsigned long */
00221         ip = htonl((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]);
00222         mask =  htonl(-1 << (32 - data[4]));
00223 
00224         /* Wrong network address */
00225         if ( (ip & mask) != ip) {
00226                 syslog(LOG_ERR,"IP address and mask do not match in %s\n",ipv4);
00227                 return 0;
00228         }
00229 
00230         /* Add addr to ip_acl list */
00231         if ( (ip_acl_curr = malloc(sizeof(*ip_acl_curr))) == NULL) {
00232                 syslog(LOG_ERR,"Can't allocate memory for ACL, malloc error\n");
00233                 return 0;
00234         }
00235 
00236         /* Save result in ACL ip list */
00237         ip_acl_curr->addr.s_addr = ip;
00238         ip_acl_curr->mask.s_addr = mask;
00239         ip_acl_curr->next = NULL;
00240 
00241         if (ip_acl_head == NULL) {
00242                 ip_acl_head = ip_acl_curr;
00243         } else {
00244                 ip_acl_prev->next = ip_acl_curr;
00245         }
00246         ip_acl_prev = ip_acl_curr;
00247         return 1;
00248 }
00249 
00250 /*
00251  * Add domain to DNS ACL list
00252  * Domain will be added only if it has passed domain name check.
00253  *
00254  * In this case domain valid format is:
00255  * 1) Domain names must use only alphanumeric characters and dashes (-).
00256  * 2) Domain names mustn't begin or end with dashes (-).
00257  * 3) Domain names mustn't have more than 63 characters.
00258  *
00259  * Return:
00260  * 1 - for success
00261  * 0 - for failure
00262  *
00263  * 0 - alpha(-> 1), number(-> 1), dot(-> -1), dash(-> -1), all other(-> -1)
00264  * 1 - alpha(-> 1), number(-> 1), dot(-> 2),  dash(-> 6),  all other(-> -1)
00265  * 2 - alpha(-> 3), number(-> 1), dot(-> -1), dash(-> -1), all other(-> -1)
00266  * 3 - alpha(-> 4), number(-> 1), dot(-> 2),  dash(-> 6),  all other(-> -1)
00267  * 4 - alpha(-> 5), number(-> 1), dot(-> 2),  dash(-> 6),  all other(-> -1)
00268  * 5 - alpha(-> 1), number(-> 1), dot(-> 2),  dash(-> 6),  all other(-> -1)
00269  * 6 - alpha(-> 1), number(-> 1), dot(-> 2),  dash(-> 6),  all other(-> -1)
00270 
00271  * For real FQDN only 4 and 5 states are good for exit.
00272  * I don't check if top domain exists (com, ru and etc.)
00273  * But in real life NRPE could work in LAN,
00274  * with local domain zones like .local or with names like 'mars' added to /etc/hosts.
00275  * So 1 is good state too. And maybe this check is not necessary at all...
00276  */
00277 
00278 int add_domain_to_acl(char *domain) {
00279         int state = 0;
00280         int len = strlen(domain);
00281         int i, c;
00282 
00283         struct dns_acl *dns_acl_curr;
00284 
00285         if (len > 63)
00286                 return 0;
00287 
00288         for (i = 0; i < len; i++) {
00289                 c = domain[i];
00290                 switch (isvalidchar(c)) {
00291                 case 1:
00292                         state = 1;
00293                         break;
00294                 case 2:
00295                         switch (state) {
00296                         case 0: case 1: case 5: case 6:
00297                                 state = 1;
00298                                 break;
00299                         case 2: case 3: case 4:
00300                                 state++;
00301                                 break;
00302                         }
00303                         break;
00304 
00305                 case 4:
00306                         switch (state) {
00307                         case 0: case 2:
00308                                 state = -1;
00309                                 break;
00310                         default:
00311                                 state = 2;
00312                         }
00313                         break;
00314                 case 6:
00315                         switch (state) {
00316                         case 0: case 2:
00317                                 state = -1;
00318                                 break;
00319                         default:
00320                                 state = 6;
00321                         }
00322                         break;
00323                 default:
00324                         /* Not valid chars */
00325                         return 0;
00326                 }
00327         }
00328 
00329         /* Check exit code */
00330         switch (state) {
00331         case 1: case 4: case 5:
00332                 /* Add name to domain ACL list */
00333                 if ( (dns_acl_curr = malloc(sizeof(*dns_acl_curr))) == NULL) {
00334                         syslog(LOG_ERR,"Can't allocate memory for ACL, malloc error\n");
00335                         return 0;
00336                 }
00337                 strcpy(dns_acl_curr->domain, domain);
00338                 dns_acl_curr->next = NULL;
00339 
00340                 if (dns_acl_head == NULL)
00341                         dns_acl_head = dns_acl_curr;
00342                 else
00343                         dns_acl_prev->next = dns_acl_curr;
00344 
00345                 dns_acl_prev = dns_acl_curr;
00346                 return 1;
00347         default:
00348                 return 0;
00349         }
00350 }
00351 
00352 /* Checks connectiong host in ACL
00353  *
00354  * Returns:
00355  * 1 - on success
00356  * 0 - on failure
00357  */
00358 
00359 int is_an_allowed_host(struct in_addr host) {
00360         struct ip_acl *ip_acl_curr = ip_acl_head;
00361         struct dns_acl *dns_acl_curr = dns_acl_head;
00362         struct in_addr addr;
00363         struct hostent *he;
00364 
00365         while (ip_acl_curr != NULL) {
00366                 if ( (host.s_addr & ip_acl_curr->mask.s_addr) == ip_acl_curr->addr.s_addr)
00367                         return 1;
00368 
00369                 ip_acl_curr = ip_acl_curr->next;
00370         }
00371 
00372         while(dns_acl_curr != NULL) {
00373         he = gethostbyname(dns_acl_curr->domain);
00374         if (he == NULL)
00375                         return 0;
00376 
00377                 while (*he->h_addr_list) {
00378                         memmove((char *)&addr,*he->h_addr_list++, sizeof(addr));
00379                         if (addr.s_addr == host.s_addr)
00380                                 return 1;
00381                 }
00382                 dns_acl_curr = dns_acl_curr->next;
00383         }
00384         return 0;
00385 }
00386 
00387 /* The trim() function takes a source string and copies it to the destination string,
00388  * stripped of leading and training whitespace. The destination string must be 
00389  * allocated at least as large as the source string.
00390  */
00391 
00392 void trim( char *src, char *dest) {
00393        char *sptr, *dptr;
00394 
00395        for( sptr = src; isblank( *sptr) && *sptr; sptr++); /* Jump past leading spaces */
00396        for( dptr = dest; !isblank( *sptr) && *sptr; ) {
00397               *dptr = *sptr;
00398               sptr++;
00399               dptr++;
00400        }
00401        *dptr = '\0';
00402        return;
00403 }
00404 
00405 /* This function splits allowed_hosts to substrings with comma(,) as a delimeter.
00406  * It doesn't check validness of ACL record (add_ipv4_to_acl() and add_domain_to_acl() do),
00407  * just trims spaces from ACL records.
00408  * After this it sends ACL records to add_ipv4_to_acl() or add_domain_to_acl().
00409  */
00410 
00411 void parse_allowed_hosts(char *allowed_hosts) {
00412        char *hosts = strdup( allowed_hosts);     /* Copy since strtok* modifes original */
00413        char *saveptr;
00414        char *tok;
00415        const char *delim = ",";
00416        char *trimmed_tok;
00417 
00418        tok = strtok_r( hosts, delim, &saveptr);
00419        while( tok) {
00420               trimmed_tok = malloc( sizeof( char) * ( strlen( tok) + 1));
00421               trim( tok, trimmed_tok);
00422               if( strlen( trimmed_tok) > 0) {
00423                      if (!add_ipv4_to_acl(trimmed_tok) && !add_domain_to_acl(trimmed_tok)) {
00424                             syslog(LOG_ERR,"Can't add to ACL this record (%s). Check allowed_hosts option!\n",trimmed_tok);
00425                      }
00426               }
00427               free( trimmed_tok);
00428               tok = strtok_r(( char *)0, delim, &saveptr);
00429        }
00430 
00431        free( hosts);
00432 }
00433 
00434 /*
00435  * Converts mask in unsigned long format to two digit prefix
00436  */
00437 
00438 unsigned int prefix_from_mask(struct in_addr mask) {
00439         int prefix = 0;
00440         unsigned long bit = 1;
00441         int i;
00442 
00443         for (i = 0; i < 32; i++) {
00444                 if (mask.s_addr & bit)
00445                         prefix++;
00446 
00447                 bit = bit << 1;
00448         }
00449         return (prefix);
00450 }
00451 
00452 /*
00453  * It shows all hosts in ACL lists
00454  */
00455 
00456 void show_acl_lists(void) {
00457         struct ip_acl *ip_acl_curr = ip_acl_head;
00458         struct dns_acl *dns_acl_curr = dns_acl_head;
00459 
00460         while (ip_acl_curr != NULL) {
00461                 printf(" 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);
00462                 ip_acl_curr = ip_acl_curr->next;
00463         }
00464 
00465         while (dns_acl_curr != NULL) {
00466                 printf("DNS ACL: %s\n", dns_acl_curr->domain);
00467                 dns_acl_curr = dns_acl_curr->next;
00468         }
00469 }