diff --git a/src/lib-program-client/program-client-remote.c b/src/lib-program-client/program-client-remote.c index 9933297151..82e905af21 100644 --- a/src/lib-program-client/program-client-remote.c +++ b/src/lib-program-client/program-client-remote.c @@ -9,13 +9,16 @@ #include "eacces-error.h" #include "istream-private.h" #include "ostream.h" - +#include "dns-lookup.h" #include "program-client-private.h" #include #include #include +static +void program_client_net_connect_again(struct program_client *pclient); + /* * Script client input stream */ @@ -185,6 +188,17 @@ struct program_client_remote { struct program_client client; bool noreply:1; + bool resolved:1; + + const char *hostname; + struct dns_lookup_settings dns_set; + struct dns_lookup *lookup; + unsigned int ips_count; + unsigned int ips_left; + struct ip_addr *ips; + in_port_t port; + + struct timeout *to_retry; }; static @@ -262,6 +276,208 @@ int program_client_unix_connect(struct program_client *pclient) return 0; } +static +void program_client_net_connect_timeout(struct program_client *pclient) +{ + io_remove(&pclient->io); + timeout_remove(&pclient->to); + + i_error("connect(%s) failed: timeout in %u milliseconds", + pclient->path, + pclient->set.client_connect_timeout_msecs); + /* set error to timeout here */ + pclient->error = PROGRAM_CLIENT_ERROR_CONNECT_TIMEOUT; + i_close_fd(&pclient->fd_out); + pclient->fd_in = pclient->fd_out = -1; + program_client_net_connect_again(pclient); +} + +/* see if connect suceeded or not, if it did, then proceed + normally, otherwise try reconnect to next address */ +static +void program_client_net_connected(struct program_client *pclient) +{ + io_remove(&pclient->io); + if ((errno = net_geterror(pclient->fd_out)) != 0) { + i_error("connect(%s) failed: %m", + pclient->path); + /* disconnect and try again */ + i_close_fd(&pclient->fd_out); + pclient->fd_in = pclient->fd_out = -1; + program_client_net_connect_again(pclient); + } else { + pclient->io = io_add(pclient->fd_out, IO_WRITE, + program_client_remote_connected, pclient); + } +} + +static +void program_client_net_connect_real(struct program_client *pclient) +{ + struct program_client_remote *slclient = + (struct program_client_remote *) pclient; + + if (pclient->to != NULL) + timeout_remove(&pclient->to); + + if (slclient->to_retry != NULL) + timeout_remove(&slclient->to_retry); + + i_assert(slclient->ips_count > 0); + + bool ipv6 = slclient->ips->family == AF_INET6; + pclient->path = p_strdup_printf(pclient->pool, "%s%s%s:%u", + ipv6 ? "[" : "", + net_ip2addr(slclient->ips), + ipv6 ? "]" : "", + slclient->port); + + if (pclient->debug) { + i_debug("Trying to connect %s (timeout %u msecs)", + pclient->path, + pclient->set.client_connect_timeout_msecs); + } + + /* try to connect */ + int fd; + if ((fd = net_connect_ip(slclient->ips, slclient->port, + (slclient->ips->family == AF_INET ? + &net_ip4_any : &net_ip6_any))) < 0) { + i_error("connect(%s) failed: %m", pclient->path); + slclient->to_retry = timeout_add_short(0, + program_client_net_connect_again, + pclient); + return; + } + + pclient->fd_in = (slclient->noreply && pclient->output == NULL && + !pclient->output_seekable ? -1 : fd); + pclient->fd_out = fd; + pclient->io = io_add(fd, IO_WRITE, program_client_net_connected, pclient); + + if (pclient->set.client_connect_timeout_msecs != 0) { + pclient->to = timeout_add(pclient->set.client_connect_timeout_msecs, + program_client_net_connect_timeout, pclient); + } +} + +static +void program_client_net_connect_again(struct program_client *pclient) +{ + struct program_client_remote *slclient = + (struct program_client_remote *) pclient; + + enum program_client_error error = pclient->error; + pclient->error = PROGRAM_CLIENT_ERROR_NONE; + + if (--slclient->ips_left == 0) { + if (slclient->ips_count > 1) + i_error("program-client-net: %s: No addresses left to try", + slclient->hostname); + program_client_fail(pclient, + error != PROGRAM_CLIENT_ERROR_NONE ? + error : + PROGRAM_CLIENT_ERROR_OTHER); + return; + }; + + slclient->ips++; + program_client_net_connect_real(pclient); +} + +static +void program_client_net_connect_resolved(const struct dns_lookup_result *result, + struct program_client *pclient) +{ + struct program_client_remote *slclient = + (struct program_client_remote *) pclient; + + if (result->ret != 0) { + i_error("program-client-net: Cannot resolve '%s': %s", + pclient->path, + result->error); + program_client_fail(pclient, PROGRAM_CLIENT_ERROR_OTHER); + return; + } + + /* reduce timeout */ + if (pclient->set.client_connect_timeout_msecs > 0) { + if (pclient->set.client_connect_timeout_msecs <= result->msecs) { + /* we ran out of time */ + program_client_fail(pclient, + PROGRAM_CLIENT_ERROR_CONNECT_TIMEOUT); + return; + } + pclient->set.client_connect_timeout_msecs -= result->msecs; + } + + /* then connect */ + slclient->ips_count = result->ips_count; + slclient->ips_left = slclient->ips_count; + slclient->ips = p_memdup(pclient->pool, result->ips, + sizeof(struct ip_addr)*result->ips_count); + program_client_net_connect_real(pclient); +} + +static +int program_client_net_connect_init(struct program_client *pclient) +{ + struct program_client_remote *slclient = + (struct program_client_remote *) pclient; + + struct ip_addr ip; + + if (slclient->ips != NULL) { + slclient->hostname = p_strdup(pclient->pool, + net_ip2addr(slclient->ips)); + } else if (net_addr2ip(pclient->path, &ip) == 0) { + slclient->hostname = p_strdup(pclient->pool, + net_ip2addr(&ip)); + slclient->resolved = TRUE; + slclient->ips = p_new(pclient->pool, struct ip_addr, 1); + *slclient->ips = ip; + slclient->ips_count = 1; + } else { + slclient->resolved = FALSE; + slclient->hostname = p_strdup(pclient->pool, pclient->path); + if (pclient->set.dns_client_socket_path != NULL) { + slclient->dns_set.dns_client_socket_path = + pclient->set.dns_client_socket_path; + slclient->dns_set.timeout_msecs = + pclient->set.client_connect_timeout_msecs; + dns_lookup(pclient->path, &slclient->dns_set, + program_client_net_connect_resolved, + pclient, &slclient->lookup); + return 0; + } else { + struct ip_addr *ips; + unsigned int ips_count; + int err; + /* guess we do it here then.. */ + if ((err = net_gethostbyname(pclient->path, + &ips, &ips_count)) != 0) { + i_error("program-client-remote: " + "Cannot resolve '%s': %s", + pclient->path, + net_gethosterror(err)); + return -1; + } + slclient->ips_count = ips_count; + slclient->ips = p_memdup(pclient->pool, + ips, + sizeof(*ips)*ips_count); + } + } + + slclient->ips_left = slclient->ips_count; + slclient->to_retry = timeout_add_short(0, + program_client_net_connect_real, + pclient); + + return 0; +} + + static int program_client_remote_close_output(struct program_client *pclient) { @@ -317,8 +533,14 @@ void program_client_remote_disconnect(struct program_client *pclient, bool force } static -void program_client_remote_switch_ioloop(struct program_client *pclient ATTR_UNUSED) +void program_client_remote_switch_ioloop(struct program_client *pclient) { + struct program_client_remote *slclient = + (struct program_client_remote *)pclient; + if (slclient->to_retry != NULL) + slclient->to_retry = io_loop_move_timeout(&slclient->to_retry); + if (slclient->lookup) + dns_lookup_switch_ioloop(slclient->lookup); } struct program_client * @@ -340,3 +562,51 @@ program_client_unix_create(const char *socket_path, const char *const *args, return &pclient->client; } + +struct program_client * +program_client_net_create(const char *host, in_port_t port, + const char *const *args, + const struct program_client_settings *set, + bool noreply) +{ + struct program_client_remote *pclient; + pool_t pool; + + pool = pool_alloconly_create("program client net", 1024); + pclient = p_new(pool, struct program_client_remote, 1); + program_client_init(&pclient->client, pool, host, args, set); + pclient->port = port; + pclient->client.connect = program_client_net_connect_init; + pclient->client.close_output = program_client_remote_close_output; + pclient->client.disconnect = program_client_remote_disconnect; + pclient->noreply = noreply; + + return &pclient->client; +} + +struct program_client * +program_client_net_create_ips(const struct ip_addr *ips, size_t ips_count, + in_port_t port, + const char *const *args, + const struct program_client_settings *set, + bool noreply) +{ + struct program_client_remote *pclient; + pool_t pool; + + i_assert(ips != NULL && ips_count > 0); + + pool = pool_alloconly_create("program client net", 1024); + pclient = p_new(pool, struct program_client_remote, 1); + program_client_init(&pclient->client, pool, net_ip2addr(ips), args, set); + pclient->port = port; + pclient->client.connect = program_client_net_connect_init; + pclient->client.close_output = program_client_remote_close_output; + pclient->client.disconnect = program_client_remote_disconnect; + pclient->client.switch_ioloop = program_client_remote_switch_ioloop; + pclient->noreply = noreply; + pclient->ips = p_memdup(pool, ips, + sizeof(struct ip_addr)*ips_count); + pclient->ips_count = ips_count; + return &pclient->client; +} diff --git a/src/lib-program-client/program-client.h b/src/lib-program-client/program-client.h index 76ea56d90e..ca78fae9f7 100644 --- a/src/lib-program-client/program-client.h +++ b/src/lib-program-client/program-client.h @@ -5,6 +5,7 @@ #define PROGRAM_CLIENT_H #include "restrict-access.h" +#include "net.h" struct program_client; @@ -15,6 +16,7 @@ struct program_client_settings { restrict_access_init(&set.restrict_set); */ struct restrict_access_settings restrict_set; + const char *dns_client_socket_path; const char *home; bool allow_root:1; @@ -31,7 +33,14 @@ struct program_client *program_client_local_create(const char *bin_path, struct program_client *program_client_unix_create(const char *socket_path, const char *const *args, const struct program_client_settings *set, bool noreply); - +struct program_client *program_client_net_create(const char *host, in_port_t port, + const char *const *args, + const struct program_client_settings *set, bool noreply); +struct program_client * +program_client_net_create_ips(const struct ip_addr *ips, size_t ips_count, + in_port_t port, const char *const *args, + const struct program_client_settings *set, + bool noreply); void program_client_destroy(struct program_client **_pclient); void program_client_set_input(struct program_client *pclient,