/*
** rblcheck 1.5 - Command-line interface to RBL-style filters.
** Copyright (C) 1997, 1998, 1999, 2000, 2001,
** Edward S. Marshall <esm@logic.net>
**
** 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 <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <arpa/inet.h>
#include <resolv.h>
#include <netdb.h>
#include <unistd.h>
/*-- PORTABILITY ------------------------------------------------------------*/
#ifdef STDC_HEADERS
#include <stdlib.h>
#endif
/* getopt() handling, for Linux (and probably others) */
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
/* NeXT puts optind's declaration in libc.h. */
#ifdef HAVE_LIBC_H
#include <libc.h>
#endif
/* FreeBSD 3.x needs this. */
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#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 <service>] <address> [ <address> ... ]\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 <service> Add a new service to the RBL services list\n\
-h, -? Display this help message\n\
-v Display version information\n\
<address> 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 <zone>'?)\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 */
syntax highlighted by Code2HTML, v. 0.9.1