379 lines
9.9 KiB
C
379 lines
9.9 KiB
C
|
/* rblcheck.c
|
||
|
dnsbl (rbl) checker application
|
||
|
|
||
|
Copyright (C) 2005 Michael Tokarev <mjt@corpit.ru>
|
||
|
This file is part of UDNS library, an async DNS stub resolver.
|
||
|
|
||
|
This library is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU Lesser General Public
|
||
|
License as published by the Free Software Foundation; either
|
||
|
version 2.1 of the License, or (at your option) any later version.
|
||
|
|
||
|
This library 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
|
||
|
Lesser General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU Lesser General Public
|
||
|
License along with this library, in file named COPYING.LGPL; if not,
|
||
|
write to the Free Software Foundation, Inc., 59 Temple Place,
|
||
|
Suite 330, Boston, MA 02111-1307 USA
|
||
|
|
||
|
*/
|
||
|
|
||
|
#ifdef HAVE_CONFIG_H
|
||
|
# include "config.h"
|
||
|
#endif
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#ifdef WINDOWS
|
||
|
# include <winsock2.h>
|
||
|
#else
|
||
|
# include <unistd.h>
|
||
|
# include <sys/types.h>
|
||
|
# include <sys/socket.h>
|
||
|
# include <netinet/in.h>
|
||
|
#endif
|
||
|
#include <time.h>
|
||
|
#include <errno.h>
|
||
|
#include <stdarg.h>
|
||
|
#include "udns.h"
|
||
|
|
||
|
#ifndef HAVE_GETOPT
|
||
|
# include "getopt.c"
|
||
|
#endif
|
||
|
|
||
|
static const char *version = "udns-rblcheck 0.4";
|
||
|
static char *progname;
|
||
|
|
||
|
static void error(int die, const char *fmt, ...) {
|
||
|
va_list ap;
|
||
|
fprintf(stderr, "%s: ", progname);
|
||
|
va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap);
|
||
|
putc('\n', stderr);
|
||
|
fflush(stderr);
|
||
|
if (die)
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
struct rblookup {
|
||
|
struct ipcheck *parent;
|
||
|
struct in_addr key;
|
||
|
const char *zone;
|
||
|
struct dns_rr_a4 *addr;
|
||
|
struct dns_rr_txt *txt;
|
||
|
};
|
||
|
|
||
|
struct ipcheck {
|
||
|
const char *name;
|
||
|
int naddr;
|
||
|
int listed;
|
||
|
struct rblookup *lookup;
|
||
|
};
|
||
|
|
||
|
#define notlisted ((void*)1)
|
||
|
|
||
|
static int nzones, nzalloc;
|
||
|
static const char **zones;
|
||
|
|
||
|
static int do_txt;
|
||
|
static int stopfirst;
|
||
|
static int verbose = 1;
|
||
|
/* verbosity level:
|
||
|
* <0 - only bare As/TXTs
|
||
|
* 0 - what RBL result
|
||
|
* 1(default) - what is listed by RBL: result
|
||
|
* 2 - what is[not ]listed by RBL: result, name lookups
|
||
|
*/
|
||
|
|
||
|
static int listed;
|
||
|
static int failures;
|
||
|
|
||
|
static void *ecalloc(int size, int cnt) {
|
||
|
void *t = calloc(size, cnt);
|
||
|
if (!t)
|
||
|
error(1, "out of memory");
|
||
|
return t;
|
||
|
}
|
||
|
|
||
|
static void addzone(const char *zone) {
|
||
|
if (nzones >= nzalloc) {
|
||
|
const char **zs = (const char**)ecalloc(sizeof(char*), (nzalloc += 16));
|
||
|
if (zones) {
|
||
|
memcpy(zs, zones, nzones * sizeof(char*));
|
||
|
free(zones);
|
||
|
}
|
||
|
zones = zs;
|
||
|
}
|
||
|
zones[nzones++] = zone;
|
||
|
}
|
||
|
|
||
|
static int addzonefile(const char *fname) {
|
||
|
FILE *f = fopen(fname, "r");
|
||
|
char linebuf[2048];
|
||
|
if (!f)
|
||
|
return 0;
|
||
|
while(fgets(linebuf, sizeof(linebuf), f)) {
|
||
|
char *p = linebuf, *e;
|
||
|
while(*p == ' ' || *p == '\t') ++p;
|
||
|
if (*p == '#' || *p == '\n') continue;
|
||
|
e = p;
|
||
|
while(*e && *e != ' ' && *e != '\t' && *e != '\n')
|
||
|
++e;
|
||
|
*e++ = '\0';
|
||
|
p = memcpy(ecalloc(e - p, 1), p, e - p); // strdup
|
||
|
addzone(p);
|
||
|
}
|
||
|
fclose(f);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static void dnserror(struct rblookup *ipl, const char *what) {
|
||
|
char buf[4*4];
|
||
|
error(0, "unable to %s for %s (%s): %s",
|
||
|
what, dns_ntop(AF_INET, &ipl->key, buf, sizeof(buf)),
|
||
|
ipl->zone, dns_strerror(dns_status(0)));
|
||
|
++failures;
|
||
|
}
|
||
|
|
||
|
static void display_result(struct ipcheck *ipc) {
|
||
|
int j;
|
||
|
struct rblookup *l, *le;
|
||
|
char buf[4*4];
|
||
|
if (!ipc->naddr) return;
|
||
|
for (l = ipc->lookup, le = l + nzones * ipc->naddr; l < le; ++l) {
|
||
|
if (!l->addr) continue;
|
||
|
if (verbose < 2 && l->addr == notlisted) continue;
|
||
|
if (verbose >= 0) {
|
||
|
dns_ntop(AF_INET, &l->key, buf, sizeof(buf));
|
||
|
if (ipc->name) printf("%s[%s]", ipc->name, buf);
|
||
|
else printf("%s", buf);
|
||
|
}
|
||
|
if (l->addr == notlisted) {
|
||
|
printf(" is NOT listed by %s\n", l->zone);
|
||
|
continue;
|
||
|
}
|
||
|
else if (verbose >= 1)
|
||
|
printf(" is listed by %s: ", l->zone);
|
||
|
else if (verbose >= 0)
|
||
|
printf(" %s ", l->zone);
|
||
|
if (verbose >= 1 || !do_txt)
|
||
|
for (j = 0; j < l->addr->dnsa4_nrr; ++j)
|
||
|
printf("%s%s", j ? " " : "",
|
||
|
dns_ntop(AF_INET, &l->addr->dnsa4_addr[j], buf, sizeof(buf)));
|
||
|
if (!do_txt) ;
|
||
|
else if (l->txt) {
|
||
|
for(j = 0; j < l->txt->dnstxt_nrr; ++j) {
|
||
|
unsigned char *t = l->txt->dnstxt_txt[j].txt;
|
||
|
unsigned char *e = t + l->txt->dnstxt_txt[j].len;
|
||
|
printf("%s\"", verbose > 0 ? "\n\t" : j ? " " : "");
|
||
|
while(t < e) {
|
||
|
if (*t < ' ' || *t >= 127) printf("\\x%02x", *t);
|
||
|
else if (*t == '\\' || *t == '"') printf("\\%c", *t);
|
||
|
else putchar(*t);
|
||
|
++t;
|
||
|
}
|
||
|
putchar('"');
|
||
|
}
|
||
|
free(l->txt);
|
||
|
}
|
||
|
else
|
||
|
printf("%s<no text available>", verbose > 0 ? "\n\t" : "");
|
||
|
free(l->addr);
|
||
|
putchar('\n');
|
||
|
}
|
||
|
free(ipc->lookup);
|
||
|
}
|
||
|
|
||
|
static void txtcb(struct dns_ctx *ctx, struct dns_rr_txt *r, void *data) {
|
||
|
struct rblookup *ipl = data;
|
||
|
if (r) {
|
||
|
ipl->txt = r;
|
||
|
++ipl->parent->listed;
|
||
|
}
|
||
|
else if (dns_status(ctx) != DNS_E_NXDOMAIN)
|
||
|
dnserror(ipl, "lookup DNSBL TXT record");
|
||
|
}
|
||
|
|
||
|
static void a4cb(struct dns_ctx *ctx, struct dns_rr_a4 *r, void *data) {
|
||
|
struct rblookup *ipl = data;
|
||
|
if (r) {
|
||
|
ipl->addr = r;
|
||
|
++listed;
|
||
|
if (do_txt) {
|
||
|
if (dns_submit_a4dnsbl_txt(0, &ipl->key, ipl->zone, txtcb, ipl))
|
||
|
return;
|
||
|
dnserror(ipl, "submit DNSBL TXT record");
|
||
|
}
|
||
|
++ipl->parent->listed;
|
||
|
}
|
||
|
else if (dns_status(ctx) != DNS_E_NXDOMAIN)
|
||
|
dnserror(ipl, "lookup DNSBL A record");
|
||
|
else
|
||
|
ipl->addr = notlisted;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
submit_a_queries(struct ipcheck *ipc,
|
||
|
int naddr, const struct in_addr *addr) {
|
||
|
int z, a;
|
||
|
struct rblookup *rl = ecalloc(sizeof(*rl), nzones * naddr);
|
||
|
ipc->lookup = rl;
|
||
|
ipc->naddr = naddr;
|
||
|
for(a = 0; a < naddr; ++a) {
|
||
|
for(z = 0; z < nzones; ++z) {
|
||
|
rl->key = addr[a];
|
||
|
rl->zone = zones[z];
|
||
|
rl->parent = ipc;
|
||
|
if (!dns_submit_a4dnsbl(0, &rl->key, rl->zone, a4cb, rl))
|
||
|
dnserror(rl, "submit DNSBL A query");
|
||
|
++rl;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void namecb(struct dns_ctx *ctx, struct dns_rr_a4 *rr, void *data) {
|
||
|
struct ipcheck *ipc = data;
|
||
|
if (rr) {
|
||
|
submit_a_queries(ipc, rr->dnsa4_nrr, rr->dnsa4_addr);
|
||
|
free(rr);
|
||
|
}
|
||
|
else {
|
||
|
error(0, "unable to lookup `%s': %s",
|
||
|
ipc->name, dns_strerror(dns_status(ctx)));
|
||
|
++failures;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int submit(struct ipcheck *ipc) {
|
||
|
struct in_addr addr;
|
||
|
if (dns_pton(AF_INET, ipc->name, &addr) > 0) {
|
||
|
submit_a_queries(ipc, 1, &addr);
|
||
|
ipc->name = NULL;
|
||
|
}
|
||
|
else if (!dns_submit_a4(0, ipc->name, 0, namecb, ipc)) {
|
||
|
error(0, "unable to submit name query for %s: %s\n",
|
||
|
ipc->name, dns_strerror(dns_status(0)));
|
||
|
++failures;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void waitdns(struct ipcheck *ipc) {
|
||
|
struct timeval tv;
|
||
|
fd_set fds;
|
||
|
int c;
|
||
|
int fd = dns_sock(NULL);
|
||
|
time_t now = 0;
|
||
|
FD_ZERO(&fds);
|
||
|
while((c = dns_timeouts(NULL, -1, now)) > 0) {
|
||
|
FD_SET(fd, &fds);
|
||
|
tv.tv_sec = c;
|
||
|
tv.tv_usec = 0;
|
||
|
c = select(fd+1, &fds, NULL, NULL, &tv);
|
||
|
now = time(NULL);
|
||
|
if (c > 0)
|
||
|
dns_ioevent(NULL, now);
|
||
|
if (stopfirst && ipc->listed)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int main(int argc, char **argv) {
|
||
|
int c;
|
||
|
struct ipcheck ipc;
|
||
|
char *nameserver = NULL;
|
||
|
int zgiven = 0;
|
||
|
|
||
|
if (!(progname = strrchr(argv[0], '/'))) progname = argv[0];
|
||
|
else argv[0] = ++progname;
|
||
|
|
||
|
while((c = getopt(argc, argv, "hqtvms:S:cn:")) != EOF) switch(c) {
|
||
|
case 's': ++zgiven; addzone(optarg); break;
|
||
|
case 'S':
|
||
|
++zgiven;
|
||
|
if (addzonefile(optarg)) break;
|
||
|
error(1, "unable to read zonefile `%s'", optarg);
|
||
|
case 'c': ++zgiven; nzones = 0; break;
|
||
|
case 'q': --verbose; break;
|
||
|
case 'v': ++verbose; break;
|
||
|
case 't': do_txt = 1; break;
|
||
|
case 'n': nameserver = optarg; break;
|
||
|
case 'm': ++stopfirst; break;
|
||
|
case 'h':
|
||
|
printf("%s: %s (udns library version %s).\n",
|
||
|
progname, version, dns_version());
|
||
|
printf("Usage is: %s [options] address..\n", progname);
|
||
|
printf(
|
||
|
"Where options are:\n"
|
||
|
" -h - print this help and exit\n"
|
||
|
" -s service - add the service (DNSBL zone) to the serice list\n"
|
||
|
" -S service-file - add the DNSBL zone(s) read from the given file\n"
|
||
|
" -c - clear service list\n"
|
||
|
" -v - increase verbosity level (more -vs => more verbose)\n"
|
||
|
" -q - decrease verbosity level (opposite of -v)\n"
|
||
|
" -t - obtain and print TXT records if any\n"
|
||
|
" -m - stop checking after first address match in any list\n"
|
||
|
" -n ipaddr - use the given nameserver instead of the default\n"
|
||
|
"(if no -s or -S option is given, use $RBLCHECK_ZONES, ~/.rblcheckrc\n"
|
||
|
"or /etc/rblcheckrc in that order)\n"
|
||
|
);
|
||
|
return 0;
|
||
|
default:
|
||
|
error(1, "use `%s -h' for help", progname);
|
||
|
}
|
||
|
|
||
|
if (!zgiven) {
|
||
|
char *s = getenv("RBLCHECK_ZONES");
|
||
|
if (s) {
|
||
|
char *k;
|
||
|
s = strdup(s);
|
||
|
for(k = strtok(s, " \t"); k; k = strtok(NULL, " \t"))
|
||
|
addzone(k);
|
||
|
free(s);
|
||
|
}
|
||
|
else { /* probably worthless on windows? */
|
||
|
char *path;
|
||
|
char *home = getenv("HOME");
|
||
|
if (!home) home = ".";
|
||
|
path = malloc(strlen(home) + 1 + sizeof(".rblcheckrc"));
|
||
|
sprintf(path, "%s/.rblcheckrc", home);
|
||
|
if (!addzonefile(path))
|
||
|
addzonefile("/etc/rblcheckrc");
|
||
|
free(path);
|
||
|
}
|
||
|
}
|
||
|
if (!nzones)
|
||
|
error(1, "no service (zone) list specified (-s or -S option)");
|
||
|
|
||
|
argv += optind;
|
||
|
argc -= optind;
|
||
|
|
||
|
if (!argc)
|
||
|
return 0;
|
||
|
|
||
|
if (dns_init(NULL, 0) < 0)
|
||
|
error(1, "unable to initialize DNS library: %s", strerror(errno));
|
||
|
if (nameserver) {
|
||
|
dns_add_serv(NULL, NULL);
|
||
|
if (dns_add_serv(NULL, nameserver) < 0)
|
||
|
error(1, "wrong IP address for a nameserver: `%s'", nameserver);
|
||
|
}
|
||
|
if (dns_open(NULL) < 0)
|
||
|
error(1, "unable to initialize DNS library: %s", strerror(errno));
|
||
|
|
||
|
for (c = 0; c < argc; ++c) {
|
||
|
if (c && (verbose > 1 || (verbose == 1 && do_txt))) putchar('\n');
|
||
|
memset(&ipc, 0, sizeof(ipc));
|
||
|
ipc.name = argv[c];
|
||
|
submit(&ipc);
|
||
|
waitdns(&ipc);
|
||
|
display_result(&ipc);
|
||
|
if (stopfirst > 1 && listed) break;
|
||
|
}
|
||
|
|
||
|
return listed ? 100 : failures ? 2 : 0;
|
||
|
}
|