Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
8.4.1 Problem
You want to restrict access to the network based on hostname or IP address. 8.4.2 Solution
First, get the IP address of the remote connection, and verify that the address has a hostname associated with it. To ensure that the hostname is not being spoofed (i.e., the address reverses to one hostname, but the hostname does not map to that IP address), look up the hostname and compare the resulting IP address with the IP address of the connection; if the IP addresses do not match, the hostname is likely being spoofed. Next, compare the IP address and/or hostname with a set of rules that determine whether to grant the remote connection access. 8.4.3 Discussion
The first step in restricting access from the network based on hostname or IP address is to ensure that the remote connection is not engaging in a DNS spoofing attack. No foolproof method exists for guaranteeing that the address is not being spoofed, though the code presented here can provide a reasonable assurance for most cases. In particular, if the DNS server for the domain that an IP address reverse-maps to has been compromised, there is no way to know. The first code listing that we present implements a worker function, check_spoofdns( ), which performs a set of DNS lookups and compares the results. The first lookup retrieves the hostname to which an IP address maps. An IP address does not necessarily have to reverse-map to a hostname, so if this first lookup yields no mapping, it is generally safe to assume that no spoofing is taking place. If the IP address does map to a hostname, a lookup is performed on that hostname to retrieve the IP address or addresses to which it maps. The hostname should exist, but if it does not, the connection should be considered suspect. Although it is possible that something funny is going on with the remote connection, the lack of a name-to- address mapping could be innocent. Each of the addresses returned by the hostname lookup is compared against the IP address of the remote connection. If the IP address of the remote connection is not matched, the likelihood of a spoofing attack is high, though still not guaranteed. If the IP address of the remote connection is matched, the code assumes that no spoofing attack is taking place. #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #define SPC_ERROR_NOREVERSE 1 /* IP address does not map to a hostname */ #define SPC_ERROR_NOHOSTNAME 2 /* Reversed hostname does not exist */ #define SPC_ERROR_BADHOSTNAME 3 /* IP addresses do not match */ #define SPC_ERROR_HOSTDENIED 4 /* TCP/SPC Wrappers denied host access */ static int check_spoofdns(int sockfd, struct sockaddr_in *addr, char **name) { int addrlen, i; char *hostname; struct hostent *he; *name = 0; for (;;) { addrlen = sizeof(struct sockaddr_in); if (getpeername(sockfd, (struct sockaddr *)addr, &addrlen) != -1) break; if (errno != EINTR && errno != EAGAIN) return -1; } for (;;) { he = gethostbyaddr((char *)&addr->sin_addr, sizeof(addr->sin_addr), AF_INET); if (he) break; if (h_errno = = HOST_NOT_FOUND) { endhostent( ); return SPC_ERROR_NOREVERSE; } if (h_errno != TRY_AGAIN) { endhostent( ); return -1; } } hostname = strdup(he->h_name); for (;;) { if ((he = gethostbyname(hostname)) != 0) break; if (h_errno = = HOST_NOT_FOUND) { endhostent( ); free(hostname); return SPC_ERROR_NOHOSTNAME; } if (h_errno != TRY_AGAIN) { endhostent( ); free(hostname); return -1; } } /* Check all IP addresses returned for the hostname. If one matches, return * 0 to indicate that the address is not likely being spoofed. */ for (i = 0; he->h_addr_list[i]; i++) if (*(in_addr_t *)he->h_addr_list[i] = = addr->sin_addr.s_addr) { *name = hostname; endhostent( ); return 0; } /* No matches. Spoofing very likely */ free(hostname); endhostent( ); return SPC_ERROR_BADHOSTNAME; } The next code listing contains several worker functions as well as the function spc_host_init( ), which requires a single argument that is the name of a file from which access restriction information is to be read. The access restriction information is read from the file and stored in an in-memory list, which is then used by spc_host_check( ) (we'll describe that function shortly). Access restriction information read by spc_host_init( ) is required to be in a very specific format. Whitespace is mostly ignored, and lines beginning with a hash mark (#) or a semicolon (;) are considered comments and ignored. Any other line in the file must begin with either "allow:" or "deny:" to indicate the type of rule. Following the rule type is a whitespace-separated list of addresses that are to be either allowed or denied access. Addresses may be hostnames or IP addresses. IP addresses may be specified as an address and mask or simply as an address. In the former case, the address may contain up to four parts, where each part must be expressed in decimal (ranging from 0 to 255), and a period (.) must be used to separate them. A forward slash (/) separates the address from the mask, and the mask is expressed as the number of bits to set. Table 8-1 lists example representations that are accepted as valid.
If any errors are encountered when parsing the access restriction data file, a message containing the name of the file and the line number is printed. Parsing of the file then continues on the next line. Fatal errors (e.g., out of memory) are also noted in a similar fashion, but parsing terminates immediately and any data successfully parsed so far is thrown away. When spc_host_init( ) completes successfully (even if parse errors are encountered), it will return 1; otherwise, it will return 0. #define SPC_HOST_ALLOW 1 #define SPC_HOST_DENY 0 typedef struct { int action; char *name; in_addr_t addr; in_addr_t mask; } spc_hostrule_t; static int spc_host_rulecount; static spc_hostrule_t *spc_host_rules; static int add_rule(spc_hostrule_t *rule) { spc_hostrule_t *tmp; if (!(spc_host_rulecount % 256)) { if (!(tmp = (spc_hostrule_t *)realloc(spc_host_rules, sizeof(spc_host_rulecount) * (spc_host_rulecount + 256)))) return 0; spc_host_rules = tmp; } spc_host_rules[spc_host_rulecount++] = *rule; return 1; } static void free_rules(void) { int i; if (spc_host_rules) { for (i = 0; i < spc_host_rulecount; i++) if (spc_host_rules[i].name) free(spc_host_rules[i].name); free(spc_host_rules); spc_host_rulecount = 0; spc_host_rules = 0; } } static in_addr_t parse_addr(char *str) { int shift = 24; char *tmp; in_addr_t addr = 0; for (tmp = str; *tmp; tmp++) { if (*tmp = = '.') { *tmp = 0; addr |= (atoi(str) << shift); str = tmp + 1; if ((shift -= 8) < 0) return INADDR_NONE; } else if (!isdigit(*tmp)) return INADDR_NONE; } addr |= (atoi(str) << shift); return htonl(addr); } static in_addr_t make_mask(int bits) { in_addr_t mask; bits = (bits < 0 ? 0 : (bits > 32 ? 32 : bits)); for (mask = 0; bits--; mask |= (1 << (31 - bits))); return htonl(mask); } int spc_host_init(const char *filename) { int lineno = 0; char *buf, *p, *slash, *tmp; FILE *f; size_t bufsz, len = 0; spc_hostrule_t rule; if (!(f = fopen(filename, "r"))) return 0; if (!(buf = (char *)malloc(256))) { fclose(f); return 0; } while (fgets(buf + len, bufsz - len, f) != 0) { len += strlen(buf + len); if (buf[len - 1] != '\n') { if (!(buf = (char *)realloc((tmp = buf), bufsz += 256))) { fprintf(stderr, "%s line %d: out of memory\n", filename, ++lineno); free(tmp); fclose(f); free_rules( ); return 0; } continue; } buf[--len] = 0; lineno++; for (tmp = buf; *tmp && isspace(*tmp); tmp++) len--; while (len && isspace(tmp[len - 1])) len--; tmp[len] = 0; len = 0; if (!tmp[0] || tmp[0] = = '#' || tmp[0] = = ';') continue; memset(&rule, 0, sizeof(rule)); if (strncasecmp(tmp, "allow:", 6) && strncasecmp(tmp, "deny:", 5)) { fprintf(stderr, "%s line %d: parse error; continuing anyway.\n", filename, lineno); continue; } if (!strncasecmp(tmp, "deny:", 5)) { rule.action = SPC_HOST_DENY; tmp += 5; } else { rule.action = SPC_HOST_ALLOW; tmp += 6; } while (*tmp && isspace(*tmp)) tmp++; if (!*tmp) { fprintf(stderr, "%s line %d: parse error; continuing anyway.\n", filename, lineno); continue; } for (p = tmp; *p; tmp = p) { while (*p && !isspace(*p)) p++; if (*p) *p++ = 0; if ((slash = strchr(tmp, '/')) != 0) { *slash++ = 0; rule.name = 0; rule.addr = parse_addr(tmp); rule.mask = make_mask(atoi(slash)); } else { if (inet_addr(tmp) = = INADDR_NONE) rule.name = strdup(tmp); else { rule.name = 0; rule.addr = inet_addr(tmp); rule.mask = 0xFFFFFFFF; } } if (!add_rule(&rule)) { fprintf(stderr, "%s line %d: out of memory\n", filename, lineno); free(buf); fclose(f); free_rules( ); return 0; } } } free(buf); fclose(f); return 1; } Finally, the function spc_host_check( ) performs access restriction checks. If the remote connection should be allowed, the return will be 0. If some kind of error unrelated to access restriction occurs (e.g., out of memory, bad socket descriptor, etc.), the return will be -1. Otherwise, one of the following error constants may be returned:
The function spc_host_check( ) has the following signature: int spc_host_check(int sockfd, int strict, int action); This function has the following arguments:
You may use spc_host_check( ) without using spc_host_init( ), in which case it will essentially only perform DNS spoofing checks. If you do not use spc_host_init( ), spc_host_check( ) will have an empty rule set, and it will always use the default action if the remote connection passes the DNS spoofing checks. int spc_host_check(int sockfd, int strict, int action) { int i, rc; char *hostname; struct sockaddr_in addr; if ((rc = check_spoofdns(sockfd, &addr, &hostname)) = = -1) return -1; if (rc && (rc != SPC_ERROR_NOREVERSE || strict)) return rc; for (i = 0; i < spc_host_rulecount; i++) { if (spc_host_rules[i].name) { if (hostname && !strcasecmp(hostname, spc_host_rules[i].name)) { free(hostname); return (spc_host_rules[i].action = = SPC_HOST_ALLOW); } } else { if ((addr.sin_addr.s_addr & spc_host_rules[i].mask) = = spc_host_rules[i].addr) { free(hostname); return (spc_host_rules[i].action = = SPC_HOST_ALLOW); } } } if (hostname) free(hostname); return (action = = SPC_HOST_ALLOW); } |