dns-lookup.c revision 30f128cc663b8aeb68bb3bfb9ed49ab2e48029e1
0N/A/* Copyright (c) 2010-2013 Dovecot authors, see the included COPYING file */
0N/A
0N/A#include "lib.h"
0N/A#include "ioloop.h"
0N/A#include "net.h"
0N/A#include "llist.h"
0N/A#include "istream.h"
0N/A#include "write-full.h"
0N/A#include "time-util.h"
0N/A#include "dns-lookup.h"
0N/A
0N/A#include <stdio.h>
0N/A#include <unistd.h>
0N/A
0N/A#define MAX_INBUF_SIZE 512
0N/A
0N/Astruct dns_lookup {
0N/A struct dns_lookup *prev, *next;
873N/A struct dns_client *client;
0N/A bool ptr_lookup;
0N/A
0N/A struct timeout *to;
0N/A
0N/A struct timeval start_time;
3231N/A unsigned int warn_msecs;
6130N/A
0N/A struct dns_lookup_result result;
0N/A struct ip_addr *ips;
0N/A unsigned int ip_idx;
6355N/A char *name;
0N/A
0N/A dns_lookup_callback_t *callback;
6355N/A void *context;
5550N/A};
0N/A
0N/Astruct dns_client {
0N/A int fd;
1400N/A char *path;
6355N/A
6355N/A unsigned int timeout_msecs, idle_timeout_msecs;
6355N/A
4134N/A struct istream *input;
4134N/A struct io *io;
2086N/A struct timeout *to_idle;
6355N/A
0N/A struct dns_lookup *head, *tail;
0N/A bool deinit_client_at_free;
6355N/A};
0N/A
0N/A#undef dns_lookup
0N/A#undef dns_lookup_ptr
0N/A#undef dns_client_lookup
0N/A#undef dns_client_lookup_ptr
0N/A
0N/Astatic void dns_lookup_free(struct dns_lookup **_lookup);
0N/A
0N/Astatic void dns_client_disconnect(struct dns_client *client, const char *error)
0N/A{
0N/A struct dns_lookup *lookup;
0N/A struct dns_lookup_result result;
1400N/A
5844N/A memset(&result, 0, sizeof(result));
4134N/A result.ret = EAI_FAIL;
4134N/A result.error = error;
4134N/A
4134N/A while (client->head != NULL) {
4134N/A lookup = client->head;
4134N/A lookup->callback(&result, lookup->context);
4134N/A dns_lookup_free(&lookup);
5844N/A }
4134N/A if (client->to_idle != NULL)
4134N/A timeout_remove(&client->to_idle);
4134N/A if (client->io != NULL)
4134N/A io_remove(&client->io);
4134N/A if (client->input != NULL)
4134N/A i_stream_destroy(&client->input);
4134N/A if (client->fd != -1) {
4134N/A if (close(client->fd) < 0)
4134N/A i_error("close(%s) failed: %m", client->path);
4134N/A client->fd = -1;
4134N/A }
4134N/A}
4134N/A
4134N/Astatic int dns_lookup_input_line(struct dns_lookup *lookup, const char *line)
4134N/A{
4134N/A struct dns_lookup_result *result = &lookup->result;
4134N/A
4134N/A if (result->ips_count == 0) {
4134N/A if (lookup->ptr_lookup) {
4134N/A /* <ret> [<name>] */
4134N/A if (strncmp(line, "0 ", 2) == 0) {
4134N/A result->name = lookup->name =
4134N/A i_strdup(line + 2);
4134N/A result->ret = 0;
4134N/A } else {
4134N/A if (str_to_int(line, &result->ret) < 0) {
4134N/A return -1;
4134N/A }
4134N/A result->error = net_gethosterror(result->ret);
4134N/A }
4134N/A return 1;
4134N/A }
4134N/A /* first line: <ret> <ip count> */
4134N/A if (sscanf(line, "%d %u", &result->ret,
4134N/A &result->ips_count) == 0)
4134N/A return -1;
4134N/A if (result->ret != 0) {
6130N/A result->error = net_gethosterror(result->ret);
4134N/A return 1;
4134N/A }
4134N/A if (result->ips_count == 0)
4134N/A return -1;
4134N/A
4134N/A result->ips = lookup->ips =
4134N/A i_new(struct ip_addr, result->ips_count);
4134N/A } else {
5844N/A if (net_addr2ip(line, &lookup->ips[lookup->ip_idx]) < 0)
4134N/A return -1;
4134N/A if (++lookup->ip_idx == result->ips_count) {
4134N/A result->ret = 0;
4134N/A return 1;
4134N/A }
4134N/A }
4134N/A return 0;
4134N/A}
4134N/A
4134N/Astatic void dns_lookup_save_msecs(struct dns_lookup *lookup)
4134N/A{
4134N/A struct timeval now;
4134N/A int diff;
4134N/A
1400N/A if (gettimeofday(&now, NULL) < 0)
1400N/A i_fatal("gettimeofday() failed: %m");
1400N/A
1400N/A diff = timeval_diff_msecs(&now, &lookup->start_time);
0N/A if (diff > 0)
0N/A lookup->result.msecs = diff;
0N/A}
0N/A
4134N/Astatic void dns_client_input(struct dns_client *client)
0N/A{
0N/A const char *line;
0N/A struct dns_lookup *lookup = client->head;
0N/A bool retry = FALSE;
0N/A int ret = 0;
0N/A
0N/A while ((line = i_stream_read_next_line(client->input)) != NULL) {
0N/A if (lookup == NULL) {
0N/A dns_client_disconnect(client, t_strdup_printf(
4134N/A "Unexpected input from %s", client->path));
0N/A return;
4134N/A }
0N/A ret = dns_lookup_input_line(lookup, line);
0N/A if (ret > 0)
0N/A break;
0N/A if (ret < 0) {
0N/A dns_client_disconnect(client, t_strdup_printf(
0N/A "Invalid input from %s", client->path));
0N/A return;
0N/A }
0N/A }
0N/A
0N/A if (ret != 0 && lookup->result.error != NULL) {
0N/A /* already got the error */
0N/A } else if (client->input->stream_errno != 0) {
4134N/A dns_client_disconnect(client, t_strdup_printf(
0N/A "read(%s) failed: %s", client->path,
4134N/A i_stream_get_error(client->input)));
0N/A return;
4134N/A } else if (client->input->eof) {
0N/A dns_client_disconnect(client, t_strdup_printf(
0N/A "Unexpected EOF from %s", client->path));
0N/A return;
0N/A }
0N/A if (ret > 0) {
0N/A dns_lookup_save_msecs(lookup);
0N/A lookup->callback(&lookup->result, lookup->context);
4134N/A retry = !lookup->client->deinit_client_at_free;
4134N/A dns_lookup_free(&lookup);
0N/A }
4134N/A if (retry)
4134N/A dns_client_input(client);
0N/A}
4134N/A
4134N/Astatic void dns_lookup_timeout(struct dns_lookup *lookup)
4134N/A{
0N/A lookup->result.error = "DNS lookup timed out";
0N/A
0N/A lookup->callback(&lookup->result, lookup->context);
0N/A dns_lookup_free(&lookup);
0N/A}
0N/A
0N/Aint dns_lookup(const char *host, const struct dns_lookup_settings *set,
0N/A dns_lookup_callback_t *callback, void *context,
0N/A struct dns_lookup **lookup_r)
4134N/A{
0N/A struct dns_client *client;
0N/A
0N/A client = dns_client_init(set);
0N/A client->deinit_client_at_free = TRUE;
0N/A if (dns_client_lookup(client, host, callback, context, lookup_r) < 0) {
0N/A dns_client_deinit(&client);
0N/A return -1;
773N/A }
773N/A return 0;
773N/A}
773N/A
0N/Aint dns_lookup_ptr(const struct ip_addr *ip,
773N/A const struct dns_lookup_settings *set,
773N/A dns_lookup_callback_t *callback, void *context,
773N/A struct dns_lookup **lookup_r)
0N/A{
773N/A struct dns_client *client;
773N/A
773N/A client = dns_client_init(set);
0N/A client->deinit_client_at_free = TRUE;
773N/A if (dns_client_lookup_ptr(client, ip, callback, context, lookup_r) < 0) {
0N/A dns_client_deinit(&client);
0N/A return -1;
0N/A }
4134N/A return 0;
0N/A}
773N/A
0N/Astatic void dns_client_idle_timeout(struct dns_client *client)
0N/A{
0N/A i_assert(client->head == NULL);
0N/A
0N/A dns_client_disconnect(client, "Idle timeout");
4134N/A}
0N/A
0N/Astatic void dns_lookup_free(struct dns_lookup **_lookup)
0N/A{
0N/A struct dns_lookup *lookup = *_lookup;
4134N/A struct dns_client *client = lookup->client;
0N/A
0N/A *_lookup = NULL;
773N/A
0N/A DLLIST2_REMOVE(&client->head, &client->tail, lookup);
0N/A if (lookup->to != NULL)
0N/A timeout_remove(&lookup->to);
773N/A i_free(lookup->name);
773N/A i_free(lookup->ips);
773N/A if (client->deinit_client_at_free)
773N/A dns_client_deinit(&client);
773N/A else if (client->head == NULL) {
773N/A client->to_idle = timeout_add(client->idle_timeout_msecs,
773N/A dns_client_idle_timeout, client);
773N/A }
6355N/A i_free(lookup);
0N/A}
0N/A
6355N/Avoid dns_lookup_abort(struct dns_lookup **lookup)
6355N/A{
0N/A dns_lookup_free(lookup);
0N/A}
0N/A
0N/Avoid dns_lookup_switch_ioloop(struct dns_lookup *lookup)
0N/A{
0N/A if (lookup->to != NULL)
0N/A lookup->to = io_loop_move_timeout(&lookup->to);
0N/A lookup->client->io = io_loop_move_io(&lookup->client->io);
4134N/A}
0N/A
2086N/Astruct dns_client *dns_client_init(const struct dns_lookup_settings *set)
0N/A{
0N/A struct dns_client *client;
0N/A
0N/A client = i_new(struct dns_client, 1);
5492N/A client->path = i_strdup(set->dns_client_socket_path);
0N/A client->timeout_msecs = set->timeout_msecs;
0N/A client->idle_timeout_msecs = set->idle_timeout_msecs;
773N/A client->fd = -1;
0N/A return client;
0N/A}
0N/A
0N/Avoid dns_client_deinit(struct dns_client **_client)
0N/A{
0N/A struct dns_client *client = *_client;
0N/A
0N/A *_client = NULL;
0N/A
0N/A i_assert(client->head == NULL);
0N/A
0N/A dns_client_disconnect(client, "deinit");
773N/A i_free(client->path);
0N/A i_free(client);
0N/A}
0N/A
0N/Aint dns_client_connect(struct dns_client *client, const char **error_r)
0N/A{
4134N/A if (client->fd != -1)
0N/A return 0;
0N/A
0N/A client->fd = net_connect_unix(client->path);
2086N/A if (client->fd == -1) {
2086N/A *error_r = t_strdup_printf("connect(%s) failed: %m",
0N/A client->path);
0N/A return -1;
4134N/A }
0N/A client->input = i_stream_create_fd(client->fd, MAX_INBUF_SIZE, FALSE);
0N/A client->io = io_add(client->fd, IO_READ, dns_client_input, client);
4134N/A return 0;
2086N/A}
0N/A
0N/Astatic int
0N/Adns_client_send_request(struct dns_client *client, const char *cmd,
0N/A const char **error_r)
0N/A{
5492N/A int ret;
0N/A
773N/A if (client->fd == -1) {
0N/A if (dns_client_connect(client, error_r) < 0)
0N/A return -1;
0N/A ret = -1;
0N/A } else {
4134N/A /* already connected. if write() fails, retry connecting */
2086N/A ret = 0;
0N/A }
0N/A
0N/A if (write_full(client->fd, cmd, strlen(cmd)) < 0) {
0N/A *error_r = t_strdup_printf("write(%s) failed: %m", client->path);
0N/A return ret;
5492N/A }
5492N/A return 1;
5492N/A}
5550N/A
5550N/Astatic int
5550N/Adns_client_lookup_common(struct dns_client *client,
5550N/A const char *cmd, bool ptr_lookup,
5492N/A dns_lookup_callback_t *callback, void *context,
5550N/A struct dns_lookup **lookup_r)
5550N/A{
5550N/A struct dns_lookup *lookup;
5550N/A struct dns_lookup_result result;
5550N/A int ret;
5550N/A
5550N/A memset(&result, 0, sizeof(result));
5550N/A result.ret = EAI_FAIL;
5550N/A
5492N/A if ((ret = dns_client_send_request(client, cmd, &result.error)) <= 0) {
5492N/A if (ret == 0) {
5492N/A /* retry once */
5492N/A ret = dns_client_send_request(client, cmd, &result.error);
5492N/A }
5492N/A if (ret <= 0) {
5492N/A callback(&result, context);
5492N/A return -1;
5492N/A }
5492N/A }
5492N/A
5492N/A lookup = i_new(struct dns_lookup, 1);
5492N/A lookup->client = client;
5492N/A lookup->ptr_lookup = ptr_lookup;
5492N/A if (client->timeout_msecs != 0) {
0N/A lookup->to = timeout_add(client->timeout_msecs,
5844N/A dns_lookup_timeout, lookup);
5844N/A }
0N/A lookup->result.ret = EAI_FAIL;
0N/A lookup->callback = callback;
0N/A lookup->context = context;
4134N/A if (gettimeofday(&lookup->start_time, NULL) < 0)
0N/A i_fatal("gettimeofday() failed: %m");
0N/A
0N/A if (client->to_idle != NULL)
4134N/A timeout_remove(&client->to_idle);
0N/A DLLIST2_APPEND(&client->head, &client->tail, lookup);
0N/A *lookup_r = lookup;
0N/A return 0;
0N/A}
int dns_client_lookup(struct dns_client *client, const char *host,
dns_lookup_callback_t *callback, void *context,
struct dns_lookup **lookup_r)
{
const char *cmd = t_strconcat("IP\t", host, "\n", NULL);
return dns_client_lookup_common(client, cmd, FALSE,
callback, context, lookup_r);
}
int dns_client_lookup_ptr(struct dns_client *client, const struct ip_addr *ip,
dns_lookup_callback_t *callback, void *context,
struct dns_lookup **lookup_r)
{
const char *cmd = t_strconcat("NAME\t", net_ip2addr(ip), "\n", NULL);
return dns_client_lookup_common(client, cmd, TRUE,
callback, context, lookup_r);
}