/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 | | /** @} */ |