Mercurial > dovecot > original-hg > dovecot-1.2
view src/master/listener.c @ 9658:8ba4253adc9b HEAD tip
*-login: SSL connections didn't get closed when the client got destroyed.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Thu, 08 May 2014 16:41:29 +0300 |
parents | 00cd9aacd03c |
children |
line wrap: on
line source
/* Copyright (c) 2002-2010 Dovecot authors, see the included COPYING file */ #include "common.h" #include "array.h" #include "ioloop.h" #include "fd-close-on-exec.h" #include "listener.h" #include <stdlib.h> #include <unistd.h> static void resolve_ip(const char *set_name, const char *name, struct ip_addr *ip, unsigned int *port) { struct ip_addr *ip_list; const char *p; unsigned int ips_count; int ret; if (*name == '\0') { /* defaults to "*" or "[::]" */ ip->family = 0; return; } if (name[0] == '[') { /* IPv6 address */ p = strchr(name, ']'); if (p == NULL) { i_fatal("%s: Missing ']' in address %s", set_name, name); } name = t_strdup_until(name+1, p); p++; if (*p == '\0') p = NULL; else if (*p != ':') { i_fatal("%s: Invalid data after ']' in address %s", set_name, name); } #ifndef HAVE_IPV6 i_fatal("%s: Compiled without IPv6 support, can't listen on %s", set_name, name); #endif } else { p = strrchr(name, ':'); if (p != NULL) name = t_strdup_until(name, p); } if (p != NULL) { if (!is_numeric(p+1, '\0')) { i_fatal("%s: Invalid port in address %s", set_name, name); } *port = atoi(p+1); } if (strcmp(name, "*") == 0) { /* IPv4 any */ net_get_ip_any4(ip); return; } if (strcmp(name, "::") == 0) { /* IPv6 any */ net_get_ip_any6(ip); return; } /* Return the first IP if there happens to be multiple. */ ret = net_gethostbyname(name, &ip_list, &ips_count); if (ret != 0) { i_fatal("%s: Can't resolve address %s: %s", set_name, name, net_gethosterror(ret)); } if (ips_count < 1) i_fatal("%s: No IPs for address: %s", set_name, name); *ip = ip_list[0]; } static void check_conflicts_set(const struct settings *set, const struct ip_addr *ip, unsigned int port, const char *name1, const char *name2) { const struct listener *listens = NULL; unsigned int i, count; if (array_is_created(&set->listens)) listens = array_get(&set->listens, &count); else count = 0; for (i = 0; i < count; i++) { if (listens[i].fd <= 0 || listens[i].port != port || !net_ip_compare(&listens[i].ip, ip)) continue; i_fatal("Protocols %s and %s are listening in same ip/port", name1, name2); } if (array_is_created(&set->ssl_listens)) listens = array_get(&set->ssl_listens, &count); else count = 0; for (i = 0; i < count; i++) { if (listens[i].fd <= 0 || listens[i].port != port || !net_ip_compare(&listens[i].ip, ip)) continue; i_fatal("Protocols %ss and %ss are listening in same ip/port", name1, name2); } } static void check_conflicts(const struct ip_addr *ip, unsigned int port, const char *proto) { struct server_settings *server; for (server = settings_root; server != NULL; server = server->next) { if (server->imap != NULL) { check_conflicts_set(server->imap, ip, port, "imap", proto); } if (server->pop3 != NULL) { check_conflicts_set(server->pop3, ip, port, "pop3", proto); } } } static void listener_init(const char *set_name, const char *listen_list, unsigned int default_port, ARRAY_TYPE(listener) *listens_arr) { const char *const *tmp; struct listener l, *listens; unsigned int i, count; if (!array_is_created(listens_arr)) i_array_init(listens_arr, 4); listens = array_get_modifiable(listens_arr, &count); for (i = 0; i < count; i++) listens[i].wanted = FALSE; memset(&l, 0, sizeof(l)); l.fd = -1; l.wanted = TRUE; for (tmp = t_strsplit_spaces(listen_list, ", "); *tmp != NULL; tmp++) { l.port = default_port; resolve_ip(set_name, *tmp, &l.ip, &l.port); /* see if it already exists */ for (i = 0; i < count; i++) { if (listens[i].port == l.port && net_ip_compare(&listens[i].ip, &l.ip)) { listens[i].wanted = TRUE; break; } } if (i == count) { array_append(listens_arr, &l, 1); listens = array_get_modifiable(listens_arr, &count); } } /* close unwanted fds */ for (i = 0; i < count; ) { if (listens[i].wanted) i++; else { if (listens[i].fd > 0) { if (close(listens[i].fd) < 0) i_error("close(listener) failed: %m"); } array_delete(listens_arr, i, 1); listens = array_get_modifiable(listens_arr, &count); } } } static void listener_close_fds(ARRAY_TYPE(listener) *listens_arr) { const struct listener *listens; unsigned int i, count; if (!array_is_created(listens_arr)) return; listens = array_get(listens_arr, &count); for (i = 0; i < count; i++) { if (listens[i].fd > 0) { if (close(listens[i].fd) < 0) i_error("close(listener) failed: %m"); } } array_free(listens_arr); } static void listen_parse_and_close_unneeded(struct settings *set) { const char *const *proto; unsigned int default_port; bool nonssl_listen = FALSE, ssl_listen = FALSE; if (set == NULL) return; /* register wanted protocols */ proto = t_strsplit_spaces(set->protocols, " "); for (; *proto != NULL; proto++) { if (strcasecmp(*proto, "imap") == 0) { if (set->protocol == MAIL_PROTOCOL_IMAP) nonssl_listen = TRUE; } else if (strcasecmp(*proto, "imaps") == 0) { if (set->protocol == MAIL_PROTOCOL_IMAP && strcmp(set->ssl, "no") != 0) ssl_listen = TRUE; } else if (strcasecmp(*proto, "pop3") == 0) { if (set->protocol == MAIL_PROTOCOL_POP3) nonssl_listen = TRUE; } else if (strcasecmp(*proto, "pop3s") == 0) { if (set->protocol == MAIL_PROTOCOL_POP3 && strcmp(set->ssl, "no") != 0) ssl_listen = TRUE; } } if (!nonssl_listen) listener_close_fds(&set->listens); else { default_port = set->protocol == MAIL_PROTOCOL_IMAP ? 143 : 110; listener_init("listen", set->listen, default_port, &set->listens); } if (!ssl_listen) listener_close_fds(&set->ssl_listens); else { default_port = set->protocol == MAIL_PROTOCOL_IMAP ? 993 : 995; listener_init("ssl_listen", *set->ssl_listen != '\0' ? set->ssl_listen : set->listen, default_port, &set->ssl_listens); } } static void listen_copy_old(struct settings *old_set, struct settings *new_set) { if (old_set == NULL || new_set == NULL) { if (old_set != NULL) { listener_close_fds(&old_set->listens); listener_close_fds(&old_set->ssl_listens); } return; } i_assert(!array_is_created(&new_set->listens)); i_assert(!array_is_created(&new_set->ssl_listens)); new_set->listens = old_set->listens; new_set->ssl_listens = old_set->ssl_listens; old_set->listens.arr.buffer = NULL; old_set->ssl_listens.arr.buffer = NULL; } static void listener_array_listen_missing(const char *proto, ARRAY_TYPE(listener) *listens_arr, bool retry) { struct listener *listens; unsigned int i, j, count; if (!array_is_created(listens_arr)) return; listens = array_get_modifiable(listens_arr, &count); for (i = 0; i < count; i++) { if (listens[i].fd > 0) continue; for (j = 0; j < 10; j++) { listens[i].fd = net_listen(&listens[i].ip, &listens[i].port, 128); if (listens[i].fd != -1) break; if (errno == EADDRINUSE) { /* retry */ } else if (errno == EINTR && io_loop_is_running(ioloop)) { /* SIGHUPing sometimes gets us here. we don't want to die. */ } else { /* error */ break; } check_conflicts(&listens[i].ip, listens[i].port, proto); if (!retry) break; /* wait a while and try again. we're SIGHUPing so we most likely just closed it ourself.. */ sleep(1); } if (listens[i].fd == -1) { i_fatal("listen(%s, %d) failed: %m", net_ip2addr(&listens[i].ip), listens[i].port); } net_set_nonblock(listens[i].fd, TRUE); fd_close_on_exec(listens[i].fd, TRUE); } } static void listener_listen_missing(struct settings *set, const char *proto, bool retry) { listener_array_listen_missing(proto, &set->listens, retry); listener_array_listen_missing(t_strconcat(proto, "s", NULL), &set->ssl_listens, retry); } void listeners_open_fds(struct server_settings *old_set, bool retry) { struct server_settings *server; for (server = settings_root; server != NULL; server = server->next) { if (old_set != NULL) { listen_copy_old(old_set->imap, server->imap); listen_copy_old(old_set->pop3, server->pop3); } listen_parse_and_close_unneeded(server->imap); listen_parse_and_close_unneeded(server->pop3); if (old_set != NULL) old_set = old_set->next; } for (server = settings_root; server != NULL; server = server->next) { if (server->imap != NULL) listener_listen_missing(server->imap, "imap", retry); if (server->pop3 != NULL) listener_listen_missing(server->pop3, "pop3", retry); } } void listeners_close_fds(void) { struct server_settings *server; for (server = settings_root; server != NULL; server = server->next) { if (server->imap != NULL) { listener_close_fds(&server->imap->listens); listener_close_fds(&server->imap->ssl_listens); } if (server->pop3 != NULL) { listener_close_fds(&server->pop3->listens); listener_close_fds(&server->pop3->ssl_listens); } } }