309 lines
8.1 KiB
C
309 lines
8.1 KiB
C
|
/*
|
||
|
* Blocklist Copyright 2013 Regents of the University of Michigan
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||
|
* use this file except in compliance with the License. You may obtain a copy
|
||
|
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*/
|
||
|
|
||
|
#include <errno.h>
|
||
|
#include <stdio.h>
|
||
|
#include <assert.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <stdbool.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include <sys/socket.h>
|
||
|
#include <netinet/in.h>
|
||
|
#include <arpa/inet.h>
|
||
|
#include <netdb.h>
|
||
|
|
||
|
#include "blocklist.h"
|
||
|
#include "constraint.h"
|
||
|
#include "logger.h"
|
||
|
#include "xalloc.h"
|
||
|
|
||
|
#define ADDR_DISALLOWED 0
|
||
|
#define ADDR_ALLOWED 1
|
||
|
|
||
|
typedef struct bl_linked_list {
|
||
|
bl_cidr_node_t *first;
|
||
|
bl_cidr_node_t *last;
|
||
|
uint32_t len;
|
||
|
} bl_ll_t;
|
||
|
|
||
|
static constraint_t *constraint = NULL;
|
||
|
|
||
|
// keep track of the prefixes we've tried to BL/WL
|
||
|
// for logging purposes
|
||
|
static bl_ll_t *blocklisted_cidrs = NULL;
|
||
|
static bl_ll_t *allowlisted_cidrs = NULL;
|
||
|
|
||
|
void bl_ll_add(bl_ll_t *l, struct in_addr addr, uint16_t p)
|
||
|
{
|
||
|
assert(l);
|
||
|
bl_cidr_node_t *new = xmalloc(sizeof(bl_cidr_node_t));
|
||
|
new->next = NULL;
|
||
|
new->ip_address = addr.s_addr;
|
||
|
new->prefix_len = p;
|
||
|
if (!l->first) {
|
||
|
l->first = new;
|
||
|
} else {
|
||
|
l->last->next = new;
|
||
|
}
|
||
|
l->last = new;
|
||
|
l->len++;
|
||
|
}
|
||
|
|
||
|
bl_cidr_node_t *get_blocklisted_cidrs(void) { return blocklisted_cidrs->first; }
|
||
|
|
||
|
bl_cidr_node_t *get_allowlisted_cidrs(void) { return allowlisted_cidrs->first; }
|
||
|
|
||
|
uint32_t blocklist_lookup_index(uint64_t index)
|
||
|
{
|
||
|
return ntohl(constraint_lookup_index(constraint, index, ADDR_ALLOWED));
|
||
|
}
|
||
|
|
||
|
// check whether a single IP address is allowed to be scanned.
|
||
|
// 1 => is allowed
|
||
|
// 0 => is not allowed
|
||
|
int blocklist_is_allowed(uint32_t s_addr)
|
||
|
{
|
||
|
return constraint_lookup_ip(constraint, ntohl(s_addr)) == ADDR_ALLOWED;
|
||
|
}
|
||
|
|
||
|
static void _add_constraint(struct in_addr addr, int prefix_len, int value)
|
||
|
{
|
||
|
constraint_set(constraint, ntohl(addr.s_addr), prefix_len, value);
|
||
|
if (value == ADDR_ALLOWED) {
|
||
|
bl_ll_add(allowlisted_cidrs, addr, prefix_len);
|
||
|
} else if (value == ADDR_DISALLOWED) {
|
||
|
bl_ll_add(blocklisted_cidrs, addr, prefix_len);
|
||
|
} else {
|
||
|
log_fatal("blocklist",
|
||
|
"unknown type of blocklist operation specified");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// blocklist a CIDR network allocation
|
||
|
// e.g. blocklist_add("128.255.134.0", 24)
|
||
|
void blocklist_prefix(char *ip, int prefix_len)
|
||
|
{
|
||
|
struct in_addr addr;
|
||
|
addr.s_addr = inet_addr(ip);
|
||
|
_add_constraint(addr, prefix_len, ADDR_DISALLOWED);
|
||
|
}
|
||
|
|
||
|
// allowlist a CIDR network allocation
|
||
|
void allowlist_prefix(char *ip, int prefix_len)
|
||
|
{
|
||
|
struct in_addr addr;
|
||
|
addr.s_addr = inet_addr(ip);
|
||
|
_add_constraint(addr, prefix_len, ADDR_ALLOWED);
|
||
|
}
|
||
|
|
||
|
static int is_ip_ipv6(char *ip) {
|
||
|
// don't modify the input string
|
||
|
char *new_str = strdup(ip);
|
||
|
// check if there's a subnet mask_char
|
||
|
char *mask_char = strchr(new_str, '/');
|
||
|
if (mask_char != NULL) {
|
||
|
// set mask_char char to NULL char, so we can check if subnet is valid IPv6
|
||
|
*mask_char = '\0';
|
||
|
}
|
||
|
// attempt conversion of IP into IPv6 struct to check if IP is an IPv6 address
|
||
|
struct in6_addr ipv6_addr;
|
||
|
if (inet_pton(AF_INET6, new_str, &ipv6_addr) == 1) {
|
||
|
free(new_str);
|
||
|
return true;
|
||
|
}
|
||
|
free(new_str);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static int init_from_string(char *ip, int value)
|
||
|
{
|
||
|
if (is_ip_ipv6(ip)) {
|
||
|
log_debug("constraint", "ignoring IPv6 IP/subnet: %s", ip);
|
||
|
return 0;
|
||
|
}
|
||
|
int prefix_len = 32;
|
||
|
char *slash = strchr(ip, '/');
|
||
|
if (slash) { // split apart network and prefix length
|
||
|
*slash = '\0';
|
||
|
char *end;
|
||
|
char *len = slash + 1;
|
||
|
errno = 0;
|
||
|
prefix_len = strtol(len, &end, 10);
|
||
|
if (end == len || errno != 0 || prefix_len < 0 ||
|
||
|
prefix_len > 32) {
|
||
|
log_fatal("constraint",
|
||
|
"'%s' is not a valid IPv4 prefix length", len);
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
struct in_addr addr;
|
||
|
int ret = -1;
|
||
|
if (inet_aton(ip, &addr) == 0) {
|
||
|
// Not an IP and not a CIDR block, try dns resolution
|
||
|
struct addrinfo hint, *res;
|
||
|
memset(&hint, 0, sizeof(hint));
|
||
|
hint.ai_family = PF_INET;
|
||
|
int r = getaddrinfo(ip, NULL, &hint, &res);
|
||
|
if (r) {
|
||
|
log_error("constraint",
|
||
|
"'%s' is not a valid IP "
|
||
|
"address or hostname",
|
||
|
ip);
|
||
|
return -1;
|
||
|
}
|
||
|
// Got some addrinfo, let's see what happens
|
||
|
for (struct addrinfo *aip = res; aip; aip = aip->ai_next) {
|
||
|
if (aip->ai_family != AF_INET) {
|
||
|
continue;
|
||
|
}
|
||
|
struct sockaddr_in *sa =
|
||
|
(struct sockaddr_in *)aip->ai_addr;
|
||
|
memcpy(&addr, &sa->sin_addr, sizeof(addr));
|
||
|
log_debug("constraint", "%s retrieved by hostname",
|
||
|
inet_ntoa(addr));
|
||
|
ret = 0;
|
||
|
_add_constraint(addr, prefix_len, value);
|
||
|
}
|
||
|
} else {
|
||
|
_add_constraint(addr, prefix_len, value);
|
||
|
return 0;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int init_from_file(char *file, const char *name, int value,
|
||
|
int ignore_invalid_hosts)
|
||
|
{
|
||
|
FILE *fp;
|
||
|
char line[1000];
|
||
|
|
||
|
fp = fopen(file, "r");
|
||
|
if (fp == NULL) {
|
||
|
log_fatal(name, "unable to open %s file: %s: %s", name, file,
|
||
|
strerror(errno));
|
||
|
}
|
||
|
|
||
|
while (fgets(line, sizeof(line), fp) != NULL) {
|
||
|
char *comment = strchr(line, '#');
|
||
|
if (comment) {
|
||
|
*comment = '\0';
|
||
|
}
|
||
|
// hostnames can be up to 255 bytes
|
||
|
char ip[256];
|
||
|
if ((sscanf(line, "%255s", ip)) == EOF) {
|
||
|
continue;
|
||
|
}
|
||
|
if (init_from_string(ip, value)) {
|
||
|
if (!ignore_invalid_hosts) {
|
||
|
log_fatal(name, "unable to parse %s file: %s",
|
||
|
name, file);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
fclose(fp);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void init_from_array(char **cidrs, size_t len, int value,
|
||
|
int ignore_invalid_hosts)
|
||
|
{
|
||
|
for (int i = 0; i < (int)len; i++) {
|
||
|
int ret = init_from_string(cidrs[i], value);
|
||
|
if (ret && !ignore_invalid_hosts) {
|
||
|
log_fatal("constraint",
|
||
|
"Unable to init from CIDR list");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uint64_t blocklist_count_allowed(void)
|
||
|
{
|
||
|
assert(constraint);
|
||
|
return constraint_count_ips(constraint, ADDR_ALLOWED);
|
||
|
}
|
||
|
|
||
|
uint64_t blocklist_count_not_allowed(void)
|
||
|
{
|
||
|
assert(constraint);
|
||
|
return constraint_count_ips(constraint, ADDR_DISALLOWED);
|
||
|
}
|
||
|
|
||
|
// network order
|
||
|
uint32_t blocklist_ip_to_index(uint32_t ip)
|
||
|
{
|
||
|
assert(constraint);
|
||
|
uint32_t ip_hostorder = ntohl(ip);
|
||
|
return constraint_lookup_ip(constraint, ip_hostorder);
|
||
|
}
|
||
|
|
||
|
// Initialize address constraints from allowlist and blocklist files.
|
||
|
// Either can be set to NULL to omit.
|
||
|
int blocklist_init(char *allowlist_filename, char *blocklist_filename,
|
||
|
char **allowlist_entries, size_t allowlist_entries_len,
|
||
|
char **blocklist_entries, size_t blocklist_entries_len,
|
||
|
int ignore_invalid_hosts)
|
||
|
{
|
||
|
assert(!constraint);
|
||
|
|
||
|
blocklisted_cidrs = xcalloc(1, sizeof(bl_ll_t));
|
||
|
allowlisted_cidrs = xcalloc(1, sizeof(bl_ll_t));
|
||
|
|
||
|
if (allowlist_filename && allowlist_entries) {
|
||
|
log_warn("allowlist",
|
||
|
"both a allowlist file and destination addresses "
|
||
|
"were specified. The union of these two sources "
|
||
|
"will be utilized.");
|
||
|
}
|
||
|
if (allowlist_filename || allowlist_entries_len > 0) {
|
||
|
// using a allowlist, so default to allowing nothing
|
||
|
constraint = constraint_init(ADDR_DISALLOWED);
|
||
|
log_debug("constraint", "blocklisting 0.0.0.0/0");
|
||
|
if (allowlist_filename) {
|
||
|
init_from_file(allowlist_filename, "allowlist",
|
||
|
ADDR_ALLOWED, ignore_invalid_hosts);
|
||
|
}
|
||
|
if (allowlist_entries) {
|
||
|
init_from_array(allowlist_entries,
|
||
|
allowlist_entries_len, ADDR_ALLOWED,
|
||
|
ignore_invalid_hosts);
|
||
|
}
|
||
|
} else {
|
||
|
// no allowlist, so default to allowing everything
|
||
|
log_debug("blocklist",
|
||
|
"no allowlist file or allowlist entries provided");
|
||
|
constraint = constraint_init(ADDR_ALLOWED);
|
||
|
}
|
||
|
if (blocklist_filename) {
|
||
|
init_from_file(blocklist_filename, "blocklist", ADDR_DISALLOWED,
|
||
|
ignore_invalid_hosts);
|
||
|
}
|
||
|
if (blocklist_entries) {
|
||
|
init_from_array(blocklist_entries, blocklist_entries_len,
|
||
|
ADDR_DISALLOWED, ignore_invalid_hosts);
|
||
|
}
|
||
|
init_from_string(strdup("0.0.0.0"), ADDR_DISALLOWED);
|
||
|
constraint_paint_value(constraint, ADDR_ALLOWED);
|
||
|
uint64_t allowed = blocklist_count_allowed();
|
||
|
log_debug("constraint",
|
||
|
"%lu addresses (%0.0f%% of address "
|
||
|
"space) can be scanned",
|
||
|
allowed, allowed * 100. / ((long long int)1 << 32));
|
||
|
if (!allowed) {
|
||
|
log_error("blocklist",
|
||
|
"no addresses are eligible to be scanned in the "
|
||
|
"current configuration. This may be because the "
|
||
|
"blocklist being used by ZMap (%s) prevents "
|
||
|
"any addresses from receiving probe packets.",
|
||
|
blocklist_filename);
|
||
|
return EXIT_FAILURE;
|
||
|
}
|
||
|
return EXIT_SUCCESS;
|
||
|
}
|