/* SSSD Socket utils Copyright (C) Simo Sorce 2016 Copyright (C) Sumit Bose 2009 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #include "util/util.h" static errno_t set_fcntl_flags(int fd, int fd_flags, int fl_flags) { int ret; int cur_flags; ret = fcntl(fd, F_GETFD, 0); if (ret == -1) { ret = errno; DEBUG(SSSDBG_CRIT_FAILURE, "fcntl F_GETFD failed [%d][%s].\n", ret, strerror(ret)); return ret; } cur_flags = ret; ret = fcntl(fd, F_SETFD, cur_flags | fd_flags); if (ret == -1) { ret = errno; DEBUG(SSSDBG_CRIT_FAILURE, "fcntl F_SETFD failed [%d][%s].\n", ret, strerror(ret)); return ret; } ret = fcntl(fd, F_GETFL, 0); if (ret == -1) { ret = errno; DEBUG(SSSDBG_CRIT_FAILURE, "fcntl F_GETFD failed [%d][%s].\n", ret, strerror(ret)); return ret; } cur_flags = ret; ret = fcntl(fd, F_SETFL, cur_flags | fl_flags); if (ret == -1) { ret = errno; DEBUG(SSSDBG_CRIT_FAILURE, "fcntl F_SETFD failed [%d][%s].\n", ret, strerror(ret)); return ret; } return EOK; } static errno_t set_fd_common_opts(int fd) { int dummy = 1; int ret; /* SO_KEEPALIVE and TCP_NODELAY are set by OpenLDAP client libraries but * failures are ignored.*/ ret = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &dummy, sizeof(dummy)); if (ret != 0) { ret = errno; DEBUG(SSSDBG_FUNC_DATA, "setsockopt SO_KEEPALIVE failed.[%d][%s].\n", ret, strerror(ret)); } ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &dummy, sizeof(dummy)); if (ret != 0) { ret = errno; DEBUG(SSSDBG_FUNC_DATA, "setsockopt TCP_NODELAY failed.[%d][%s].\n", ret, strerror(ret)); } return EOK; } struct sssd_async_connect_state { struct tevent_fd *fde; int fd; socklen_t addr_len; struct sockaddr_storage addr; }; static void sssd_async_connect_done(struct tevent_context *ev, struct tevent_fd *fde, uint16_t flags, void *priv); struct tevent_req *sssd_async_connect_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, int fd, const struct sockaddr *addr, socklen_t addr_len) { struct tevent_req *req; struct sssd_async_connect_state *state; int ret; req = tevent_req_create(mem_ctx, &state, struct sssd_async_connect_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); return NULL; } state->fd = fd; state->addr_len = addr_len; memcpy(&state->addr, addr, addr_len); ret = connect(fd, addr, addr_len); if (ret == EOK) { goto done; } ret = errno; switch (ret) { case EINPROGRESS: case EINTR: /* Despite the connect() man page says waiting on a non-blocking * connect should be done by checking for writability, we need to check * also for readability. * With TEVENT_FD_READ, connect fails much faster in offline mode with * errno 113/No route to host. */ state->fde = tevent_add_fd(ev, state, fd, TEVENT_FD_READ | TEVENT_FD_WRITE, sssd_async_connect_done, req); if (state->fde == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_fd failed.\n"); ret = ENOMEM; goto done; } return req; default: DEBUG(SSSDBG_CRIT_FAILURE, "connect failed [%d][%s].\n", ret, strerror(ret)); } done: if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } tevent_req_post(req, ev); return req; } static void sssd_async_connect_done(struct tevent_context *ev, struct tevent_fd *fde, uint16_t flags, void *priv) { struct tevent_req *req = talloc_get_type(priv, struct tevent_req); struct sssd_async_connect_state *state = tevent_req_data(req, struct sssd_async_connect_state); int ret; errno = 0; ret = connect(state->fd, (struct sockaddr *) &state->addr, state->addr_len); if (ret == -1) { ret = errno; if (ret == EALREADY || ret == EINPROGRESS || ret == EINTR) { return; /* Try again later */ } } talloc_zfree(fde); if (ret == EOK) { tevent_req_done(req); } else { ret = errno; DEBUG(SSSDBG_CRIT_FAILURE, "connect failed [%d][%s].\n", ret, strerror(ret)); tevent_req_error(req, ret); } } int sssd_async_connect_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } static void sssd_async_connect_timeout(struct tevent_context *ev, struct tevent_timer *te, struct timeval tv, void *pvt) { struct tevent_req *connection_request; DEBUG(SSSDBG_CONF_SETTINGS, "The connection timed out\n"); connection_request = talloc_get_type(pvt, struct tevent_req); tevent_req_error(connection_request, ETIMEDOUT); } struct sssd_async_socket_state { struct tevent_timer *connect_timeout; int sd; }; static int sssd_async_socket_state_destructor(void *data); static void sssd_async_socket_init_done(struct tevent_req *subreq); struct tevent_req *sssd_async_socket_init_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sockaddr_storage *addr, socklen_t addr_len, int timeout) { struct sssd_async_socket_state *state; struct tevent_req *req, *subreq; struct timeval tv; int ret; req = tevent_req_create(mem_ctx, &state, struct sssd_async_socket_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); return NULL; } state->sd = -1; talloc_set_destructor((TALLOC_CTX *)state, sssd_async_socket_state_destructor); state->sd = socket(addr->ss_family, SOCK_STREAM, 0); if (state->sd == -1) { ret = errno; DEBUG(SSSDBG_CRIT_FAILURE, "socket failed [%d][%s].\n", ret, strerror(ret)); goto fail; } ret = set_fd_common_opts(state->sd); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "set_fd_common_opts failed.\n"); goto fail; } ret = set_fcntl_flags(state->sd, FD_CLOEXEC, O_NONBLOCK); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "settting fd flags failed.\n"); goto fail; } DEBUG(SSSDBG_TRACE_ALL, "Using file descriptor [%d] for the connection.\n", state->sd); subreq = sssd_async_connect_send(state, ev, state->sd, (struct sockaddr *) addr, addr_len); if (subreq == NULL) { ret = ENOMEM; DEBUG(SSSDBG_CRIT_FAILURE, "sssd_async_connect_send failed.\n"); goto fail; } DEBUG(SSSDBG_TRACE_FUNC, "Setting %d seconds timeout for connecting\n", timeout); tv = tevent_timeval_current_ofs(timeout, 0); state->connect_timeout = tevent_add_timer(ev, subreq, tv, sssd_async_connect_timeout, subreq); if (state->connect_timeout == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); ret = ENOMEM; goto fail; } tevent_req_set_callback(subreq, sssd_async_socket_init_done, req); return req; fail: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void sssd_async_socket_init_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct sssd_async_socket_state *state = tevent_req_data(req, struct sssd_async_socket_state); int ret; /* kill the timeout handler now that we got a reply */ talloc_zfree(state->connect_timeout); ret = sssd_async_connect_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "sdap_async_sys_connect request failed: [%d]: %s.\n", ret, sss_strerror(ret)); goto fail; } tevent_req_done(req); return; fail: tevent_req_error(req, ret); } int sssd_async_socket_init_recv(struct tevent_req *req, int *sd) { struct sssd_async_socket_state *state = tevent_req_data(req, struct sssd_async_socket_state); TEVENT_REQ_RETURN_ON_ERROR(req); /* steal the sd and neutralize destructor actions */ *sd = state->sd; state->sd = -1; return EOK; } static int sssd_async_socket_state_destructor(void *data) { struct sssd_async_socket_state *state = talloc_get_type(data, struct sssd_async_socket_state); if (state->sd != -1) { DEBUG(SSSDBG_TRACE_FUNC, "closing socket [%d]\n", state->sd); close(state->sd); state->sd = -1; } return 0; }