/* ** rblcheck 1.5 - Command-line interface to RBL-style filters. ** Copyright (C) 1997, 1998, 1999, 2000, 2001, ** Edward S. Marshall ** ** 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. ** ** $Id: rblcheck.c,v 1.14 2001/11/08 22:05:27 logic Exp $ ** */ #include "config.h" #include #include #include #include #include #include #include #include #include /*-- PORTABILITY ------------------------------------------------------------*/ #ifdef STDC_HEADERS #include #endif /* getopt() handling, for Linux (and probably others) */ #ifdef HAVE_GETOPT_H #include #endif /* NeXT puts optind's declaration in libc.h. */ #ifdef HAVE_LIBC_H #include #endif /* FreeBSD 3.x needs this. */ #ifdef HAVE_UNISTD_H #include #endif /* Unabashedly borrowed from the bind 8.1.1 sources, just in case, since bind 4.x defines these differently, and some don't even have this. */ #ifndef NS_INT16SZ #define NS_INT16SZ 2 #endif #ifndef NS_INT32SZ #define NS_INT32SZ 4 #endif #ifndef NS_GET16 #define NS_GET16(s, cp) { \ register unsigned char *t_cp = (unsigned char *)(cp); \ (s) = ((unsigned short)t_cp[0] << 8) \ | ((unsigned short)t_cp[1]) \ ; \ (cp) += NS_INT16SZ; \ } #endif #ifndef T_TXT #define T_TXT 16 #endif #ifndef PACKETSZ #define PACKETSZ 512 #endif /*-- LOCAL DEFINITIONS ------------------------------------------------------*/ #define RESULT_SIZE 4096 /* What is the longest result text we support? */ /*-- GLOBAL VARIABLES -------------------------------------------------------*/ /* Simple linked list to hold the sites we support. See sites.h. */ struct rbl { char * site; struct rbl * next; }; struct rbl * rblsites = NULL; /* Name the program was invoked as. */ const char * progname; /* Global options. */ int quiet = 0; int txt = 0; int firstmatch = 0; /*-- FUNCTIONS --------------------------------------------------------------*/ /* version() * Display the version of this program back to the user. */ void version() { fprintf( stderr, "%s %s\nCopyright (C) 1997, 1998, 1999, 2000, 2001 Edward S. Marshall\n", PACKAGE, VERSION ); } /* usage() * Display how to use this program back to the user. */ void usage() { version(); fprintf( stderr, "Usage: %s [-qtlcvh?] [-s ]
[
... ]\n\ \n\ -q Quiet mode; no output\n\ -t Print a TXT record, if any\n\ -m Stop checking after first address match in any list\n\ -l List default RBL services to check\n\ -c Clear the current list of RBL services\n\ -s Add a new service to the RBL services list\n\ -h, -? Display this help message\n\ -v Display version information\n\
An IP address to look up; specify `-' to read multiple\n\ addresses from standard input.\n", progname ); } /* togglesite() * This function takes the name of the site, and either adds it to the * list of sites to check, or removes it if it already exists. */ struct rbl * togglesite( char * sitename, struct rbl * sites ) { struct rbl * ptr; struct rbl * last = NULL; int sitelen; sitelen = strlen( sitename ); for( ptr = sites; ptr != NULL; last = ptr, ptr = ptr->next ) { if( ( strlen( ptr->site ) == sitelen ) && ( ! strcmp( ptr->site, sitename ) ) ) { if( last ) last->next = ptr->next; else sites = ptr->next; free( ptr->site ); free( ptr ); return sites; } } if( ! ( ptr = ( struct rbl * )malloc( sizeof( struct rbl ) ) ) ) { perror( "malloc failed" ); exit( 1 ); } if( last ) last->next = ptr; else sites = ptr; if( ( ptr->site = ( char * )malloc( sitelen + 1 ) ) ) strcpy( ptr->site, sitename ); else { perror( "malloc failed" ); exit( 1 ); } ptr->next = NULL; return sites; } /* rblcheck() * Checks the specified dotted-quad address against the provided RBL * domain. If "txt" is non-zero, we perform a TXT record lookup. We * return the text returned from a TXT match, or an empty string, on * a successful match, or NULL on an unsuccessful match. */ char * rblcheck( struct in_addr a, char * rbldomain, int txt ) { char * domain; char * result = NULL; u_char fixedans[ PACKETSZ ]; u_char * answer; const u_char * cp; u_char * rp; const u_char * cend; const u_char * rend; int len; u_char *p; int i, j; /* 16 characters max in a dotted-quad address, plus 1 for null */ domain = ( char * )malloc( 17 + strlen( rbldomain ) ); p = (u_char *)&a; /* Create a domain name, in reverse. */ sprintf( domain, "%d.%d.%d.%d.%s", p[3], p[2], p[1], p[0], rbldomain ); /* Make our DNS query. */ res_init(); answer = fixedans; len = res_query( domain, C_IN, T_A, answer, PACKETSZ ); /* Was there a problem? If so, the domain doesn't exist. */ if( len == -1 ) return result; if( len > PACKETSZ ) { answer = malloc( len ); len = res_query( domain, C_IN, T_A, answer, len ); if( len == -1 ) return result; } result = ( char * )malloc( RESULT_SIZE ); result[ 0 ] = '\0'; if( !txt ) { return result; } /* Make another DNS query for textual data; this shouldn't be a performance hit, since it'll now be cached at the nameserver we're using. */ res_init(); len = res_query( domain, C_IN, T_TXT, answer, PACKETSZ ); /* Just in case there's no TXT record... */ if( len == -1 ) { return result; } /* Skip the header and the address we queried. */ cp = answer + sizeof( HEADER ); while( *cp != '\0' ) { i = *cp++; while( i-- ) cp++; } /* This seems to be a bit of magic data that we need to skip. I wish there were good online documentation for programming for libresolv, so I'd know what I'm skipping here. Anyone reading this, feel free to enlighten me. */ cp += 1 + NS_INT16SZ + NS_INT32SZ; /* Skip the type, class and ttl. */ cp += ( NS_INT16SZ * 2 ) + NS_INT32SZ; /* Get the length and end of the buffer. */ NS_GET16( i, cp ); cend = cp + i; /* Iterate over any multiple answers we might have. In this context, it's unlikely, but anyway. */ rp = result; rend = result + RESULT_SIZE - 1; while( cp < cend && rp < rend ) { i = *cp++; if( i != 0 ) for( j = i; j > 0 && cp < cend && rp < rend; j-- ) { if( *cp == '\n' || *cp == '"' || *cp == '\\' ) { *rp++ = '\\'; } *rp++ = *cp++; } } *rp = '\0'; return result; } /* full_rblcheck * Takes an IP address, and feeds it to rblcheck() for each defined * RBL listing, handling output of results if necessary. */ int full_rblcheck( char * addr ) { struct hostent *ent; struct in_addr a; int count = 0; int rblchecking = 0; char * response; struct rbl * ptr; int fail; int c; for( ptr = rblsites; ptr != NULL; ptr = ptr->next ) { fail = 0; if ((ent = gethostbyname(addr)) != NULL) { memcpy(&a, ent->h_addr_list[0], sizeof(a)); if (ent->h_addr_list[1]) { if (!quiet && rblchecking == 0) fprintf(stderr, "%s resolved to mutiple addresses: ", addr); } if (!quiet && rblchecking++ == 0) fprintf(stderr, "checking %s\n", inet_ntoa(a)); } else { if (!inet_aton(addr, &a)) fail++; } if (fail) { fprintf( stderr, "%s: warning: invalid address `%s'\n", progname, addr ); return 0; } response = rblcheck( a, ptr->site, txt ); if( !quiet || response ) printf( "%s %s%s%s%s%s%s", addr, ( !quiet && !response ? "not " : "" ), ( !quiet ? "RBL filtered by " : "" ), ( !quiet ? ptr->site : "" ), ( txt && response && strlen( response ) && !quiet ? ": " : "" ), ( txt && response ? response : "" ), ( quiet && ( !txt || ( response && !strlen( response ) ) ) ? "" : "\n" ) ); if( response ) { count++; free( response ); } if( firstmatch && count ) return count; } return count; } /*-- MAINLINE ---------------------------------------------------------------*/ int main( argc, argv ) int argc; char **argv; { int a; struct rbl * ptr; int rblfiltered = 0; char inbuf[ RESULT_SIZE ]; /* Hack to handle the easy addition of sites at compile time. */ #define SITE(x) rblsites = togglesite( (x), rblsites ); #include "sites.h" #undef SITE progname = argv[ 0 ]; while( ( a = getopt( argc, argv, "qtlms:c?hv" ) ) != EOF ) switch( a ) { case 'q': /* Quiet mode. */ quiet = 1; break; case 't': /* Display TXT record. */ txt = 1; break; case 'm': /* Stop after first successful match. */ firstmatch = 1; break; case 'l': /* Display supported RBL systems. */ for( ptr = rblsites; ptr != NULL; ptr = ptr->next ) printf( "%s\n", ptr->site ); return 0; case 's': /* Toggle a particular zone. */ rblsites = togglesite( optarg, rblsites ); break; case 'c': /* Clear the rbl zones. */ ptr = rblsites; while( ptr != NULL ) { rblsites = ptr->next; free( ptr->site ); free( ptr ); ptr = rblsites; } break; case '?': case 'h': /* Help */ usage(); return 0; case 'v': /* Verision */ version(); return 0; } /* Did they tell us to check anything? */ if( optind == argc ) { fprintf( stderr, "%s: no IP address(es) specified\n", progname ); usage(); return -1; } /* Do we have any listings to search? */ if( !rblsites ) { fprintf( stderr, "%s: no rbl listing(s) specified (need `-s '?)\n", progname ); return 0; } /* Loop through the command line. */ while( optind < argc ) { /* Handle addresses from stdin. */ if( argv[ optind ][ 0 ] == '-' && argv[ optind ][ 1 ] == '\0' ) while( fgets( inbuf, RESULT_SIZE - 1, stdin ) != NULL ) { inbuf[ strlen( inbuf ) - 1 ] = '\0'; rblfiltered += full_rblcheck( inbuf ); if( firstmatch && rblfiltered ) return rblfiltered; } else rblfiltered += full_rblcheck( argv[ optind ] ); if( firstmatch && rblfiltered ) return rblfiltered; optind++; } return rblfiltered; } /* EOF */