Blame src/connect.c

Packit 6c0a39
/*
Packit 6c0a39
 * connect.c - handles connections to ssh servers
Packit 6c0a39
 *
Packit 6c0a39
 * This file is part of the SSH Library
Packit 6c0a39
 *
Packit 6c0a39
 * Copyright (c) 2003-2013 by Aris Adamantiadis
Packit 6c0a39
 *
Packit 6c0a39
 * The SSH Library is free software; you can redistribute it and/or modify
Packit 6c0a39
 * it under the terms of the GNU Lesser General Public License as published by
Packit 6c0a39
 * the Free Software Foundation; either version 2.1 of the License, or (at your
Packit 6c0a39
 * option) any later version.
Packit 6c0a39
 *
Packit 6c0a39
 * The SSH Library is distributed in the hope that it will be useful, but
Packit 6c0a39
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
Packit 6c0a39
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
Packit 6c0a39
 * License for more details.
Packit 6c0a39
 *
Packit 6c0a39
 * You should have received a copy of the GNU Lesser General Public License
Packit 6c0a39
 * along with the SSH Library; see the file COPYING.  If not, write to
Packit 6c0a39
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
Packit 6c0a39
 * MA 02111-1307, USA.
Packit 6c0a39
 */
Packit 6c0a39
Packit 6c0a39
#include "config.h"
Packit 6c0a39
Packit 6c0a39
#include <errno.h>
Packit 6c0a39
#include <fcntl.h>
Packit Service fcc0d2
#include <stdbool.h>
Packit 6c0a39
#include <stdio.h>
Packit 6c0a39
#include <stdlib.h>
Packit 6c0a39
#include <string.h>
Packit 6c0a39
Packit 6c0a39
#include "libssh/libssh.h"
Packit 6c0a39
#include "libssh/misc.h"
Packit 6c0a39
Packit 6c0a39
#ifdef _WIN32
Packit 6c0a39
/*
Packit 6c0a39
 * Only use Windows API functions available on Windows 2000 SP4 or later.
Packit 6c0a39
 * The available constants are in <sdkddkver.h>.
Packit 6c0a39
 *  http://msdn.microsoft.com/en-us/library/aa383745.aspx
Packit 6c0a39
 *  http://blogs.msdn.com/oldnewthing/archive/2007/04/11/2079137.aspx
Packit 6c0a39
 */
Packit 6c0a39
#undef _WIN32_WINNT
Packit 6c0a39
#ifdef HAVE_WSPIAPI_H
Packit 6c0a39
#define _WIN32_WINNT 0x0500 /* _WIN32_WINNT_WIN2K */
Packit 6c0a39
#undef NTDDI_VERSION
Packit 6c0a39
#define NTDDI_VERSION 0x05000400 /* NTDDI_WIN2KSP4 */
Packit 6c0a39
#else
Packit 6c0a39
#define _WIN32_WINNT 0x0501 /* _WIN32_WINNT_WINXP */
Packit 6c0a39
#undef NTDDI_VERSION
Packit 6c0a39
#define NTDDI_VERSION 0x05010000 /* NTDDI_WINXP */
Packit 6c0a39
#endif
Packit 6c0a39
Packit 6c0a39
#if _MSC_VER >= 1400
Packit 6c0a39
#include <io.h>
Packit 6c0a39
#undef close
Packit 6c0a39
#define close _close
Packit 6c0a39
#endif /* _MSC_VER */
Packit 6c0a39
#include <winsock2.h>
Packit 6c0a39
#include <ws2tcpip.h>
Packit 6c0a39
Packit 6c0a39
/* <wspiapi.h> is necessary for getaddrinfo before Windows XP, but it isn't
Packit 6c0a39
 * available on some platforms like MinGW. */
Packit 6c0a39
#ifdef HAVE_WSPIAPI_H
Packit 6c0a39
#include <wspiapi.h>
Packit 6c0a39
#endif
Packit 6c0a39
Packit 6c0a39
#ifndef EINPROGRESS
Packit 6c0a39
#define EINPROGRESS WSAEINPROGRESS
Packit 6c0a39
#endif
Packit 6c0a39
Packit 6c0a39
#else /* _WIN32 */
Packit 6c0a39
Packit 6c0a39
#include <netdb.h>
Packit 6c0a39
#include <sys/socket.h>
Packit 6c0a39
#include <sys/select.h>
Packit 6c0a39
#include <netinet/in.h>
Packit 6c0a39
#include <netinet/tcp.h>
Packit 6c0a39
Packit 6c0a39
#endif /* _WIN32 */
Packit 6c0a39
Packit 6c0a39
#include "libssh/priv.h"
Packit 6c0a39
#include "libssh/socket.h"
Packit 6c0a39
#include "libssh/channels.h"
Packit 6c0a39
#include "libssh/session.h"
Packit 6c0a39
#include "libssh/poll.h"
Packit 6c0a39
Packit 6c0a39
#ifndef HAVE_GETADDRINFO
Packit 6c0a39
#error "Your system must have getaddrinfo()"
Packit 6c0a39
#endif
Packit 6c0a39
Packit 6c0a39
#ifdef _WIN32
Packit 6c0a39
#ifndef gai_strerror
Packit 6c0a39
char WSAAPI *gai_strerrorA(int code)
Packit 6c0a39
{
Packit 6c0a39
    static char buf[256];
Packit 6c0a39
Packit 6c0a39
    snprintf(buf, sizeof(buf), "Undetermined error code (%d)", code);
Packit 6c0a39
Packit 6c0a39
    return buf;
Packit 6c0a39
}
Packit 6c0a39
#endif /* gai_strerror */
Packit 6c0a39
#endif /* _WIN32 */
Packit 6c0a39
Packit 6c0a39
static int ssh_connect_socket_close(socket_t s)
Packit 6c0a39
{
Packit 6c0a39
#ifdef _WIN32
Packit 6c0a39
    return closesocket(s);
Packit 6c0a39
#else
Packit 6c0a39
    return close(s);
Packit 6c0a39
#endif
Packit 6c0a39
}
Packit 6c0a39
Packit 6c0a39
static int getai(const char *host, int port, struct addrinfo **ai)
Packit 6c0a39
{
Packit 6c0a39
    const char *service = NULL;
Packit 6c0a39
    struct addrinfo hints;
Packit 6c0a39
    char s_port[10];
Packit 6c0a39
Packit 6c0a39
    ZERO_STRUCT(hints);
Packit 6c0a39
Packit 6c0a39
    hints.ai_protocol = IPPROTO_TCP;
Packit 6c0a39
    hints.ai_family = PF_UNSPEC;
Packit 6c0a39
    hints.ai_socktype = SOCK_STREAM;
Packit 6c0a39
Packit 6c0a39
    if (port == 0) {
Packit 6c0a39
        hints.ai_flags = AI_PASSIVE;
Packit 6c0a39
    } else {
Packit 6c0a39
        snprintf(s_port, sizeof(s_port), "%hu", (unsigned short)port);
Packit 6c0a39
        service = s_port;
Packit 6c0a39
#ifdef AI_NUMERICSERV
Packit 6c0a39
        hints.ai_flags = AI_NUMERICSERV;
Packit 6c0a39
#endif
Packit 6c0a39
    }
Packit 6c0a39
Packit 6c0a39
    if (ssh_is_ipaddr(host)) {
Packit 6c0a39
        /* this is an IP address */
Packit 6c0a39
        SSH_LOG(SSH_LOG_PACKET, "host %s matches an IP address", host);
Packit 6c0a39
        hints.ai_flags |= AI_NUMERICHOST;
Packit 6c0a39
    }
Packit 6c0a39
Packit 6c0a39
    return getaddrinfo(host, service, &hints, ai);
Packit 6c0a39
}
Packit 6c0a39
Packit 6c0a39
static int set_tcp_nodelay(socket_t socket)
Packit 6c0a39
{
Packit 6c0a39
    int opt = 1;
Packit 6c0a39
Packit 6c0a39
    return setsockopt(socket,
Packit 6c0a39
                      IPPROTO_TCP,
Packit 6c0a39
                      TCP_NODELAY,
Packit 6c0a39
                      (void *)&opt,
Packit 6c0a39
                      sizeof(opt));
Packit 6c0a39
}
Packit 6c0a39
Packit 6c0a39
/**
Packit 6c0a39
 * @internal
Packit 6c0a39
 *
Packit 6c0a39
 * @brief Launches a nonblocking connect to an IPv4 or IPv6 host
Packit 6c0a39
 * specified by its IP address or hostname.
Packit 6c0a39
 *
Packit 6c0a39
 * @returns A file descriptor, < 0 on error.
Packit 6c0a39
 * @warning very ugly !!!
Packit 6c0a39
 */
Packit 6c0a39
socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host,
Packit 6c0a39
                                      const char *bind_addr, int port)
Packit 6c0a39
{
Packit 6c0a39
    socket_t s = -1;
Packit 6c0a39
    int rc;
Packit 6c0a39
    struct addrinfo *ai = NULL;
Packit 6c0a39
    struct addrinfo *itr = NULL;
Packit 6c0a39
Packit 6c0a39
    rc = getai(host, port, &ai;;
Packit 6c0a39
    if (rc != 0) {
Packit 6c0a39
        ssh_set_error(session, SSH_FATAL,
Packit 6c0a39
                      "Failed to resolve hostname %s (%s)",
Packit 6c0a39
                      host, gai_strerror(rc));
Packit 6c0a39
Packit 6c0a39
        return -1;
Packit 6c0a39
    }
Packit 6c0a39
Packit 6c0a39
    for (itr = ai; itr != NULL; itr = itr->ai_next) {
Packit 6c0a39
        /* create socket */
Packit 6c0a39
        s = socket(itr->ai_family, itr->ai_socktype, itr->ai_protocol);
Packit 6c0a39
        if (s < 0) {
Packit 6c0a39
            ssh_set_error(session, SSH_FATAL,
Packit 6c0a39
                          "Socket create failed: %s", strerror(errno));
Packit 6c0a39
            continue;
Packit 6c0a39
        }
Packit 6c0a39
Packit 6c0a39
        if (bind_addr) {
Packit 6c0a39
            struct addrinfo *bind_ai;
Packit 6c0a39
            struct addrinfo *bind_itr;
Packit 6c0a39
Packit 6c0a39
            SSH_LOG(SSH_LOG_PACKET, "Resolving %s", bind_addr);
Packit 6c0a39
Packit 6c0a39
            rc = getai(bind_addr, 0, &bind_ai);
Packit 6c0a39
            if (rc != 0) {
Packit 6c0a39
                ssh_set_error(session, SSH_FATAL,
Packit 6c0a39
                              "Failed to resolve bind address %s (%s)",
Packit 6c0a39
                              bind_addr,
Packit 6c0a39
                              gai_strerror(rc));
Packit 6c0a39
                ssh_connect_socket_close(s);
Packit 6c0a39
                s = -1;
Packit 6c0a39
                break;
Packit 6c0a39
            }
Packit 6c0a39
Packit 6c0a39
            for (bind_itr = bind_ai;
Packit 6c0a39
                 bind_itr != NULL;
Packit 6c0a39
                 bind_itr = bind_itr->ai_next)
Packit 6c0a39
            {
Packit 6c0a39
                if (bind(s, bind_itr->ai_addr, bind_itr->ai_addrlen) < 0) {
Packit 6c0a39
                    ssh_set_error(session, SSH_FATAL,
Packit 6c0a39
                                  "Binding local address: %s", strerror(errno));
Packit 6c0a39
                    continue;
Packit 6c0a39
                } else {
Packit 6c0a39
                    break;
Packit 6c0a39
                }
Packit 6c0a39
            }
Packit 6c0a39
            freeaddrinfo(bind_ai);
Packit 6c0a39
Packit 6c0a39
            /* Cannot bind to any local addresses */
Packit 6c0a39
            if (bind_itr == NULL) {
Packit 6c0a39
                ssh_connect_socket_close(s);
Packit 6c0a39
                s = -1;
Packit 6c0a39
                continue;
Packit 6c0a39
            }
Packit 6c0a39
        }
Packit 6c0a39
Packit 6c0a39
        rc = ssh_socket_set_nonblocking(s);
Packit 6c0a39
        if (rc < 0) {
Packit 6c0a39
            ssh_set_error(session, SSH_FATAL,
Packit 6c0a39
                          "Failed to set socket non-blocking for %s:%d",
Packit 6c0a39
                          host, port);
Packit 6c0a39
            ssh_connect_socket_close(s);
Packit 6c0a39
            s = -1;
Packit 6c0a39
            continue;
Packit 6c0a39
        }
Packit 6c0a39
Packit 6c0a39
        if (session->opts.nodelay) {
Packit 6c0a39
            /* For winsock, socket options are only effective before connect */
Packit 6c0a39
            rc = set_tcp_nodelay(s);
Packit 6c0a39
            if (rc < 0) {
Packit 6c0a39
                ssh_set_error(session, SSH_FATAL,
Packit 6c0a39
                              "Failed to set TCP_NODELAY on socket: %s",
Packit 6c0a39
                              strerror(errno));
Packit 6c0a39
                ssh_connect_socket_close(s);
Packit 6c0a39
                s = -1;
Packit 6c0a39
                continue;
Packit 6c0a39
            }
Packit 6c0a39
        }
Packit 6c0a39
Packit 6c0a39
        errno = 0;
Packit 6c0a39
        rc = connect(s, itr->ai_addr, itr->ai_addrlen);
Packit 6c0a39
        if (rc == -1 && (errno != 0) && (errno != EINPROGRESS)) {
Packit 6c0a39
            ssh_set_error(session, SSH_FATAL,
Packit 6c0a39
                          "Failed to connect: %s", strerror(errno));
Packit 6c0a39
            ssh_connect_socket_close(s);
Packit 6c0a39
            s = -1;
Packit 6c0a39
            continue;
Packit 6c0a39
        }
Packit 6c0a39
Packit 6c0a39
        break;
Packit 6c0a39
    }
Packit 6c0a39
Packit 6c0a39
    freeaddrinfo(ai);
Packit 6c0a39
Packit 6c0a39
    return s;
Packit 6c0a39
}
Packit 6c0a39
Packit 6c0a39
/**
Packit 6c0a39
 * @addtogroup libssh_session
Packit 6c0a39
 *
Packit 6c0a39
 * @{
Packit 6c0a39
 */
Packit 6c0a39
Packit 6c0a39
static int ssh_select_cb (socket_t fd, int revents, void *userdata)
Packit 6c0a39
{
Packit 6c0a39
    fd_set *set = (fd_set *)userdata;
Packit 6c0a39
    if (revents & POLLIN) {
Packit 6c0a39
        FD_SET(fd, set);
Packit 6c0a39
    }
Packit 6c0a39
    return 0;
Packit 6c0a39
}
Packit 6c0a39
Packit 6c0a39
/**
Packit 6c0a39
 * @brief A wrapper for the select syscall
Packit 6c0a39
 *
Packit 6c0a39
 * This functions acts more or less like the select(2) syscall.\n
Packit 6c0a39
 * There is no support for writing or exceptions.\n
Packit 6c0a39
 *
Packit 6c0a39
 * @param[in]  channels Arrays of channels pointers terminated by a NULL.
Packit 6c0a39
 *                      It is never rewritten.
Packit 6c0a39
 *
Packit 6c0a39
 * @param[out] outchannels Arrays of same size that "channels", there is no need
Packit 6c0a39
 *                         to initialize it.
Packit 6c0a39
 *
Packit 6c0a39
 * @param[in]  maxfd    Maximum +1 file descriptor from readfds.
Packit 6c0a39
 *
Packit 6c0a39
 * @param[in]  readfds  A fd_set of file descriptors to be select'ed for
Packit 6c0a39
 *                      reading.
Packit 6c0a39
 *
Packit 6c0a39
 * @param[in]  timeout  The timeout in milliseconds.
Packit 6c0a39
 *
Packit 6c0a39
 * @return              SSH_OK on success,
Packit 6c0a39
 *                      SSH_ERROR on error,
Packit 6c0a39
 *                      SSH_EINTR if it was interrupted. In that case,
Packit 6c0a39
 *                      just restart it.
Packit 6c0a39
 *
Packit 6c0a39
 * @warning libssh is not reentrant here. That means that if a signal is caught
Packit 6c0a39
 *          during the processing of this function, you cannot call libssh
Packit 6c0a39
 *          functions on sessions that are busy with ssh_select().
Packit 6c0a39
 *
Packit 6c0a39
 * @see select(2)
Packit 6c0a39
 */
Packit 6c0a39
int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd,
Packit 6c0a39
               fd_set *readfds, struct timeval *timeout)
Packit 6c0a39
{
Packit 6c0a39
    fd_set origfds;
Packit 6c0a39
    socket_t fd;
Packit 6c0a39
    size_t i, j;
Packit 6c0a39
    int rc;
Packit 6c0a39
    int base_tm, tm;
Packit 6c0a39
    struct ssh_timestamp ts;
Packit 6c0a39
    ssh_event event = ssh_event_new();
Packit 6c0a39
    int firstround = 1;
Packit 6c0a39
Packit 6c0a39
    base_tm = tm = (timeout->tv_sec * 1000) + (timeout->tv_usec / 1000);
Packit 6c0a39
    for (i = 0 ; channels[i] != NULL; ++i) {
Packit 6c0a39
        ssh_event_add_session(event, channels[i]->session);
Packit 6c0a39
    }
Packit 6c0a39
Packit 6c0a39
    ZERO_STRUCT(origfds);
Packit 6c0a39
    FD_ZERO(&origfds);
Packit 6c0a39
    for (fd = 0; fd < maxfd ; fd++) {
Packit 6c0a39
        if (FD_ISSET(fd, readfds)) {
Packit 6c0a39
            ssh_event_add_fd(event, fd, POLLIN, ssh_select_cb, readfds);
Packit 6c0a39
            FD_SET(fd, &origfds);
Packit 6c0a39
        }
Packit 6c0a39
    }
Packit 6c0a39
    outchannels[0] = NULL;
Packit 6c0a39
    FD_ZERO(readfds);
Packit 6c0a39
    ssh_timestamp_init(&ts);
Packit 6c0a39
    do {
Packit 6c0a39
        /* Poll every channel */
Packit 6c0a39
        j = 0;
Packit 6c0a39
        for (i = 0; channels[i]; i++) {
Packit 6c0a39
            rc = ssh_channel_poll(channels[i], 0);
Packit 6c0a39
            if (rc != 0) {
Packit 6c0a39
                outchannels[j] = channels[i];
Packit 6c0a39
                j++;
Packit 6c0a39
            } else {
Packit 6c0a39
                rc = ssh_channel_poll(channels[i], 1);
Packit 6c0a39
                if (rc != 0) {
Packit 6c0a39
                    outchannels[j] = channels[i];
Packit 6c0a39
                    j++;
Packit 6c0a39
                }
Packit 6c0a39
            }
Packit 6c0a39
        }
Packit 6c0a39
Packit 6c0a39
        outchannels[j] = NULL;
Packit 6c0a39
        if (j != 0) {
Packit 6c0a39
            break;
Packit 6c0a39
        }
Packit 6c0a39
Packit 6c0a39
        /* watch if a user socket was triggered */
Packit 6c0a39
        for (fd = 0; fd < maxfd; fd++) {
Packit 6c0a39
            if (FD_ISSET(fd, readfds)) {
Packit 6c0a39
                goto out;
Packit 6c0a39
            }
Packit 6c0a39
        }
Packit 6c0a39
Packit 6c0a39
        /* If the timeout is elapsed, we should go out */
Packit 6c0a39
        if (!firstround && ssh_timeout_elapsed(&ts, base_tm)) {
Packit 6c0a39
            goto out;
Packit 6c0a39
        }
Packit 6c0a39
Packit 6c0a39
        /* since there's nothing, let's fire the polling */
Packit 6c0a39
        rc = ssh_event_dopoll(event,tm);
Packit 6c0a39
        if (rc == SSH_ERROR) {
Packit 6c0a39
            goto out;
Packit 6c0a39
        }
Packit 6c0a39
Packit 6c0a39
        tm = ssh_timeout_update(&ts, base_tm);
Packit 6c0a39
        firstround = 0;
Packit 6c0a39
    } while (1);
Packit 6c0a39
out:
Packit 6c0a39
    for (fd = 0; fd < maxfd; fd++) {
Packit 6c0a39
        if (FD_ISSET(fd, &origfds)) {
Packit 6c0a39
            ssh_event_remove_fd(event, fd);
Packit 6c0a39
        }
Packit 6c0a39
    }
Packit 6c0a39
    ssh_event_free(event);
Packit 6c0a39
    return SSH_OK;
Packit 6c0a39
}
Packit 6c0a39
Packit 6c0a39
/** @} */