Coverage Report

Created: 2026-03-20 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libssh/src/connect.c
Line
Count
Source
1
/*
2
 * connect.c - handles connections to ssh servers
3
 *
4
 * This file is part of the SSH Library
5
 *
6
 * Copyright (c) 2003-2013 by Aris Adamantiadis
7
 *
8
 * The SSH Library is free software; you can redistribute it and/or modify
9
 * it under the terms of the GNU Lesser General Public License as published by
10
 * the Free Software Foundation; either version 2.1 of the License, or (at your
11
 * option) any later version.
12
 *
13
 * The SSH Library is distributed in the hope that it will be useful, but
14
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
16
 * License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public License
19
 * along with the SSH Library; see the file COPYING.  If not, write to
20
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
21
 * MA 02111-1307, USA.
22
 */
23
24
#include "config.h"
25
26
#include <errno.h>
27
#include <fcntl.h>
28
#include <stdbool.h>
29
#include <stdio.h>
30
#include <stdlib.h>
31
#include <string.h>
32
#ifdef HAVE_SYS_TIME_H
33
#include <sys/time.h>
34
#endif /* HAVE_SYS_TIME_H */
35
36
#include "libssh/libssh.h"
37
#include "libssh/misc.h"
38
39
#ifdef _WIN32
40
/*
41
 * Only use Windows API functions available on Windows 2000 SP4 or later.
42
 * The available constants are in <sdkddkver.h>.
43
 *  http://msdn.microsoft.com/en-us/library/aa383745.aspx
44
 *  http://blogs.msdn.com/oldnewthing/archive/2007/04/11/2079137.aspx
45
 */
46
#undef _WIN32_WINNT
47
#ifdef HAVE_WSPIAPI_H
48
#define _WIN32_WINNT 0x0500 /* _WIN32_WINNT_WIN2K */
49
#undef NTDDI_VERSION
50
#define NTDDI_VERSION 0x05000400 /* NTDDI_WIN2KSP4 */
51
#else
52
#define _WIN32_WINNT 0x0501 /* _WIN32_WINNT_WINXP */
53
#undef NTDDI_VERSION
54
#define NTDDI_VERSION 0x05010000 /* NTDDI_WINXP */
55
#endif
56
57
#include <winsock2.h>
58
#include <ws2tcpip.h>
59
60
/* <wspiapi.h> is necessary for getaddrinfo before Windows XP, but it isn't
61
 * available on some platforms like MinGW. */
62
#ifdef HAVE_WSPIAPI_H
63
#include <wspiapi.h>
64
#endif
65
66
#ifndef EINPROGRESS
67
#define EINPROGRESS WSAEINPROGRESS
68
#endif
69
70
#else /* _WIN32 */
71
72
#include <netdb.h>
73
#include <sys/socket.h>
74
#include <sys/select.h>
75
#include <netinet/in.h>
76
#include <netinet/tcp.h>
77
78
#endif /* _WIN32 */
79
80
#include "libssh/priv.h"
81
#include "libssh/socket.h"
82
#include "libssh/channels.h"
83
#include "libssh/session.h"
84
#include "libssh/poll.h"
85
86
#ifndef HAVE_GETADDRINFO
87
#error "Your system must have getaddrinfo()"
88
#endif
89
90
#ifdef _WIN32
91
#ifndef gai_strerror
92
char WSAAPI *gai_strerrorA(int code)
93
{
94
    static char buf[256];
95
96
    snprintf(buf, sizeof(buf), "Undetermined error code (%d)", code);
97
98
    return buf;
99
}
100
#endif /* gai_strerror */
101
#endif /* _WIN32 */
102
103
static int ssh_connect_socket_close(socket_t s)
104
0
{
105
#ifdef _WIN32
106
    return closesocket(s);
107
#else
108
0
    return close(s);
109
0
#endif
110
0
}
111
112
static int
113
getai(const char *host, int port, int ai_family, struct addrinfo **ai)
114
0
{
115
0
    const char *service = NULL;
116
0
    struct addrinfo hints;
117
0
    char s_port[10];
118
119
0
    ZERO_STRUCT(hints);
120
121
0
    hints.ai_protocol = IPPROTO_TCP;
122
0
    hints.ai_family = ai_family;
123
0
    hints.ai_socktype = SOCK_STREAM;
124
125
0
    if (port == 0) {
126
0
        hints.ai_flags = AI_PASSIVE;
127
0
    } else {
128
0
        snprintf(s_port, sizeof(s_port), "%hu", (unsigned short)port);
129
0
        service = s_port;
130
0
#ifdef AI_NUMERICSERV
131
0
        hints.ai_flags = AI_NUMERICSERV;
132
0
#endif
133
0
    }
134
135
0
    if (ssh_is_ipaddr(host) == 1) {
136
        /* this is an IP address */
137
0
        SSH_LOG(SSH_LOG_PACKET, "host %s matches an IP address", host);
138
0
        hints.ai_flags |= AI_NUMERICHOST;
139
0
    }
140
141
0
    return getaddrinfo(host, service, &hints, ai);
142
0
}
143
144
static int set_tcp_nodelay(socket_t socket)
145
0
{
146
0
    int opt = 1;
147
148
0
    return setsockopt(socket,
149
0
                      IPPROTO_TCP,
150
0
                      TCP_NODELAY,
151
0
                      (void *)&opt,
152
0
                      sizeof(opt));
153
0
}
154
155
/**
156
 * @internal
157
 *
158
 * @brief Launches a nonblocking connect to an IPv4 or IPv6 host
159
 * specified by its IP address or hostname.
160
 *
161
 * @returns A file descriptor, < 0 on error.
162
 * @warning very ugly !!!
163
 */
164
socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host,
165
                                      const char *bind_addr, int port)
166
0
{
167
0
    socket_t s = -1, first = -1;
168
0
    int rc;
169
0
    int ai_family;
170
0
    static const char *ai_family_str = NULL;
171
0
    struct addrinfo *ai = NULL;
172
0
    struct addrinfo *itr = NULL;
173
0
    char addrname[NI_MAXHOST], portname[NI_MAXSERV];
174
175
0
    switch (session->opts.address_family) {
176
0
    case SSH_ADDRESS_FAMILY_INET:
177
0
        ai_family = PF_INET;
178
0
        ai_family_str = "inet";
179
0
        break;
180
0
    case SSH_ADDRESS_FAMILY_INET6:
181
0
        ai_family = PF_INET6;
182
0
        ai_family_str = "inet6";
183
0
        break;
184
0
    case SSH_ADDRESS_FAMILY_ANY:
185
0
    default:
186
0
        ai_family = PF_UNSPEC;
187
0
        ai_family_str = "any";
188
0
    }
189
0
    SSH_LOG(SSH_LOG_PACKET,
190
0
            "Resolve target hostname %s port %d (%s)",
191
0
            host,
192
0
            port,
193
0
            ai_family_str);
194
0
    rc = getai(host, port, ai_family, &ai);
195
0
    if (rc != 0) {
196
0
        ssh_set_error(session,
197
0
                      SSH_FATAL,
198
0
                      "Failed to resolve hostname %s (%s): %s",
199
0
                      host,
200
0
                      ai_family_str,
201
0
                      gai_strerror(rc));
202
203
0
        return -1;
204
0
    }
205
206
0
    for (itr = ai; itr != NULL; itr = itr->ai_next) {
207
0
        char err_msg[SSH_ERRNO_MSG_MAX] = {0};
208
        /* create socket */
209
0
        s = socket(itr->ai_family, itr->ai_socktype, itr->ai_protocol);
210
0
        if (s < 0) {
211
0
            ssh_set_error(session, SSH_FATAL,
212
0
                          "Socket create failed: %s",
213
0
                          ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
214
0
            continue;
215
0
        }
216
217
0
        if (bind_addr) {
218
0
            struct addrinfo *bind_ai = NULL;
219
0
            struct addrinfo *bind_itr = NULL;
220
221
0
            SSH_LOG(SSH_LOG_PACKET,
222
0
                    "Resolving bind address %s (%s)",
223
0
                    bind_addr,
224
0
                    ai_family_str);
225
226
0
            rc = getai(bind_addr, 0, ai_family, &bind_ai);
227
0
            if (rc != 0) {
228
0
                ssh_set_error(session,
229
0
                              SSH_FATAL,
230
0
                              "Failed to resolve bind address %s (%s): %s",
231
0
                              bind_addr,
232
0
                              ai_family_str,
233
0
                              gai_strerror(rc));
234
0
                ssh_connect_socket_close(s);
235
0
                s = -1;
236
0
                break;
237
0
            }
238
239
0
            for (bind_itr = bind_ai;
240
0
                 bind_itr != NULL;
241
0
                 bind_itr = bind_itr->ai_next)
242
0
            {
243
0
                rc = bind(s, bind_itr->ai_addr, bind_itr->ai_addrlen);
244
0
                if (rc < 0) {
245
0
                    ssh_set_error(session, SSH_FATAL,
246
0
                                  "Binding local address: %s",
247
0
                                  ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
248
0
                    continue;
249
0
                } else {
250
0
                    break;
251
0
                }
252
0
            }
253
0
            freeaddrinfo(bind_ai);
254
255
            /* Cannot bind to any local addresses */
256
0
            if (bind_itr == NULL) {
257
0
                ssh_connect_socket_close(s);
258
0
                s = -1;
259
0
                continue;
260
0
            }
261
0
        }
262
263
0
        rc = ssh_socket_set_nonblocking(s);
264
0
        if (rc < 0) {
265
0
            ssh_set_error(session, SSH_FATAL,
266
0
                          "Failed to set socket non-blocking for %s:%d",
267
0
                          host, port);
268
0
            ssh_connect_socket_close(s);
269
0
            s = -1;
270
0
            continue;
271
0
        }
272
273
0
        if (session->opts.nodelay) {
274
            /* For winsock, socket options are only effective before connect */
275
0
            rc = set_tcp_nodelay(s);
276
0
            if (rc < 0) {
277
0
                ssh_set_error(session, SSH_FATAL,
278
0
                              "Failed to set TCP_NODELAY on socket: %s",
279
0
                              ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
280
0
                ssh_connect_socket_close(s);
281
0
                s = -1;
282
0
                continue;
283
0
            }
284
0
        }
285
286
0
        rc = getnameinfo(itr->ai_addr,
287
0
                         itr->ai_addrlen,
288
0
                         addrname,
289
0
                         sizeof(addrname),
290
0
                         portname,
291
0
                         sizeof(portname),
292
0
                         NI_NUMERICHOST | NI_NUMERICSERV);
293
0
        if (rc != 0) {
294
0
            ssh_set_error(session, SSH_FATAL,
295
0
                          "getnameinfo failed: %s",
296
0
                          ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
297
0
            ssh_connect_socket_close(s);
298
0
            s = -1;
299
0
            continue;
300
0
        }
301
302
0
        errno = 0;
303
0
        SSH_LOG(SSH_LOG_PACKET,
304
0
                "Connecting to host %s [%s] port %s",
305
0
                host,
306
0
                addrname,
307
0
                portname);
308
0
        rc = connect(s, itr->ai_addr, itr->ai_addrlen);
309
0
        if (rc == -1) {
310
0
            if ((errno != 0) && (errno != EINPROGRESS)) {
311
0
                ssh_set_error(session, SSH_FATAL,
312
0
                              "Failed to connect: %s",
313
0
                              ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
314
0
                ssh_connect_socket_close(s);
315
0
                s = -1;
316
0
            } else {
317
0
                if (first == -1) {
318
0
                    SSH_LOG(SSH_LOG_PACKET, "EINPROGRESS => Store for later.");
319
0
                    first = s;
320
0
                } else { /* errno == EINPROGRESS */
321
                    /* save only the first "working" socket */
322
0
                    ssh_connect_socket_close(s);
323
0
                    s = -1;
324
0
                }
325
0
            }
326
0
            continue;
327
0
        }
328
329
0
        break;
330
0
    }
331
332
0
    freeaddrinfo(ai);
333
334
    /* first let's go through all the addresses looking for immediate
335
     * connection, otherwise return the first address without error or error */
336
0
    if (s == -1) {
337
0
        s = first;
338
0
    } else if (s != first && first != -1) {
339
        /* Clean up the saved socket if any */
340
0
        ssh_connect_socket_close(first);
341
0
    }
342
343
0
    return s;
344
0
}
345
346
/**
347
 * @addtogroup libssh_session
348
 *
349
 * @{
350
 */
351
352
static int ssh_select_cb (socket_t fd, int revents, void *userdata)
353
0
{
354
0
    fd_set *set = (fd_set *)userdata;
355
0
    if (revents & POLLIN) {
356
0
        FD_SET(fd, set);
357
0
    }
358
0
    return 0;
359
0
}
360
361
/**
362
 * @brief A wrapper for the select syscall
363
 *
364
 * This function acts more or less like the select(2) syscall.\n
365
 * There is no support for writing or exceptions.\n
366
 *
367
 * @param[in]  channels Arrays of channels pointers terminated by a NULL.
368
 *                      It is never rewritten.
369
 *
370
 * @param[out] outchannels Arrays of the same size as "channels", there is no
371
 *                         need to initialize it.
372
 *
373
 * @param[in]  maxfd    Maximum +1 file descriptor from readfds.
374
 *
375
 * @param[in]  readfds  A fd_set of file descriptors to be select'ed for
376
 *                      reading.
377
 *
378
 * @param[in]  timeout  The timeout in milliseconds.
379
 *
380
 * @return              SSH_OK on success,
381
 *                      SSH_ERROR on error,
382
 *                      SSH_EINTR if it was interrupted. In that case,
383
 *                      just restart it.
384
 *
385
 * @warning libssh is not reentrant here. That means that if a signal is caught
386
 *          during the processing of this function, you cannot call libssh
387
 *          functions on sessions that are busy with ssh_select().
388
 *
389
 * @see select(2)
390
 */
391
int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd,
392
               fd_set *readfds, struct timeval *timeout)
393
0
{
394
0
    fd_set origfds;
395
0
    socket_t fd;
396
0
    size_t i, j;
397
0
    int rc;
398
0
    int base_tm, tm;
399
0
    struct ssh_timestamp ts;
400
0
    ssh_event event = ssh_event_new();
401
0
    int firstround = 1;
402
403
0
    base_tm = tm = (timeout->tv_sec * 1000) + (timeout->tv_usec / 1000);
404
0
    for (i = 0 ; channels[i] != NULL; ++i) {
405
0
        ssh_event_add_session(event, channels[i]->session);
406
0
    }
407
408
0
    ZERO_STRUCT(origfds);
409
0
    FD_ZERO(&origfds);
410
0
    for (fd = 0; fd < maxfd ; fd++) {
411
0
        if (FD_ISSET(fd, readfds)) {
412
0
            ssh_event_add_fd(event, fd, POLLIN, ssh_select_cb, readfds);
413
0
            FD_SET(fd, &origfds);
414
0
        }
415
0
    }
416
0
    outchannels[0] = NULL;
417
0
    FD_ZERO(readfds);
418
0
    ssh_timestamp_init(&ts);
419
0
    do {
420
        /* Poll every channel */
421
0
        j = 0;
422
0
        for (i = 0; channels[i]; i++) {
423
0
            rc = ssh_channel_poll(channels[i], 0);
424
0
            if (rc != 0) {
425
0
                outchannels[j] = channels[i];
426
0
                j++;
427
0
            } else {
428
0
                rc = ssh_channel_poll(channels[i], 1);
429
0
                if (rc != 0) {
430
0
                    outchannels[j] = channels[i];
431
0
                    j++;
432
0
                }
433
0
            }
434
0
        }
435
436
0
        outchannels[j] = NULL;
437
0
        if (j != 0) {
438
0
            break;
439
0
        }
440
441
        /* watch if a user socket was triggered */
442
0
        for (fd = 0; fd < maxfd; fd++) {
443
0
            if (FD_ISSET(fd, readfds)) {
444
0
                goto out;
445
0
            }
446
0
        }
447
448
        /* If the timeout is elapsed, we should go out */
449
0
        if (!firstround && ssh_timeout_elapsed(&ts, base_tm)) {
450
0
            goto out;
451
0
        }
452
453
        /* since there's nothing, let's fire the polling */
454
0
        rc = ssh_event_dopoll(event,tm);
455
0
        if (rc == SSH_ERROR) {
456
0
            goto out;
457
0
        }
458
459
0
        tm = ssh_timeout_update(&ts, base_tm);
460
0
        firstround = 0;
461
0
    } while (1);
462
0
out:
463
0
    for (fd = 0; fd < maxfd; fd++) {
464
0
        if (FD_ISSET(fd, &origfds)) {
465
0
            ssh_event_remove_fd(event, fd);
466
0
        }
467
0
    }
468
0
    ssh_event_free(event);
469
0
    return SSH_OK;
470
0
}
471
472
/** @} */