static int ssh_connect_ai_timeout(ssh_session session, const char *host, int port, struct addrinfo *ai, long timeout, long usec, socket_t s) { int timeout_ms; ssh_pollfd_t fds; int rc = 0; int ret; socklen_t len = sizeof(rc); /* I know we're losing some precision. But it's not like poll-like family * type of mechanisms are precise up to the microsecond. */ timeout_ms=timeout * 1000 + usec / 1000; rc = ssh_socket_set_nonblocking(s); if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Failed to set socket non-blocking for %s:%d", host, port); ssh_connect_socket_close(s); return -1; } SSH_LOG(SSH_LOG_RARE, "Trying to connect to host: %s:%d with " "timeout %d ms", host, port, timeout_ms); /* The return value is checked later */ connect(s, ai->ai_addr, ai->ai_addrlen); freeaddrinfo(ai); fds.fd=s; fds.revents=0; fds.events=POLLOUT; #ifdef _WIN32 fds.events |= POLLWRNORM; #endif rc = ssh_poll(&fds,1,timeout_ms); if (rc == 0) { /* timeout */ ssh_set_error(session, SSH_FATAL, "Timeout while connecting to %s:%d", host, port); ssh_connect_socket_close(s); return -1; } if (rc < 0) { ssh_set_error(session, SSH_FATAL, "poll error: %s", strerror(errno)); ssh_connect_socket_close(s); return -1; } rc = -1; /* Get connect(2) return code. Zero means no error */ ret = getsockopt(s, SOL_SOCKET, SO_ERROR,(char *) &rc, &len); if (ret < 0 || rc != 0) { ssh_set_error(session, SSH_FATAL, "Connect to %s:%d failed: %s", host, port, strerror(rc)); ssh_connect_socket_close(s); return -1; } /* s is connected ? */ SSH_LOG(SSH_LOG_PACKET, "Socket connected with timeout\n"); ret = ssh_socket_set_blocking(s); if (ret < 0) { ssh_set_error(session, SSH_FATAL, "Failed to set socket as blocking connecting to %s:%d failed: %s", host, port, strerror(errno)); ssh_connect_socket_close(s); return -1; } return s; }
/** * @brief SSH poll callback. This callback will be used when an event * caught on the socket. * * @param p Poll object this callback belongs to. * @param fd The raw socket. * @param revents The current poll events on the socket. * @param userdata Userdata to be passed to the callback function, * in this case the socket object. * * @return 0 on success, < 0 when the poll object has been removed * from its poll context. */ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, socket_t fd, int revents, void *v_s) { ssh_socket s = (ssh_socket)v_s; char buffer[MAX_BUF_SIZE]; int r; int err = 0; socklen_t errlen = sizeof(err); /* Do not do anything if this socket was already closed */ if (!ssh_socket_is_open(s)) { return -1; } SSH_LOG(SSH_LOG_TRACE, "Poll callback on socket %d (%s%s%s), out buffer %d",fd, (revents & POLLIN) ? "POLLIN ":"", (revents & POLLOUT) ? "POLLOUT ":"", (revents & POLLERR) ? "POLLERR":"", ssh_buffer_get_len(s->out_buffer)); if (revents & POLLERR || revents & POLLHUP) { /* Check if we are in a connecting state */ if (s->state == SSH_SOCKET_CONNECTING) { s->state = SSH_SOCKET_ERROR; r = getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen); if (r < 0) { err = errno; } s->last_errno = err; ssh_socket_close(s); if (s->callbacks && s->callbacks->connected) { s->callbacks->connected(SSH_SOCKET_CONNECTED_ERROR, err, s->callbacks->userdata); } return -1; } /* Then we are in a more standard kind of error */ /* force a read to get an explanation */ revents |= POLLIN; } if ((revents & POLLIN) && s->state == SSH_SOCKET_CONNECTED) { s->read_wontblock = 1; r = ssh_socket_unbuffered_read(s, buffer, sizeof(buffer)); if (r < 0) { if (p != NULL) { ssh_poll_remove_events(p, POLLIN); } if (s->callbacks && s->callbacks->exception) { s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, s->last_errno, s->callbacks->userdata); /* p may have been freed, so don't use it * anymore in this function */ p = NULL; return -2; } } if (r == 0) { if (p != NULL) { ssh_poll_remove_events(p, POLLIN); } if (p != NULL) { ssh_poll_remove_events(p, POLLIN); } if (s->callbacks && s->callbacks->exception) { s->callbacks->exception(SSH_SOCKET_EXCEPTION_EOF, 0, s->callbacks->userdata); /* p may have been freed, so don't use it * anymore in this function */ p = NULL; return -2; } } if (r > 0) { if (s->session->socket_counter != NULL) { s->session->socket_counter->in_bytes += r; } /* Bufferize the data and then call the callback */ r = ssh_buffer_add_data(s->in_buffer, buffer, r); if (r < 0) { return -1; } if (s->callbacks && s->callbacks->data) { do { r = s->callbacks->data(ssh_buffer_get(s->in_buffer), ssh_buffer_get_len(s->in_buffer), s->callbacks->userdata); ssh_buffer_pass_bytes(s->in_buffer, r); } while ((r > 0) && (s->state == SSH_SOCKET_CONNECTED)); /* p may have been freed, so don't use it * anymore in this function */ p = NULL; } } } #ifdef _WIN32 if (revents & POLLOUT || revents & POLLWRNORM) { #else if (revents & POLLOUT) { #endif /* First, POLLOUT is a sign we may be connected */ if (s->state == SSH_SOCKET_CONNECTING) { SSH_LOG(SSH_LOG_PACKET, "Received POLLOUT in connecting state"); s->state = SSH_SOCKET_CONNECTED; if (p != NULL) { ssh_poll_set_events(p, POLLOUT | POLLIN); } r = ssh_socket_set_blocking(ssh_socket_get_fd_in(s)); if (r < 0) { return -1; } if (s->callbacks && s->callbacks->connected) { s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, 0, s->callbacks->userdata); } return 0; } /* So, we can write data */ s->write_wontblock=1; if (p != NULL) { ssh_poll_remove_events(p, POLLOUT); } /* If buffered data is pending, write it */ if (ssh_buffer_get_len(s->out_buffer) > 0) { ssh_socket_nonblocking_flush(s); } else if (s->callbacks && s->callbacks->controlflow) { /* Otherwise advertise the upper level that write can be done */ SSH_LOG(SSH_LOG_TRACE,"sending control flow event"); s->callbacks->controlflow(SSH_SOCKET_FLOW_WRITEWONTBLOCK, s->callbacks->userdata); } /* TODO: Find a way to put back POLLOUT when buffering occurs */ } /* Return -1 if one of the poll handlers disappeared */ return (s->poll_in == NULL || s->poll_out == NULL) ? -1 : 0; } /** @internal * @brief returns the input poll handle corresponding to the socket, * creates it if it does not exist. * @returns allocated and initialized ssh_poll_handle object */ ssh_poll_handle ssh_socket_get_poll_handle_in(ssh_socket s){ if(s->poll_in) return s->poll_in; s->poll_in=ssh_poll_new(s->fd_in,0,ssh_socket_pollcallback,s); if(s->fd_in == s->fd_out && s->poll_out == NULL) s->poll_out=s->poll_in; return s->poll_in; } /** @internal * @brief returns the output poll handle corresponding to the socket, * creates it if it does not exist. * @returns allocated and initialized ssh_poll_handle object */ ssh_poll_handle ssh_socket_get_poll_handle_out(ssh_socket s){ if(s->poll_out) return s->poll_out; s->poll_out=ssh_poll_new(s->fd_out,0,ssh_socket_pollcallback,s); if(s->fd_in == s->fd_out && s->poll_in == NULL) s->poll_in=s->poll_out; return s->poll_out; } /** \internal * \brief Deletes a socket object */ void ssh_socket_free(ssh_socket s){ if (s == NULL) { return; } ssh_socket_close(s); ssh_buffer_free(s->in_buffer); ssh_buffer_free(s->out_buffer); SAFE_FREE(s); } #ifndef _WIN32 int ssh_socket_unix(ssh_socket s, const char *path) { struct sockaddr_un sunaddr; socket_t fd; sunaddr.sun_family = AF_UNIX; snprintf(sunaddr.sun_path, sizeof(sunaddr.sun_path), "%s", path); fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == SSH_INVALID_SOCKET) { ssh_set_error(s->session, SSH_FATAL, "Error from socket(AF_UNIX, SOCK_STREAM, 0): %s", strerror(errno)); return -1; } if (fcntl(fd, F_SETFD, 1) == -1) { ssh_set_error(s->session, SSH_FATAL, "Error from fcntl(fd, F_SETFD, 1): %s", strerror(errno)); close(fd); return -1; } if (connect(fd, (struct sockaddr *) &sunaddr, sizeof(sunaddr)) < 0) { ssh_set_error(s->session, SSH_FATAL, "Error from connect(): %s", strerror(errno)); close(fd); return -1; } ssh_socket_set_fd(s,fd); return 0; } #endif /** \internal * \brief closes a socket */ void ssh_socket_close(ssh_socket s){ if (ssh_socket_is_open(s)) { #ifdef _WIN32 CLOSE_SOCKET(s->fd_in); /* fd_in = fd_out under win32 */ s->last_errno = WSAGetLastError(); #else if (s->fd_out != s->fd_in && s->fd_out != -1) { CLOSE_SOCKET(s->fd_out); } CLOSE_SOCKET(s->fd_in); s->last_errno = errno; #endif } if(s->poll_in != NULL){ if(s->poll_out == s->poll_in) s->poll_out = NULL; ssh_poll_free(s->poll_in); s->poll_in=NULL; } if(s->poll_out != NULL){ ssh_poll_free(s->poll_out); s->poll_out=NULL; } s->state = SSH_SOCKET_CLOSED; } /** * @internal * @brief sets the file descriptor of the socket. * @param[out] s ssh_socket to update * @param[in] fd file descriptor to set * @warning this function updates boths the input and output * file descriptors */ void ssh_socket_set_fd(ssh_socket s, socket_t fd) { s->fd_in = s->fd_out = fd; if (s->poll_in) { ssh_poll_set_fd(s->poll_in,fd); } else { s->state = SSH_SOCKET_CONNECTING; /* POLLOUT is the event to wait for in a nonblocking connect */ ssh_poll_set_events(ssh_socket_get_poll_handle_in(s), POLLOUT); #ifdef _WIN32 ssh_poll_add_events(ssh_socket_get_poll_handle_in(s), POLLWRNORM); #endif } }