| 1 |
/* |
| 2 |
firedns.c - firedns library |
| 3 |
Copyright (C) 2002 Ian Gulliver |
| 4 |
|
| 5 |
This file has been gutted and mucked with for use in BOPM - see the |
| 6 |
real library at http://ares.penguinhosting.net/~ian/ before you judge |
| 7 |
firedns based on this.. |
| 8 |
|
| 9 |
This program is free software; you can redistribute it and/or modify |
| 10 |
it under the terms of version 2 of the GNU General Public License as |
| 11 |
published by the Free Software Foundation. |
| 12 |
|
| 13 |
This program is distributed in the hope that it will be useful, |
| 14 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 |
GNU General Public License for more details. |
| 17 |
|
| 18 |
You should have received a copy of the GNU General Public License |
| 19 |
along with this program; if not, write to the Free Software |
| 20 |
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 21 |
*/ |
| 22 |
|
| 23 |
#include "setup.h" |
| 24 |
|
| 25 |
#include <stdlib.h> |
| 26 |
#include <time.h> |
| 27 |
#include <sys/types.h> |
| 28 |
#include <sys/socket.h> |
| 29 |
#include <poll.h> |
| 30 |
#include <sys/time.h> |
| 31 |
#include <netinet/in.h> |
| 32 |
#include <arpa/inet.h> |
| 33 |
#include <string.h> |
| 34 |
#include <unistd.h> |
| 35 |
#include <stdio.h> |
| 36 |
#include <errno.h> |
| 37 |
#include <fcntl.h> |
| 38 |
|
| 39 |
#include "compat.h" |
| 40 |
#include "memory.h" |
| 41 |
#include "firedns.h" |
| 42 |
#include "config.h" |
| 43 |
#include "list.h" |
| 44 |
#include "log.h" |
| 45 |
#include "dnsbl.h" |
| 46 |
|
| 47 |
#define FIREDNS_TRIES 3 |
| 48 |
|
| 49 |
int firedns_errno = FDNS_ERR_NONE; |
| 50 |
|
| 51 |
/* Variables local to this file */ |
| 52 |
static unsigned int firedns_fdinuse; |
| 53 |
|
| 54 |
/* up to FDNS_MAX nameservers; populated by firedns_init() */ |
| 55 |
static struct in_addr servers4[FDNS_MAX]; |
| 56 |
static struct in6_addr servers6[FDNS_MAX]; |
| 57 |
|
| 58 |
/* actual count of nameservers; set by firedns_init() */ |
| 59 |
static unsigned int i4; |
| 60 |
static unsigned int i6; |
| 61 |
|
| 62 |
/* |
| 63 |
* Linked list of open DNS queries; populated by firedns_add_query(), |
| 64 |
* decimated by firedns_getresult() |
| 65 |
*/ |
| 66 |
static list_t CONNECTIONS; |
| 67 |
|
| 68 |
/* |
| 69 |
* List of errors, in order of values used in FDNS_ERR_*, returned by |
| 70 |
* firedns_strerror |
| 71 |
*/ |
| 72 |
static const char *const errors[] = |
| 73 |
{ |
| 74 |
[FDNS_ERR_NONE] = "Success", |
| 75 |
[FDNS_ERR_FORMAT] = "Format error", |
| 76 |
[FDNS_ERR_SERVFAIL] = "Server failure", |
| 77 |
[FDNS_ERR_NXDOMAIN] = "Name error", |
| 78 |
[FDNS_ERR_NOIMPT] = "Not implemented", |
| 79 |
[FDNS_ERR_REFUSED] = "Refused", |
| 80 |
[FDNS_ERR_TIMEOUT] = "Timeout", |
| 81 |
[FDNS_ERR_NETWORK] = "Network error", |
| 82 |
[FDNS_ERR_FDLIMIT] = "FD Limit reached", |
| 83 |
[FDNS_ERR_OTHER] = "Unknown error" |
| 84 |
}; |
| 85 |
|
| 86 |
/* Structures */ |
| 87 |
|
| 88 |
/* open DNS query */ |
| 89 |
struct s_connection |
| 90 |
{ |
| 91 |
/* |
| 92 |
* unique ID (random number), matches header ID; both set by |
| 93 |
* firedns_add_query() |
| 94 |
*/ |
| 95 |
unsigned char id[2]; |
| 96 |
uint16_t class; |
| 97 |
uint16_t type; |
| 98 |
|
| 99 |
/* file descriptor returned from sockets */ |
| 100 |
int fd; |
| 101 |
void *info; |
| 102 |
time_t start; |
| 103 |
char lookup[256]; |
| 104 |
int v6; |
| 105 |
}; |
| 106 |
|
| 107 |
struct s_rr_middle |
| 108 |
{ |
| 109 |
uint16_t type; |
| 110 |
uint16_t class; |
| 111 |
|
| 112 |
/* XXX - firedns depends on this being 4 bytes */ |
| 113 |
uint32_t ttl; |
| 114 |
uint16_t rdlength; |
| 115 |
}; |
| 116 |
|
| 117 |
/* DNS query header */ |
| 118 |
struct s_header |
| 119 |
{ |
| 120 |
unsigned char id[2]; |
| 121 |
unsigned char flags1; |
| 122 |
#define FLAGS1_MASK_QR 0x80 |
| 123 |
/* bitshift right 3 */ |
| 124 |
#define FLAGS1_MASK_OPCODE 0x78 |
| 125 |
#define FLAGS1_MASK_AA 0x04 |
| 126 |
#define FLAGS1_MASK_TC 0x02 |
| 127 |
#define FLAGS1_MASK_RD 0x01 |
| 128 |
|
| 129 |
unsigned char flags2; |
| 130 |
#define FLAGS2_MASK_RA 0x80 |
| 131 |
#define FLAGS2_MASK_Z 0x70 |
| 132 |
#define FLAGS2_MASK_RCODE 0x0f |
| 133 |
|
| 134 |
uint16_t qdcount; |
| 135 |
uint16_t ancount; |
| 136 |
uint16_t nscount; |
| 137 |
uint16_t arcount; |
| 138 |
|
| 139 |
/* DNS question, populated by firedns_build_query_payload() */ |
| 140 |
unsigned char payload[512]; |
| 141 |
}; |
| 142 |
|
| 143 |
/* Function prototypes */ |
| 144 |
static struct s_connection *firedns_add_query(void); |
| 145 |
static int firedns_doquery(struct s_connection *); |
| 146 |
static int firedns_build_query_payload(const char *const, uint16_t, uint16_t, unsigned char *); |
| 147 |
static int firedns_send_requests(struct s_header *, struct s_connection *, int); |
| 148 |
|
| 149 |
|
| 150 |
void |
| 151 |
firedns_init(void) |
| 152 |
{ |
| 153 |
/* |
| 154 |
* populates servers4 (or -6) struct with up to FDNS_MAX nameserver IP |
| 155 |
* addresses from /etc/firedns.conf (or /etc/resolv.conf) |
| 156 |
*/ |
| 157 |
FILE *f; |
| 158 |
struct in_addr addr4; |
| 159 |
struct in6_addr addr6; |
| 160 |
char buf[1024]; |
| 161 |
char *p = NULL; |
| 162 |
|
| 163 |
i6 = 0; |
| 164 |
i4 = 0; |
| 165 |
|
| 166 |
srand((unsigned int)time(NULL)); |
| 167 |
memset(servers4, 0, sizeof(servers4)); |
| 168 |
memset(servers6, 0, sizeof(servers6)); |
| 169 |
|
| 170 |
/* read etc/firedns.conf if we've got it, otherwise parse /etc/resolv.conf */ |
| 171 |
f = fopen(FDNS_CONFIG_PREF, "r"); |
| 172 |
|
| 173 |
if (f == NULL) |
| 174 |
{ |
| 175 |
f = fopen(FDNS_CONFIG_FBCK, "r"); |
| 176 |
|
| 177 |
if (f == NULL) |
| 178 |
{ |
| 179 |
log_printf("Unable to open %s", FDNS_CONFIG_FBCK); |
| 180 |
return; |
| 181 |
} |
| 182 |
|
| 183 |
while (fgets(buf, sizeof(buf), f)) |
| 184 |
{ |
| 185 |
if ((p = strchr(buf, '\n'))) |
| 186 |
*p = '\0'; |
| 187 |
|
| 188 |
if (strncmp(buf, "nameserver", 10) == 0) |
| 189 |
{ |
| 190 |
unsigned int i = 10; |
| 191 |
|
| 192 |
while (buf[i] == ' ' || buf[i] == '\t') |
| 193 |
++i; |
| 194 |
|
| 195 |
if (i6 < FDNS_MAX) |
| 196 |
{ |
| 197 |
if (inet_pton(AF_INET6, &buf[i], &addr6) > 0) |
| 198 |
{ |
| 199 |
memcpy(&servers6[i6++], &addr6, sizeof(struct in6_addr)); |
| 200 |
continue; |
| 201 |
} |
| 202 |
} |
| 203 |
|
| 204 |
if (i4 < FDNS_MAX) |
| 205 |
{ |
| 206 |
if (inet_pton(AF_INET, &buf[i], &addr4) > 0) |
| 207 |
memcpy(&servers4[i4++], &addr4, sizeof(struct in_addr)); |
| 208 |
} |
| 209 |
} |
| 210 |
} |
| 211 |
} |
| 212 |
else |
| 213 |
{ |
| 214 |
while (fgets(buf, sizeof(buf), f)) |
| 215 |
{ |
| 216 |
if ((p = strchr(buf, '\n'))) |
| 217 |
*p = '\0'; |
| 218 |
|
| 219 |
if (i6 < FDNS_MAX) |
| 220 |
{ |
| 221 |
if (inet_pton(AF_INET6, buf, &addr6) > 0) |
| 222 |
{ |
| 223 |
memcpy(&servers6[i6++], &addr6, sizeof(struct in6_addr)); |
| 224 |
continue; |
| 225 |
} |
| 226 |
} |
| 227 |
|
| 228 |
if (i4 < FDNS_MAX) |
| 229 |
{ |
| 230 |
if (inet_pton(AF_INET, buf, &addr4) > 0) |
| 231 |
memcpy(&servers4[i4++], &addr4, sizeof(struct in_addr)); |
| 232 |
} |
| 233 |
} |
| 234 |
} |
| 235 |
|
| 236 |
fclose(f); |
| 237 |
} |
| 238 |
|
| 239 |
/* |
| 240 |
* These little hacks are here to avoid alignment and type sizing issues completely by doing manual copies |
| 241 |
*/ |
| 242 |
static inline void |
| 243 |
firedns_fill_rr(struct s_rr_middle *restrict const rr, const unsigned char *const restrict input) |
| 244 |
{ |
| 245 |
rr->type = input[0] * 256 + input[1]; |
| 246 |
rr->class = input[2] * 256 + input[3]; |
| 247 |
rr->ttl = input[4] * 16777216 + input[5] * 65536 + input[6] * 256 + input[7]; |
| 248 |
rr->rdlength = input[8] * 256 + input[9]; |
| 249 |
} |
| 250 |
|
| 251 |
static inline void |
| 252 |
firedns_fill_header(struct s_header *const restrict header, const unsigned char *const restrict input, const int l) |
| 253 |
{ |
| 254 |
header->id[0] = input[0]; |
| 255 |
header->id[1] = input[1]; |
| 256 |
header->flags1 = input[2]; |
| 257 |
header->flags2 = input[3]; |
| 258 |
header->qdcount = input[4] * 256 + input[5]; |
| 259 |
header->ancount = input[6] * 256 + input[7]; |
| 260 |
header->nscount = input[8] * 256 + input[9]; |
| 261 |
header->arcount = input[10] * 256 + input[11]; |
| 262 |
memcpy(header->payload, &input[12], l); |
| 263 |
} |
| 264 |
|
| 265 |
static inline void |
| 266 |
firedns_empty_header(unsigned char *const restrict output, const struct s_header *const restrict header, const int l) |
| 267 |
{ |
| 268 |
output[0] = header->id[0]; |
| 269 |
output[1] = header->id[1]; |
| 270 |
output[2] = header->flags1; |
| 271 |
output[3] = header->flags2; |
| 272 |
output[4] = header->qdcount / 256; |
| 273 |
output[5] = header->qdcount % 256; |
| 274 |
output[6] = header->ancount / 256; |
| 275 |
output[7] = header->ancount % 256; |
| 276 |
output[8] = header->nscount / 256; |
| 277 |
output[9] = header->nscount % 256; |
| 278 |
output[10] = header->arcount / 256; |
| 279 |
output[11] = header->arcount % 256; |
| 280 |
memcpy(&output[12], header->payload, l); |
| 281 |
} |
| 282 |
|
| 283 |
/* immediate A query */ |
| 284 |
struct in_addr * |
| 285 |
firedns_resolveip4(const char *const name) |
| 286 |
{ |
| 287 |
static struct in_addr addr; |
| 288 |
|
| 289 |
if (inet_pton(AF_INET, name, &addr) > 0) |
| 290 |
return &addr; |
| 291 |
|
| 292 |
return firedns_resolveip(FDNS_QRY_A, name); |
| 293 |
} |
| 294 |
|
| 295 |
/* immediate AAAA query */ |
| 296 |
struct in6_addr * |
| 297 |
firedns_resolveip6(const char *const name) |
| 298 |
{ |
| 299 |
static struct in6_addr addr; |
| 300 |
|
| 301 |
if (inet_pton(AF_INET6, name, &addr) > 0) |
| 302 |
return &addr; |
| 303 |
|
| 304 |
return firedns_resolveip(FDNS_QRY_AAAA, name); |
| 305 |
} |
| 306 |
|
| 307 |
/* resolve a query of a given type */ |
| 308 |
void * |
| 309 |
firedns_resolveip(int type, const char *const name) |
| 310 |
{ |
| 311 |
struct firedns_result *result; |
| 312 |
struct timeval tv; |
| 313 |
fd_set s; |
| 314 |
|
| 315 |
for (unsigned int t = 0; t < FIREDNS_TRIES; ++t) |
| 316 |
{ |
| 317 |
int fd = firedns_getip(type, name, NULL); |
| 318 |
if (fd == -1) |
| 319 |
return NULL; |
| 320 |
|
| 321 |
tv.tv_sec = 5; |
| 322 |
tv.tv_usec = 0; |
| 323 |
FD_ZERO(&s); |
| 324 |
FD_SET(fd, &s); |
| 325 |
select(fd + 1, &s, NULL, NULL, &tv); |
| 326 |
|
| 327 |
result = firedns_getresult(fd); |
| 328 |
|
| 329 |
if (firedns_errno == FDNS_ERR_NONE) |
| 330 |
/* |
| 331 |
* Return is from static memory in getresult, so there is no need to |
| 332 |
* copy it until the next call to firedns. |
| 333 |
*/ |
| 334 |
return result->text; |
| 335 |
else if (firedns_errno == FDNS_ERR_NXDOMAIN) |
| 336 |
return NULL; |
| 337 |
} |
| 338 |
|
| 339 |
if (firedns_errno == FDNS_ERR_NONE) |
| 340 |
firedns_errno = FDNS_ERR_TIMEOUT; |
| 341 |
|
| 342 |
return NULL; |
| 343 |
} |
| 344 |
|
| 345 |
/* |
| 346 |
* build, add and send specified query; retrieve result with |
| 347 |
* firedns_getresult() |
| 348 |
*/ |
| 349 |
int |
| 350 |
firedns_getip(int type, const char *const name, void *info) |
| 351 |
{ |
| 352 |
struct s_connection *s; |
| 353 |
node_t *node; |
| 354 |
|
| 355 |
s = firedns_add_query(); |
| 356 |
s->class = 1; |
| 357 |
s->type = type; |
| 358 |
s->info = info; |
| 359 |
strlcpy(s->lookup, name, sizeof(s->lookup)); |
| 360 |
|
| 361 |
if (firedns_fdinuse >= OptionsItem->dns_fdlimit) |
| 362 |
{ |
| 363 |
firedns_errno = FDNS_ERR_FDLIMIT; |
| 364 |
|
| 365 |
/* Don't add to queue if there is no info */ |
| 366 |
if (info == NULL) |
| 367 |
xfree(s); |
| 368 |
else |
| 369 |
{ |
| 370 |
node = node_create(s); |
| 371 |
list_add(&CONNECTIONS, node); |
| 372 |
} |
| 373 |
|
| 374 |
return -1; |
| 375 |
} |
| 376 |
|
| 377 |
int fd = firedns_doquery(s); |
| 378 |
if (fd == -1) |
| 379 |
{ |
| 380 |
xfree(s); |
| 381 |
return -1; |
| 382 |
} |
| 383 |
|
| 384 |
node = node_create(s); |
| 385 |
list_add(&CONNECTIONS, node); |
| 386 |
|
| 387 |
return fd; |
| 388 |
} |
| 389 |
|
| 390 |
/* build DNS query, add to list */ |
| 391 |
static struct s_connection * |
| 392 |
firedns_add_query(void) |
| 393 |
{ |
| 394 |
struct s_connection *s; |
| 395 |
|
| 396 |
/* create new connection object */ |
| 397 |
s = xcalloc(sizeof(*s)); |
| 398 |
|
| 399 |
/* verified by firedns_getresult() */ |
| 400 |
s->id[0] = rand() % 255; |
| 401 |
s->id[1] = rand() % 255; |
| 402 |
s->fd = -1; |
| 403 |
|
| 404 |
return s; |
| 405 |
} |
| 406 |
|
| 407 |
static int |
| 408 |
firedns_doquery(struct s_connection *s) |
| 409 |
{ |
| 410 |
struct s_header h; |
| 411 |
|
| 412 |
int len = firedns_build_query_payload(s->lookup, s->type, 1, (unsigned char *)&h.payload); |
| 413 |
if (len == -1) |
| 414 |
{ |
| 415 |
firedns_errno = FDNS_ERR_FORMAT; |
| 416 |
return -1; |
| 417 |
} |
| 418 |
|
| 419 |
return firedns_send_requests(&h, s, len); |
| 420 |
} |
| 421 |
|
| 422 |
/* |
| 423 |
* populate payload with query: name= question, rr= record type |
| 424 |
*/ |
| 425 |
static int |
| 426 |
firedns_build_query_payload(const char *const name, uint16_t rr, uint16_t class, |
| 427 |
unsigned char *payload) |
| 428 |
{ |
| 429 |
int16_t payloadpos = 0; |
| 430 |
const char *tempchr, *tempchr2; |
| 431 |
uint16_t l; |
| 432 |
|
| 433 |
tempchr2 = name; |
| 434 |
|
| 435 |
/* split name up into labels, create query */ |
| 436 |
while ((tempchr = strchr(tempchr2, '.'))) |
| 437 |
{ |
| 438 |
l = tempchr - tempchr2; |
| 439 |
|
| 440 |
if (payloadpos + l + 1 > 507) |
| 441 |
return -1; |
| 442 |
|
| 443 |
payload[payloadpos++] = l; |
| 444 |
memcpy(&payload[payloadpos], tempchr2, l); |
| 445 |
payloadpos += l; |
| 446 |
tempchr2 = &tempchr[1]; |
| 447 |
} |
| 448 |
|
| 449 |
l = strlen(tempchr2); |
| 450 |
|
| 451 |
if (l) |
| 452 |
{ |
| 453 |
if (payloadpos + l + 2 > 507) |
| 454 |
return -1; |
| 455 |
|
| 456 |
payload[payloadpos++] = l; |
| 457 |
memcpy(&payload[payloadpos], tempchr2, l); |
| 458 |
payloadpos += l; |
| 459 |
payload[payloadpos++] = '\0'; |
| 460 |
} |
| 461 |
|
| 462 |
if (payloadpos > 508) |
| 463 |
return -1; |
| 464 |
|
| 465 |
l = htons(rr); |
| 466 |
memcpy(&payload[payloadpos], &l, 2); |
| 467 |
|
| 468 |
l = htons(class); |
| 469 |
memcpy(&payload[payloadpos + 2], &l, 2); |
| 470 |
|
| 471 |
return payloadpos + 4; |
| 472 |
} |
| 473 |
|
| 474 |
/* send DNS query */ |
| 475 |
static int |
| 476 |
firedns_send_requests(struct s_header *h, struct s_connection *s, int l) |
| 477 |
{ |
| 478 |
int sent_ok = 0; |
| 479 |
struct sockaddr_in addr4; |
| 480 |
struct sockaddr_in6 addr6; |
| 481 |
unsigned char payload[sizeof(struct s_header)]; |
| 482 |
|
| 483 |
/* set header flags */ |
| 484 |
h->id[0] = s->id[0]; |
| 485 |
h->id[1] = s->id[1]; |
| 486 |
h->flags1 = 0 | FLAGS1_MASK_RD; |
| 487 |
h->flags2 = 0; |
| 488 |
h->qdcount = 1; |
| 489 |
h->ancount = 0; |
| 490 |
h->nscount = 0; |
| 491 |
h->arcount = 0; |
| 492 |
|
| 493 |
/* try to create ipv6 or ipv4 socket */ |
| 494 |
s->v6 = 0; |
| 495 |
|
| 496 |
if (i6 > 0) |
| 497 |
{ |
| 498 |
s->fd = socket(AF_INET6, SOCK_DGRAM, 0); |
| 499 |
|
| 500 |
if (s->fd != -1) |
| 501 |
{ |
| 502 |
if (fcntl(s->fd, F_SETFL, O_NONBLOCK)) |
| 503 |
{ |
| 504 |
close(s->fd); |
| 505 |
s->fd = -1; |
| 506 |
} |
| 507 |
} |
| 508 |
|
| 509 |
if (s->fd != -1) |
| 510 |
{ |
| 511 |
memset(&addr6, 0, sizeof(addr6)); |
| 512 |
addr6.sin6_family = AF_INET6; |
| 513 |
|
| 514 |
if (bind(s->fd, (struct sockaddr *)&addr6, sizeof(addr6)) == 0) |
| 515 |
s->v6 = 1; |
| 516 |
else |
| 517 |
close(s->fd); |
| 518 |
} |
| 519 |
} |
| 520 |
|
| 521 |
if (s->v6 == 0) |
| 522 |
{ |
| 523 |
s->fd = socket(AF_INET, SOCK_DGRAM, 0); |
| 524 |
|
| 525 |
if (s->fd != -1) |
| 526 |
{ |
| 527 |
if (fcntl(s->fd, F_SETFL, O_NONBLOCK)) |
| 528 |
{ |
| 529 |
close(s->fd); |
| 530 |
s->fd = -1; |
| 531 |
} |
| 532 |
} |
| 533 |
|
| 534 |
if (s->fd != -1) |
| 535 |
{ |
| 536 |
memset(&addr4, 0, sizeof(addr4)); |
| 537 |
addr4.sin_family = AF_INET; |
| 538 |
addr4.sin_port = 0; |
| 539 |
addr4.sin_addr.s_addr = INADDR_ANY; |
| 540 |
|
| 541 |
if (bind(s->fd, (struct sockaddr *)&addr4, sizeof(addr4)) != 0) |
| 542 |
{ |
| 543 |
close(s->fd); |
| 544 |
s->fd = -1; |
| 545 |
} |
| 546 |
} |
| 547 |
|
| 548 |
if (s->fd == -1) |
| 549 |
{ |
| 550 |
firedns_errno = FDNS_ERR_NETWORK; |
| 551 |
return -1; |
| 552 |
} |
| 553 |
} |
| 554 |
|
| 555 |
firedns_empty_header(payload, h, l); |
| 556 |
|
| 557 |
/* if we've got ipv6 support, an ip v6 socket, and ipv6 servers, send to them */ |
| 558 |
if (i6 > 0 && s->v6 == 1) |
| 559 |
{ |
| 560 |
for (unsigned int i = 0; i < i6; ++i) |
| 561 |
{ |
| 562 |
memset(&addr6, 0, sizeof(addr6)); |
| 563 |
memcpy(&addr6.sin6_addr, &servers6[i], sizeof(addr6.sin6_addr)); |
| 564 |
|
| 565 |
addr6.sin6_family = AF_INET6; |
| 566 |
addr6.sin6_port = htons(FDNS_PORT); |
| 567 |
|
| 568 |
if (sendto(s->fd, payload, l + 12, 0, (struct sockaddr *)&addr6, sizeof(addr6)) > 0) |
| 569 |
sent_ok = 1; |
| 570 |
} |
| 571 |
} |
| 572 |
|
| 573 |
for (unsigned int i = 0; i < i4; ++i) |
| 574 |
{ |
| 575 |
/* send via ipv4-over-ipv6 if we've got an ipv6 socket */ |
| 576 |
if (s->v6 == 1) |
| 577 |
{ |
| 578 |
memset(&addr6, 0, sizeof(addr6)); |
| 579 |
memcpy(addr6.sin6_addr.s6_addr, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12); |
| 580 |
memcpy(&addr6.sin6_addr.s6_addr[12], &servers4[i].s_addr, 4); |
| 581 |
addr6.sin6_family = AF_INET6; |
| 582 |
addr6.sin6_port = htons(FDNS_PORT); |
| 583 |
|
| 584 |
if (sendto(s->fd, payload, l + 12, 0, (struct sockaddr *)&addr6, sizeof(addr6)) > 0) |
| 585 |
sent_ok = 1; |
| 586 |
|
| 587 |
continue; |
| 588 |
} |
| 589 |
|
| 590 |
/* otherwise send via standard ipv4 boringness */ |
| 591 |
memset(&addr4, 0, sizeof(addr4)); |
| 592 |
memcpy(&addr4.sin_addr, &servers4[i], sizeof(addr4.sin_addr)); |
| 593 |
addr4.sin_family = AF_INET; |
| 594 |
addr4.sin_port = htons(FDNS_PORT); |
| 595 |
|
| 596 |
if (sendto(s->fd, payload, l + 12, 0, (struct sockaddr *)&addr4, sizeof(addr4)) > 0) |
| 597 |
sent_ok = 1; |
| 598 |
} |
| 599 |
|
| 600 |
if (!sent_ok) |
| 601 |
{ |
| 602 |
close(s->fd); |
| 603 |
s->fd = -1; |
| 604 |
firedns_errno = FDNS_ERR_NETWORK; |
| 605 |
return -1; |
| 606 |
} |
| 607 |
|
| 608 |
time(&s->start); |
| 609 |
firedns_fdinuse++; |
| 610 |
firedns_errno = FDNS_ERR_NONE; |
| 611 |
|
| 612 |
return s->fd; |
| 613 |
} |
| 614 |
|
| 615 |
/* retrieve result of DNS query */ |
| 616 |
struct firedns_result * |
| 617 |
firedns_getresult(const int fd) |
| 618 |
{ |
| 619 |
static struct firedns_result result; |
| 620 |
struct s_header h; |
| 621 |
struct s_connection *c; |
| 622 |
node_t *node; |
| 623 |
int l, i, q, curanswer; |
| 624 |
struct s_rr_middle rr = { .rdlength = 0 }; |
| 625 |
unsigned char buffer[sizeof(struct s_header)]; |
| 626 |
|
| 627 |
firedns_errno = FDNS_ERR_OTHER; |
| 628 |
result.info = NULL; |
| 629 |
|
| 630 |
memset(result.text, 0, sizeof(result.text)); |
| 631 |
|
| 632 |
/* Find query in list of dns lookups */ |
| 633 |
LIST_FOREACH(node, CONNECTIONS.head) |
| 634 |
{ |
| 635 |
c = node->data; |
| 636 |
|
| 637 |
if (c->fd == fd) |
| 638 |
break; |
| 639 |
else |
| 640 |
c = NULL; |
| 641 |
} |
| 642 |
|
| 643 |
/* query not found */ |
| 644 |
if (c == NULL) |
| 645 |
return &result; |
| 646 |
|
| 647 |
/* query found -- we remove in cleanup */ |
| 648 |
l = recv(c->fd, buffer, sizeof(struct s_header), 0); |
| 649 |
|
| 650 |
result.info = c->info; |
| 651 |
strlcpy(result.lookup, c->lookup, sizeof(result.lookup)); |
| 652 |
|
| 653 |
if (l == -1) |
| 654 |
{ |
| 655 |
firedns_errno = FDNS_ERR_NETWORK; |
| 656 |
goto cleanup; |
| 657 |
} |
| 658 |
|
| 659 |
if (l < 12) |
| 660 |
goto cleanup; |
| 661 |
|
| 662 |
firedns_fill_header(&h, buffer, l - 12); |
| 663 |
|
| 664 |
if (c->id[0] != h.id[0] || c->id[1] != h.id[1]) |
| 665 |
/* |
| 666 |
* ID mismatch: we keep the connection, as this could be an answer to |
| 667 |
* a previous lookup.. |
| 668 |
*/ |
| 669 |
return NULL; |
| 670 |
|
| 671 |
if ((h.flags1 & FLAGS1_MASK_QR) == 0) |
| 672 |
goto cleanup; |
| 673 |
|
| 674 |
if ((h.flags1 & FLAGS1_MASK_OPCODE) != 0) |
| 675 |
goto cleanup; |
| 676 |
|
| 677 |
if ((h.flags2 & FLAGS2_MASK_RCODE) != 0) |
| 678 |
{ |
| 679 |
firedns_errno = (h.flags2 & FLAGS2_MASK_RCODE); |
| 680 |
goto cleanup; |
| 681 |
} |
| 682 |
|
| 683 |
if (h.ancount < 1) |
| 684 |
{ |
| 685 |
firedns_errno = FDNS_ERR_NXDOMAIN; |
| 686 |
/* no sense going on if we don't have any answers */ |
| 687 |
goto cleanup; |
| 688 |
} |
| 689 |
|
| 690 |
/* skip queries */ |
| 691 |
i = 0; |
| 692 |
q = 0; |
| 693 |
l -= 12; |
| 694 |
|
| 695 |
while (q < h.qdcount && i < l) |
| 696 |
{ |
| 697 |
if (h.payload[i] > 63) |
| 698 |
{ |
| 699 |
/* pointer */ |
| 700 |
i += 6; /* skip pointer, class and type */ |
| 701 |
q++; |
| 702 |
} |
| 703 |
else |
| 704 |
{ |
| 705 |
/* label */ |
| 706 |
if (h.payload[i] == 0) |
| 707 |
{ |
| 708 |
q++; |
| 709 |
i += 5; /* skip nil, class and type */ |
| 710 |
} |
| 711 |
else |
| 712 |
i += h.payload[i] + 1; /* skip length and label */ |
| 713 |
} |
| 714 |
} |
| 715 |
|
| 716 |
/* &h.payload[i] should now be the start of the first response */ |
| 717 |
curanswer = 0; |
| 718 |
|
| 719 |
while (curanswer < h.ancount) |
| 720 |
{ |
| 721 |
q = 0; |
| 722 |
|
| 723 |
while (q == 0 && i < l) |
| 724 |
{ |
| 725 |
if (h.payload[i] > 63) |
| 726 |
{ |
| 727 |
/* pointer */ |
| 728 |
i += 2; /* skip pointer */ |
| 729 |
q = 1; |
| 730 |
} |
| 731 |
else |
| 732 |
{ |
| 733 |
/* label */ |
| 734 |
if (h.payload[i] == 0) |
| 735 |
{ |
| 736 |
i++; |
| 737 |
q = 1; |
| 738 |
} |
| 739 |
else |
| 740 |
i += h.payload[i] + 1; /* skip length and label */ |
| 741 |
} |
| 742 |
} |
| 743 |
|
| 744 |
if (l - i < 10) |
| 745 |
goto cleanup; |
| 746 |
|
| 747 |
firedns_fill_rr(&rr, &h.payload[i]); |
| 748 |
i += 10; |
| 749 |
|
| 750 |
if (rr.type != c->type) |
| 751 |
{ |
| 752 |
curanswer++; |
| 753 |
i += rr.rdlength; |
| 754 |
continue; |
| 755 |
} |
| 756 |
|
| 757 |
if (rr.class != c->class) |
| 758 |
{ |
| 759 |
curanswer++; |
| 760 |
i += rr.rdlength; |
| 761 |
continue; |
| 762 |
} |
| 763 |
|
| 764 |
break; |
| 765 |
} |
| 766 |
|
| 767 |
if (curanswer == h.ancount) |
| 768 |
goto cleanup; |
| 769 |
if (i + rr.rdlength > l) |
| 770 |
goto cleanup; |
| 771 |
if (rr.rdlength > 1023) |
| 772 |
goto cleanup; |
| 773 |
|
| 774 |
firedns_errno = FDNS_ERR_NONE; |
| 775 |
memcpy(result.text, &h.payload[i], rr.rdlength); |
| 776 |
result.text[rr.rdlength] = '\0'; |
| 777 |
|
| 778 |
/* Clean-up */ |
| 779 |
cleanup: |
| 780 |
list_remove(&CONNECTIONS, node); |
| 781 |
node_free(node); |
| 782 |
|
| 783 |
close(c->fd); |
| 784 |
xfree(c); |
| 785 |
firedns_fdinuse--; |
| 786 |
|
| 787 |
return &result; |
| 788 |
} |
| 789 |
|
| 790 |
void |
| 791 |
firedns_cycle(void) |
| 792 |
{ |
| 793 |
node_t *node, *node_next; |
| 794 |
struct s_connection *p; |
| 795 |
struct firedns_result *res, new_result; |
| 796 |
static struct pollfd *ufds = NULL; |
| 797 |
unsigned int size = 0; |
| 798 |
time_t timenow; |
| 799 |
|
| 800 |
if (LIST_SIZE(&CONNECTIONS) == 0) |
| 801 |
return; |
| 802 |
|
| 803 |
if (ufds == NULL) |
| 804 |
ufds = xcalloc(sizeof(*ufds) * OptionsItem->dns_fdlimit); |
| 805 |
|
| 806 |
time(&timenow); |
| 807 |
|
| 808 |
LIST_FOREACH_SAFE(node, node_next, CONNECTIONS.head) |
| 809 |
{ |
| 810 |
if (size >= OptionsItem->dns_fdlimit) |
| 811 |
break; |
| 812 |
|
| 813 |
p = node->data; |
| 814 |
|
| 815 |
if (p->fd < 0) |
| 816 |
continue; |
| 817 |
|
| 818 |
if (p->fd > 0 && (p->start + OptionsItem->dns_timeout) < timenow) |
| 819 |
{ |
| 820 |
/* Timed out - remove from list */ |
| 821 |
list_remove(&CONNECTIONS, node); |
| 822 |
node_free(node); |
| 823 |
|
| 824 |
memset(new_result.text, 0, sizeof(new_result.text)); |
| 825 |
new_result.info = p->info; |
| 826 |
strlcpy(new_result.lookup, p->lookup, sizeof(new_result.lookup)); |
| 827 |
|
| 828 |
close(p->fd); |
| 829 |
xfree(p); |
| 830 |
firedns_fdinuse--; |
| 831 |
|
| 832 |
firedns_errno = FDNS_ERR_TIMEOUT; |
| 833 |
|
| 834 |
if (new_result.info) |
| 835 |
dnsbl_result(&new_result); |
| 836 |
|
| 837 |
continue; |
| 838 |
} |
| 839 |
|
| 840 |
ufds[size].events = 0; |
| 841 |
ufds[size].revents = 0; |
| 842 |
ufds[size].fd = p->fd; |
| 843 |
ufds[size].events = POLLIN; |
| 844 |
|
| 845 |
++size; |
| 846 |
} |
| 847 |
|
| 848 |
switch (poll(ufds, size, 0)) |
| 849 |
{ |
| 850 |
case -1: |
| 851 |
case 0: |
| 852 |
return; |
| 853 |
} |
| 854 |
|
| 855 |
LIST_FOREACH_SAFE(node, node_next, CONNECTIONS.head) |
| 856 |
{ |
| 857 |
p = node->data; |
| 858 |
|
| 859 |
if (p->fd > 0) |
| 860 |
{ |
| 861 |
for (unsigned int i = 0; i < size; ++i) |
| 862 |
{ |
| 863 |
if ((ufds[i].revents & POLLIN) && ufds[i].fd == p->fd) |
| 864 |
{ |
| 865 |
res = firedns_getresult(p->fd); |
| 866 |
|
| 867 |
if (res && res->info) |
| 868 |
dnsbl_result(res); |
| 869 |
|
| 870 |
break; |
| 871 |
} |
| 872 |
} |
| 873 |
} |
| 874 |
else if (firedns_fdinuse < OptionsItem->dns_fdlimit) |
| 875 |
firedns_doquery(p); |
| 876 |
} |
| 877 |
} |
| 878 |
|
| 879 |
const char * |
| 880 |
firedns_strerror(int error) |
| 881 |
{ |
| 882 |
if (error == FDNS_ERR_NETWORK) |
| 883 |
return strerror(errno); |
| 884 |
|
| 885 |
return errors[error]; |
| 886 |
} |