#include "config.h" #ifdef EARLY_LOCKOUT #include #include #include #include #include #include #include #include "externs.h" #include "early-lockout.h" typedef struct ban BAN; struct ban { BAN *link; int af; void *addr; int width; char *text; } ; static struct stat laststat = { 0 }; static BAN *bans = 0; static const char *elr_why = "not yet set"; #define MAXLINE 256 static const char *address_text(int af, const void *addr) { static char rb[64]; switch (af) { case AF_INET: sprintf(&rb[0],"%d.%d.%d.%d", ((const unsigned char *)addr)[0], ((const unsigned char *)addr)[1], ((const unsigned char *)addr)[2], ((const unsigned char *)addr)[3] ); break; case AF_INET6: sprintf(&rb[0],"%x:%x:%x:%x:%x:%x:%x:%x", (((const unsigned char *)addr)[ 0]*256) + ((const unsigned char *)addr)[ 1], (((const unsigned char *)addr)[ 2]*256) + ((const unsigned char *)addr)[ 3], (((const unsigned char *)addr)[ 4]*256) + ((const unsigned char *)addr)[ 5], (((const unsigned char *)addr)[ 6]*256) + ((const unsigned char *)addr)[ 7], (((const unsigned char *)addr)[ 8]*256) + ((const unsigned char *)addr)[ 9], (((const unsigned char *)addr)[10]*256) + ((const unsigned char *)addr)[11], (((const unsigned char *)addr)[12]*256) + ((const unsigned char *)addr)[13], (((const unsigned char *)addr)[14]*256) + ((const unsigned char *)addr)[15] ); break; default: sprintf(&rb[0],"?af %d",af); break; } return(&rb[0]); } static void append_ban(int af, void *addr, int width, BAN ***tailp) { BAN *b; int nb; switch (af) { case AF_INET: nb = 4; break; case AF_INET6: nb = 16; break; default: return; break; } b = malloc(sizeof(BAN)); b->af = af; b->addr = malloc(nb); bcopy(addr,b->addr,nb); b->width = width; b->link = 0; b->text = dup_string(address_text(af,addr)); **tailp = b; *tailp = &b->link; } static void free_banlist(BAN *list) { BAN *b; while ((b = list)) { list = b->link; free(b->addr); free(b->text); free(b); } } static int early_lockout_reload(void) { FILE *f; static char iline[MAXLINE+1]; int l; int c; BAN *newlist; BAN **newtail; struct addrinfo hints; struct addrinfo *ai0; struct addrinfo *ai; int e; char *slash; int width; hints.ai_socktype = SOCK_STREAM ^ 1; f = fopen(EARLY_LOCKOUT_FILE,"r"); if (! f) { elr_why = "fopen failed"; return(1); } newlist = 0; newtail = &newlist; l = 0; while (1) { c = getc(f); if (c == EOF) { if (l || ferror(f)) { fclose(f); free_banlist(newlist); elr_why = "l || ferror(f)"; return(1); } break; } if (c == '\n') { if (l && (iline[0] != '#')) { if (hints.ai_socktype != SOCK_STREAM) { hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_NUMERICHOST; hints.ai_addrlen = 0; // XXX API botch hints.ai_addr = 0; // XXX API botch hints.ai_canonname = 0; // XXX API botch hints.ai_next = 0; // XXX API botch } iline[l] = '\0'; slash = index(&iline[0],'/'); if (slash) { *slash++ = '\0'; width = atoi(slash); if (width < 0) { fclose(f); free_banlist(newlist); elr_why = "invalid width"; return(1); } } else { width = -1; } e = getaddrinfo(&iline[0],0,&hints,&ai0); if (e) { fclose(f); free_banlist(newlist); elr_why = &iline[0]; return(1); } for (ai=ai0;ai;ai=ai->ai_next) { switch (ai->ai_family) { case AF_INET: if (width > 32) { freeaddrinfo(ai0); fclose(f); free_banlist(newlist); elr_why = "invalid IPv4 width"; return(1); } if (width < 0) width = 32; append_ban(AF_INET,&((struct sockaddr_in *)ai->ai_addr)->sin_addr,width,&newtail); break; case AF_INET6: if (width > 128) { freeaddrinfo(ai0); fclose(f); free_banlist(newlist); elr_why = "invalid IPv6 width"; return(1); } if (width < 0) width = 128; append_ban(AF_INET6,&((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr,width,&newtail); break; } } freeaddrinfo(ai0); } l = 0; } else if (l >= MAXLINE) { fclose(f); free_banlist(newlist); elr_why = "l > MAXLINE"; return(1); } else { iline[l++] = c; } } fstat(fileno(f),&laststat); fclose(f); free_banlist(bans); bans = newlist; elr_why = "success"; return(0); } static int lockout_v4_match(struct in_addr ba, struct in_addr ra, int width) { static struct in_addr masks[33] = { { 0 } }; if ((width < 0) || (width > 32)) abort(); if (masks[15].s_addr == 0) { int i; unsigned int m; for (i=0;i<=32;i++) { m = i ? 0xffffffffU << (32-i) : 0; ((unsigned char *)&masks[i].s_addr)[0] = (m >> 24) & 0xff; ((unsigned char *)&masks[i].s_addr)[1] = (m >> 16) & 0xff; ((unsigned char *)&masks[i].s_addr)[2] = (m >> 8) & 0xff; ((unsigned char *)&masks[i].s_addr)[3] = m & 0xff; } } return(((ba.s_addr^ra.s_addr)&masks[width].s_addr)==0); } static int lockout_v6_match(const void *bav, const void *rav, int width) { unsigned char m; const unsigned char *ba; const unsigned char *ra; if ((width < 0) || (width > 128)) abort(); ba = bav; ra = rav; if (width >= 8) { if (bcmp(ba,ra,width>>3)) return(0); ba += width >> 3; ra += width >> 3; width &= 7; } if (! width) return(1); m = (0xff << (8 - width)) & 0xff; return(((ba[0]^ra[0])&m)==0); } /* * The peculiar-looking access() calls here with their results ignored * are done to get notes into syscall traces. ktracing the muck will * show the pathname strings in NAMI lines, and the calls are * otherwise harmless, even if one of those peculiarly-named files * happens to exist. */ int early_lockout_check(const struct sockaddr_storage *ra) { struct stat stb; BAN *b; access("early_lockout_check",F_OK); if (stat(EARLY_LOCKOUT_FILE,&stb) < 0) { access("stat failed",F_OK); return(0); } if ( (stb.st_ino != laststat.st_ino) || (stb.st_dev != laststat.st_dev) || (stb.st_mode != laststat.st_mode) || (stb.st_mtime != laststat.st_mtime) || (stb.st_size != laststat.st_size) ) { access("reloading",F_OK); if (early_lockout_reload()) { access("reload failed",F_OK); access(elr_why,F_OK); return(0); } } access("address is",F_OK); switch (ra->ss_family) { case AF_INET: access(address_text(AF_INET,&((const struct sockaddr_in *)ra)->sin_addr),F_OK); break; case AF_INET6: access(address_text(AF_INET6,&((const struct sockaddr_in6 *)ra)->sin6_addr),F_OK); break; default: access("unknown AF",F_OK); break; } access("testing against list",F_OK); for (b=bans;b;b=b->link) { access(b->text,F_OK); if (b->af != ra->ss_family) { access("AF wrong",F_OK); continue; } switch (b->af) { case AF_INET: if (lockout_v4_match( *(const struct in_addr *)b->addr, ((const struct sockaddr_in *)ra)->sin_addr, b->width )) { access("INET match, returning 1",F_OK); return(1); } break; case AF_INET6: if (lockout_v6_match( b->addr, &((const struct sockaddr_in6 *)ra)->sin6_addr, b->width )) { access("INET6 match, returning 1",F_OK); return(1); } break; } } access("no match, returning 0",F_OK); return(0); } #endif