#include #include #include #include #include #include #include #include #include #include extern const char *__progname; static const char *cachefile = "/home/mouse/.abcache"; static const char *abuse_net_domain = "contacts.abuse.net"; #define EXPIRETIME 31556952 /* one year */ typedef struct node NODE; struct node { NODE *up; int nkid; NODE **kid; int namelen; char *name; int naddrs; char **addrs; unsigned long int when; int dirty; } ; static int cfd; static NODE *cacheroot; static int cachedirty; static const char *querydom; static void usage(void) __attribute__((__noreturn__)); static void usage(void) { fprintf(stderr,"Usage: %s domain\n",__progname); exit(1); } static int ncread(void *fdp, char *data, int len) { return(read(*(int *)fdp,data,len)); } static int ncwrite(void *fdp, const char *data, int len) { return(write(*(int *)fdp,data,len)); } static unsigned long long int cfdpos(void) { return(lseek(cfd,0,SEEK_CUR)); } static int getsmall(FILE *f) { int c; int v; v = 0; while (1) { c = getc(f); switch (c) { case ':': return(v); break; case '0' ... '9': v = (v * 10) + (c - '0'); break; case EOF: fprintf(stderr,"%s: unexpected EOF in cache file (at %llu)\n",__progname,cfdpos()); return(-1); break; default: fprintf(stderr,"%s: invalid character %02x in small number in cache file at %llu\n",__progname,c,cfdpos()); return(-1); break; } } } static int getstamp(FILE *f, unsigned long int *loc) { int c; unsigned long int v; v = 0; while (1) { c = getc(f); switch (c) { case ':': *loc = v; return(1); break; case '0' ... '9': v = (v * 10) + (c - '0'); break; case EOF: fprintf(stderr,"%s: unexpected EOF in cache file (at %llu)\n",__progname,cfdpos()); return(0); break; default: fprintf(stderr,"%s: invalid character %02x in timestamp in cache file at %llu\n",__progname,c,cfdpos()); return(0); break; } } } static char *getstr(FILE *f) { int l; char *s; l = getsmall(f); if (l < 0) return(0); s = malloc(l+1); if (fread(s,1,l,f) != l) { fprintf(stderr,"%s: can't read string (len %d) from cache file (at %llu)\n",__progname,l,cfdpos()); free(s); return(0); } s[l] = '\0'; return(s); } static void freenode(NODE *n) { int i; for (i=n->nkid-1;i>=0;i--) freenode(n->kid[i]); free(n->kid); for (i=n->naddrs-1;i>=0;i--) free(n->addrs[i]); free(n->name); free(n); } static int name_cmp(const char *n1, int l1, const char *n2, int l2) { if (l1 < l2) return(-1); if (l1 > l2) return(1); return(strncasecmp(n1,n2,l1)); } static void sortkids(NODE *n) { int any; int i; NODE *t; do { any = 0; for (i=n->nkid-1;i>0;i--) { if (name_cmp( n->kid[i-1]->name, n->kid[i-1]->namelen, n->kid[i]->name, n->kid[i]->namelen ) > 0) { t = n->kid[i-1]; n->kid[i-1] = n->kid[i]; n->kid[i] = t; any ++; } } } while (any); } static NODE *getnode(FILE *f) { NODE *n; int c; int i; char *s; int nk; int mustsort; n = malloc(sizeof(NODE)); n->up = 0; n->nkid = 0; n->kid = 0; n->namelen = 0; n->name = 0; n->naddrs = 0; n->addrs = 0; n->when = 0; n->dirty = 0; c = getc(f); if (c == 'a') { int na; na = getsmall(f); if (na < 0) { free(n); return(0); } if (! getstamp(f,&n->when)) { free(n); return(0); } n->naddrs = na; n->addrs = malloc(na*sizeof(*n->addrs)); for (i=0;i=0;i--) free(n->addrs[i]); free(n->addrs); free(n); return(0); } n->addrs[i] = s; } } else { ungetc(c,f); } nk = getsmall(f); if (nk < 0) { freenode(n); return(0); } n->nkid = nk; n->kid = malloc(nk*sizeof(*n->kid)); for (i=0;ikid[i] = 0; mustsort = 0; for (i=0;inamelen = subnlen; subnode->name = subname; n->kid[i] = subnode; subnode->up = n; if ( (i > 0) && !mustsort && (name_cmp( n->kid[i-1]->name, n->kid[i-1]->namelen, subname, subnlen ) > 0) ) { mustsort = 1; cachedirty = 1; } } if (mustsort) sortkids(n); return(n); } static void opencache(void) { cfd = open(cachefile,O_RDWR,0); if (cfd >= 0) return; if (errno == ENOENT) { cfd = open(cachefile,O_RDWR|O_CREAT|O_EXCL,0644); if (cfd >= 0) { write(cfd,"0:",2); lseek(cfd,0,SEEK_SET); return; } } fprintf(stderr,"%s: can't open %s: %s\n",__progname,cachefile,strerror(errno)); exit(1); } static void lockcache(void) { if (flock(cfd,LOCK_EX) < 0) { fprintf(stderr,"%s: can't lock %s: %s\n",__progname,cachefile,strerror(errno)); exit(1); } } static void loadcache(void) { FILE *f; lseek(cfd,0,SEEK_SET); f = funopen(&cfd,&ncread,0,0,0); cachedirty = 0; cacheroot = getnode(f); if (cacheroot == 0) exit(1); fclose(f); } static void unlockcache(void) { if (flock(cfd,LOCK_UN) < 0) { fprintf(stderr,"%s: can't unlock %s: %s\n",__progname,cachefile,strerror(errno)); exit(1); } } static NODE *findsubnode(NODE *n, const char *name, int len, int make) { int l; int m; int h; int c; NODE *sub; l = -1; h = n->nkid; while (h-l > 1) { m = (l + h) / 2; c = name_cmp(n->kid[m]->name,n->kid[m]->namelen,name,len); if (c == 0) return(n->kid[m]); if (c < 0) l = m; else h = m; } if (! make) return(0); sub = malloc(sizeof(NODE)); sub->up = n; sub->nkid = 0; sub->kid = 0; sub->namelen = len; sub->name = malloc(len+1); bcopy(name,sub->name,len); sub->name[len] = '\0'; sub->naddrs = 0; sub->addrs = 0; sub->when = 0; sub->dirty = 0; n->kid = realloc(n->kid,(n->nkid+1)*sizeof(*n->kid)); n->kid[n->nkid++] = sub; sortkids(n); cachedirty = 1; return(sub); } static unsigned long int now(void) { static unsigned long int v = 0; if (! v) v = time(0); return(v); } static void cache_addr(const char *f, const char *a) { const char *e; const char *p; NODE *n; if (! f) return; if (! f[0]) { fprintf(stderr,"%s: cache_addr for zero-length domain\n",__progname); return; } n = cacheroot; e = f + strlen(f); if (f[0] == '.') { fprintf(stderr,"%s: zero-length label [#1] in %s\n",__progname,f); return; } while (1) { if (e[-1] == '.') { fprintf(stderr,"%s: zero-length label [#2] in %s\n",__progname,f); return; } for (p=e-1;(p>f)&&(p[-1]!='.');p--) ; n = findsubnode(n,p,e-p,1); if (p == f) break; e = p - 1; } if (! n->dirty) { n->naddrs = 0; n->when = now(); n->dirty = 1; } n->addrs = realloc(n->addrs,(n->naddrs+1)*sizeof(*n->addrs)); n->addrs[n->naddrs++] = strdup(a); cachedirty = 1; } static char *nodedom(NODE *n) { char *buf; int len; int alloc; static void app(const char *s, int l) { if (l < 0) l = strlen(s); if (len+l > alloc) buf = realloc(buf,alloc=len+l+16); bcopy(s,buf+len,l); len += l; } buf = 0; len = 0; alloc = 0; while (1) { app(n->name,-1); if (!n->up || (n->up == cacheroot)) break; app(".",1); n = n->up; } app("",1); return(buf); } static NODE *lookupdom(NODE *root, const char *dom, int complain) { const char *de; const char *dp; NODE *cur; NODE *an; de = dom + strlen(dom); cur = root; if (dom[0] == '.') { if (! complain) abort(); fprintf(stderr,"%s: zero-length label [#3] in %s\n",__progname,dom); exit(1); } an = 0; while (1) { for (dp=de;(dp>dom)&&(dp[-1]!='.');dp--) ; if (dp == de) { if (! complain) abort(); fprintf(stderr,"%s: zero-length label [#4] in %s\n",__progname,dom); exit(1); } cur = findsubnode(cur,dp,de-dp,0); if (cur == 0) break; if (cur->when) an = cur; if (dp == dom) break; de = dp - 1; } return(an); } static int dns_skip(const void *dbuf, int len, int off) { const unsigned char *d; int i; d = dbuf; while (1) { if (off >= len) return(-1); i = d[off]; switch (i & 0xc0) { case 0: if (i) off += i + 1; else return(off+1); break; case 0xc0: if (off+1 >= len) return(-1); return(off+2); break; default: return(-1); break; } } } static int query_it(const char *name, int wanttype, void (*fn)(const void *, int, const void *, const void *, int)) #define QIR_FAIL 1 /* failure such as SERVAIL or can't-send */ #define QIR_NODATA 2 /* OK but zero answers */ #define QIR_NXDOM 3 /* NXDOMAIN */ #define QIR_SUCCESS 4 /* OK, nonzero answers */ { unsigned char respbuf[8192]; unsigned char compbuf[1024]; unsigned char nbuf[1024]; int rdl; int rl; int n; int i; int x; int nl; int type; int class; static int exp(int inx) { return(dn_expand((const void *)&respbuf[0],(const void *)&respbuf[rl],(const void *)&respbuf[inx],(void *)&compbuf[0],sizeof(compbuf))); } bzero(&respbuf[0],12); /* res_query doesn't check response length (!!) */ rl = res_query(name,C_IN,wanttype,(void *)&respbuf[0],sizeof(respbuf)); if (rl <= 0) { switch (h_errno) { case HOST_NOT_FOUND: return(QIR_NXDOM); break; case NO_DATA: return(QIR_NODATA); break; } fprintf(stderr,"%s: %s: res_query failed, h_errno %d\n",__progname,name,h_errno); return(QIR_FAIL); } if (rl < 12) { fprintf(stderr,"%s: %s: response too short (%d)\n",__progname,name,rl); return(QIR_FAIL); } if (respbuf[3] & 2) { fprintf(stderr,"%s: %s: response truncated\n",__progname,name); return(QIR_FAIL); } n = (respbuf[6] * 256) + respbuf[7]; if (n == 0) return(QIR_NODATA); i = (respbuf[4] * 256) + respbuf[5]; x = 12; for (;i>0;i--) { x = dns_skip(&respbuf[0],rl,x); if (x < 0) { fprintf(stderr,"%s: %s: invalid compressed name (question)\n",__progname,name); return(QIR_FAIL); } x += 4; if (x >= rl) { fprintf(stderr,"%s: %s: packet overflow (question)\n",__progname,name); return(QIR_FAIL); } } for (i=0;i= rl) { fprintf(stderr,"%s: %s: packet overflow (answer)\n",__progname,name); return(QIR_FAIL); } rdl = (respbuf[x-2] * 256) + respbuf[x-1]; class = (respbuf[x-8] * 256) + respbuf[x-7]; if (class == C_IN) { type = (respbuf[x-10] * 256) + respbuf[x-9]; if ((type == T_CNAME) && (wanttype != T_ANY)) { if (! strcasecmp(&compbuf[0],name)) { nl = exp(x); if (nl < 0) { fprintf(stderr,"%s: %s: invalid compressed name (CNAME)\n",__progname,name); return(QIR_FAIL); } if (nl != rdl) { fprintf(stderr,"%s: %s: CNAME data length wrong\n",__progname,name); return(QIR_FAIL); } fprintf(stderr,"%s: %s: CNAMEd to %s\n",__progname,name,&compbuf[0]); strcpy(&nbuf[0],&compbuf[0]); name = &nbuf[0]; } } else if (! strcasecmp(&compbuf[0],name)) { (*fn)((const void *)&respbuf[0],type,(const void *)&respbuf[rl],(const void *)&respbuf[x],rdl); } } x += rdl; } return(QIR_SUCCESS); } static int abuse_net_lookup(const char *dom, void (*res)(const char *, const char *)) { int shouldhave; int haven; int havea; char **strs; char *qname; char *rdom; int qir; int i; int rv; static void record(const void *pktbase __attribute__((__unused__)), int type, const void *pktend __attribute__((__unused__)), const void *rrn, int rdl) { switch (type) { case T_A: if (rdl != 4) { fprintf(stderr,"%s: %s: A record has length %d\n",__progname,qname,rdl); break; } if ( ((const unsigned char *)rrn)[0] || ((const unsigned char *)rrn)[1] || ((const unsigned char *)rrn)[2] ) { fprintf(stderr,"%s: %s: A record is unreasonable (%d.%d.%d.%d)\n",__progname,qname,((const unsigned char *)rrn)[0],((const unsigned char *)rrn)[1],((const unsigned char *)rrn)[2],((const unsigned char *)rrn)[3]); break; } shouldhave = ((const unsigned char *)rrn)[3]; break; case T_TXT: { int o; int l; int n; char *t; if (rdl < 1) { fprintf(stderr,"%s: %s: malformatted TXT (zero length)\n",__progname,qname); break; } o = 0; n = 0; while (1) { if (o == rdl) break; if (o > rdl) { fprintf(stderr,"%s: %s: TXT overflows record (%d > %d)\n",__progname,qname,o,rdl); break; } l = ((const unsigned char *)rrn)[o]; t = malloc(l+1); bcopy(&((const unsigned char *)rrn)[o+1],t,l); t[l] = '\0'; if (haven >= havea) strs = realloc(strs,(havea=haven+8)*sizeof(*strs)); strs[haven++] = t; o += l + 1; n ++; } if (n < 1) { fprintf(stderr,"%s: %s: TXT record contains no strings\n",__progname,qname); } else if (n > 1) { fprintf(stderr,"%s: %s: warning: multi-string TXT record (using all strings)\n",__progname,qname); } } break; case T_HINFO: if (rdom) { fprintf(stderr,"%s: %s: ignoring extra HINFO\n",__progname,qname); break; } if (rdl < 2) { fprintf(stderr,"%s: %s: malformatted HINFO (length %d)\n",__progname,qname,rdl); break; } if ( (rdl == 9) && (((const unsigned char *)rrn)[0] == 7) && !bcmp(&((const unsigned char *)rrn)[1],"default",7) && (((const unsigned char *)rrn)[8] == 0) ) { rdom = strdup(dom); } else if ( (rdl >= 8) && (((const unsigned char *)rrn)[0] == 6) && !bcmp(&((const unsigned char *)rrn)[1],"lookup",6) && (rdl == 8+(int)((const unsigned char *)rrn)[7]) ) { int qdl; qdl = strlen(dom); if ( (rdl-8 > qdl) || strncasecmp(&((const unsigned char *)rrn)[8],dom+qdl-(rdl-8),rdl-8) ) { rdom = strdup(dom); } else { rdom = malloc(rdl-7); bcopy(&((const unsigned char *)rrn)[8],rdom,rdl-8); rdom[rdl-8] = '\0'; } } else { int l; l = ((const unsigned char *)rrn)[0]; fprintf(stderr,"%s: %s: ignoring unexpected HINFO %.*s / %.*s\n",__progname,qname,l,&((const unsigned char *)rrn)[1],((const unsigned char *)rrn)[l+1],&((const unsigned char *)rrn)[l+2]); } break; default: fprintf(stderr,"%s: %s: unexpected record type %d\n",__progname,qname,type); break; } } shouldhave = -1; haven = 0; havea = 0; strs = 0; asprintf(&qname,"%s.%s",dom,abuse_net_domain); rdom = 0; qir = query_it(qname,T_ANY,record); rv = 0; switch (qir) { case QIR_FAIL: fprintf(stderr,"%s: unexpected query failure\n",__progname); break; case QIR_NODATA: fprintf(stderr,"%s: no data found (?""?)\n",__progname); break; case QIR_NXDOM: fprintf(stderr,"%s: NXDOMAIN (?""?)\n",__progname); break; case QIR_SUCCESS: if (haven != shouldhave) { if (shouldhave < 0) { if (haven < 1) { fprintf(stderr,"%s: %s: no A or TXT records found\n",__progname,qname); } else { fprintf(stderr,"%s: %s: no A records found\n",__progname,qname); fprintf(stderr,"%s: using TXT record count (%d)\n",__progname,haven); } } else { fprintf(stderr,"%s: %s: A record count (%d) != TXT record count (%d), using TXT count\n",__progname,qname,shouldhave,haven); } } if (rdom == 0) { fprintf(stderr,"%s: %s: no HINFO record, not saving results\n",__progname,qname); } for (i=0;iwhen; tm = localtime(&t); strftime(&tbuf[0],sizeof(tbuf),"%Y-%m-%d %H:%M:%S",tm); odom = nodedom(an); printf("[Cached for %s at %s]",odom,&tbuf[0]); if (now()-t > 86400*7) printf(" *** WARNING: Old cache entry"); printf("\n\n"); for (i=0;inaddrs;i++) printf("%s\n",an->addrs[i]); on = an->naddrs; oaddrs = an->addrs; an->naddrs = 0; an->addrs = 0; unlockcache(); freenode(cacheroot); n = 0; doms = 0; addrs = 0; if (abuse_net_lookup(dom,res)) { lockcache(); loadcache(); printit = 0; if (n != on) printit = 1; if (! printit) { for (i=0;i= n) { printit = 1; break; } } } if (printit) printf("-->\n"); for (i=0;idirty) sortkids(n); if (n->when) { fprintf(f,"a%u:%lu:",n->naddrs,n->when); for (i=0;inaddrs;i++) putstr(n->addrs[i],f); } fprintf(f,"%u:",n->nkid); for (i=0;inkid;i++) { putstr(n->kid[i]->name,f); putnode(n->kid[i],f); } } static void prunecache(NODE **np) { NODE *n; int i; int j; n = *np; for (i=n->nkid-1;i>=0;i--) prunecache(&n->kid[i]); j = 0; for (i=0;inkid;i++) { if (n->kid[i]) { if (i != j) n->kid[j] = n->kid[i]; j ++; } } n->nkid = j; if (n->when+EXPIRETIME < now()) n->when = 0; if ((n->nkid == 0) && (n->when == 0)) { freenode(n); *np = 0; } } static void savecache(void) { FILE *f; prunecache(&cacheroot); lseek(cfd,0,SEEK_SET); f = funopen(&cfd,0,ncwrite,0,0); if (! cacheroot) { fprintf(f,"0:"); } else { putnode(cacheroot,f); } fclose(f); ftruncate(cfd,cfdpos()); cachedirty = 0; } int main(int, char **); int main(int ac, char **av) { if (ac != 2) usage(); opencache(); lockcache(); loadcache(); doit(av[1]); if (cachedirty) savecache(); unlockcache(); exit(0); }