Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * socket.c - socket functions for the library |
3 | | * |
4 | | * This file is part of the SSH Library |
5 | | * |
6 | | * Copyright (c) 2008-2010 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 <stdio.h> |
28 | | #ifdef _WIN32 |
29 | | #include <winsock2.h> |
30 | | #include <ws2tcpip.h> |
31 | | #ifndef UNIX_PATH_MAX |
32 | | /* Inlining the key portions of afunix.h in Windows 10 SDK; |
33 | | * that header isn't available in the mingw environment. */ |
34 | | #define UNIX_PATH_MAX 108 |
35 | | struct sockaddr_un { |
36 | | ADDRESS_FAMILY sun_family; |
37 | | char sun_path[UNIX_PATH_MAX]; |
38 | | }; |
39 | | #endif |
40 | | #else /* _WIN32 */ |
41 | | #include <fcntl.h> |
42 | | #include <sys/types.h> |
43 | | #include <sys/wait.h> |
44 | | #include <sys/socket.h> |
45 | | #include <sys/un.h> |
46 | | #include <signal.h> |
47 | | #ifdef HAVE_PTHREAD |
48 | | #include <pthread.h> |
49 | | #endif |
50 | | #endif /* _WIN32 */ |
51 | | |
52 | | #include "libssh/priv.h" |
53 | | #include "libssh/callbacks.h" |
54 | | #include "libssh/socket.h" |
55 | | #include "libssh/buffer.h" |
56 | | #include "libssh/poll.h" |
57 | | #include "libssh/session.h" |
58 | | |
59 | | /** |
60 | | * @defgroup libssh_socket The SSH socket functions. |
61 | | * @ingroup libssh |
62 | | * |
63 | | * Functions for handling sockets. |
64 | | * |
65 | | * @{ |
66 | | */ |
67 | | |
68 | | enum ssh_socket_states_e { |
69 | | SSH_SOCKET_NONE, |
70 | | SSH_SOCKET_CONNECTING, |
71 | | SSH_SOCKET_CONNECTED, |
72 | | SSH_SOCKET_EOF, |
73 | | SSH_SOCKET_ERROR, |
74 | | SSH_SOCKET_CLOSED |
75 | | }; |
76 | | |
77 | | struct ssh_socket_struct { |
78 | | socket_t fd; |
79 | | int fd_is_socket; |
80 | | int last_errno; |
81 | | int read_wontblock; /* reading now on socket will |
82 | | not block */ |
83 | | int write_wontblock; |
84 | | int data_except; |
85 | | enum ssh_socket_states_e state; |
86 | | ssh_buffer out_buffer; |
87 | | ssh_buffer in_buffer; |
88 | | ssh_session session; |
89 | | ssh_socket_callbacks callbacks; |
90 | | ssh_poll_handle poll_handle; |
91 | | #ifndef _WIN32 |
92 | | pid_t proxy_pid; |
93 | | #endif |
94 | | }; |
95 | | |
96 | | #ifdef HAVE_PTHREAD |
97 | | struct jump_thread_data_struct { |
98 | | ssh_session session; |
99 | | socket_t fd; |
100 | | }; |
101 | | |
102 | | int proxy_disconnect = 0; |
103 | | #endif /* HAVE_PTHREAD */ |
104 | | |
105 | | static int sockets_initialized = 0; |
106 | | |
107 | | static ssize_t ssh_socket_unbuffered_read(ssh_socket s, |
108 | | void *buffer, |
109 | | uint32_t len); |
110 | | static ssize_t ssh_socket_unbuffered_write(ssh_socket s, |
111 | | const void *buffer, |
112 | | uint32_t len); |
113 | | |
114 | | /** |
115 | | * \internal |
116 | | * \brief inits the socket system (windows specific) |
117 | | */ |
118 | | int ssh_socket_init(void) |
119 | 13 | { |
120 | 13 | if (sockets_initialized == 0) { |
121 | | #ifdef _WIN32 |
122 | | struct WSAData wsaData; |
123 | | |
124 | | /* Initiates use of the Winsock DLL by a process. */ |
125 | | if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) { |
126 | | return -1; |
127 | | } |
128 | | #endif |
129 | 13 | ssh_poll_init(); |
130 | | |
131 | 13 | sockets_initialized = 1; |
132 | 13 | } |
133 | | |
134 | 13 | return 0; |
135 | 13 | } |
136 | | |
137 | | /** |
138 | | * @brief Cleanup the socket system. |
139 | | */ |
140 | | void ssh_socket_cleanup(void) |
141 | 0 | { |
142 | 0 | if (sockets_initialized == 1) { |
143 | 0 | ssh_poll_cleanup(); |
144 | | #ifdef _WIN32 |
145 | | WSACleanup(); |
146 | | #endif |
147 | 0 | sockets_initialized = 0; |
148 | 0 | } |
149 | 0 | } |
150 | | |
151 | | |
152 | | /** |
153 | | * \internal |
154 | | * \brief creates a new Socket object |
155 | | */ |
156 | | ssh_socket ssh_socket_new(ssh_session session) |
157 | 26.9k | { |
158 | 26.9k | ssh_socket s; |
159 | | |
160 | 26.9k | s = calloc(1, sizeof(struct ssh_socket_struct)); |
161 | 26.9k | if (s == NULL) { |
162 | 0 | ssh_set_error_oom(session); |
163 | 0 | return NULL; |
164 | 0 | } |
165 | 26.9k | s->fd = SSH_INVALID_SOCKET; |
166 | 26.9k | s->last_errno = -1; |
167 | 26.9k | s->fd_is_socket = 1; |
168 | 26.9k | s->session = session; |
169 | 26.9k | s->in_buffer = ssh_buffer_new(); |
170 | 26.9k | if (s->in_buffer == NULL) { |
171 | 0 | ssh_set_error_oom(session); |
172 | 0 | SAFE_FREE(s); |
173 | 0 | return NULL; |
174 | 0 | } |
175 | 26.9k | s->out_buffer=ssh_buffer_new(); |
176 | 26.9k | if (s->out_buffer == NULL) { |
177 | 0 | ssh_set_error_oom(session); |
178 | 0 | SSH_BUFFER_FREE(s->in_buffer); |
179 | 0 | SAFE_FREE(s); |
180 | 0 | return NULL; |
181 | 0 | } |
182 | 26.9k | s->read_wontblock = 0; |
183 | 26.9k | s->write_wontblock = 0; |
184 | 26.9k | s->data_except = 0; |
185 | 26.9k | s->poll_handle = NULL; |
186 | 26.9k | s->state=SSH_SOCKET_NONE; |
187 | 26.9k | return s; |
188 | 26.9k | } |
189 | | |
190 | | /** |
191 | | * @internal |
192 | | * @brief Reset the state of a socket so it looks brand-new |
193 | | * @param[in] s socket to rest |
194 | | */ |
195 | | void ssh_socket_reset(ssh_socket s) |
196 | 10.1k | { |
197 | 10.1k | s->fd = SSH_INVALID_SOCKET; |
198 | 10.1k | s->last_errno = -1; |
199 | 10.1k | s->fd_is_socket = 1; |
200 | 10.1k | ssh_buffer_reinit(s->in_buffer); |
201 | 10.1k | ssh_buffer_reinit(s->out_buffer); |
202 | 10.1k | s->read_wontblock = 0; |
203 | 10.1k | s->write_wontblock = 0; |
204 | 10.1k | s->data_except = 0; |
205 | 10.1k | s->poll_handle = NULL; |
206 | 10.1k | s->state=SSH_SOCKET_NONE; |
207 | 10.1k | #ifndef _WIN32 |
208 | 10.1k | s->proxy_pid = 0; |
209 | 10.1k | #endif |
210 | 10.1k | } |
211 | | |
212 | | /** |
213 | | * @internal |
214 | | * @brief the socket callbacks, i.e. callbacks to be called |
215 | | * upon a socket event. |
216 | | * @param s socket to set callbacks on. |
217 | | * @param callbacks a ssh_socket_callback object reference. |
218 | | */ |
219 | | |
220 | | void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks) |
221 | 20.0k | { |
222 | 20.0k | s->callbacks = callbacks; |
223 | 20.0k | } |
224 | | |
225 | | void ssh_socket_set_connected(ssh_socket s, struct ssh_poll_handle_struct *p) |
226 | 10.1k | { |
227 | 10.1k | s->state = SSH_SOCKET_CONNECTED; |
228 | | /* POLLOUT is the event to wait for in a nonblocking connect */ |
229 | 10.1k | if (p != NULL) { |
230 | 10.1k | ssh_poll_set_events(p, POLLIN | POLLOUT); |
231 | 10.1k | } |
232 | 10.1k | } |
233 | | |
234 | | /** |
235 | | * @brief SSH poll callback. This callback will be used when an event |
236 | | * caught on the socket. |
237 | | * |
238 | | * @param p Poll object this callback belongs to. |
239 | | * @param fd The raw socket. |
240 | | * @param revents The current poll events on the socket. |
241 | | * @param v_s Userdata to be passed to the callback function, |
242 | | * in this case the socket object. |
243 | | * |
244 | | * @return 0 on success, < 0 when the poll object has been removed |
245 | | * from its poll context. |
246 | | */ |
247 | | int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, |
248 | | socket_t fd, |
249 | | int revents, |
250 | | void *v_s) |
251 | 25.6k | { |
252 | 25.6k | ssh_socket s = (ssh_socket)v_s; |
253 | 25.6k | void *buffer = NULL; |
254 | 25.6k | ssize_t nread = 0; |
255 | 25.6k | int rc; |
256 | 25.6k | int err = 0; |
257 | 25.6k | socklen_t errlen = sizeof(err); |
258 | | |
259 | | /* Do not do anything if this socket was already closed */ |
260 | 25.6k | if (!ssh_socket_is_open(s)) { |
261 | 0 | return -1; |
262 | 0 | } |
263 | 25.6k | SSH_LOG(SSH_LOG_TRACE, |
264 | 25.6k | "Poll callback on socket %d (%s%s%s), out buffer %" PRIu32, fd, |
265 | 25.6k | (revents & POLLIN) ? "POLLIN ":"", |
266 | 25.6k | (revents & POLLOUT) ? "POLLOUT ":"", |
267 | 25.6k | (revents & POLLERR) ? "POLLERR":"", |
268 | 25.6k | ssh_buffer_get_len(s->out_buffer)); |
269 | 25.6k | if ((revents & POLLERR) || (revents & POLLHUP)) { |
270 | | /* Check if we are in a connecting state */ |
271 | 0 | if (s->state == SSH_SOCKET_CONNECTING) { |
272 | 0 | s->state = SSH_SOCKET_ERROR; |
273 | 0 | rc = getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen); |
274 | 0 | if (rc < 0) { |
275 | 0 | err = errno; |
276 | 0 | } |
277 | 0 | ssh_socket_close(s); |
278 | | /* Overwrite ssh_socket_close() error with the real socket error */ |
279 | 0 | s->last_errno = err; |
280 | 0 | errno = err; |
281 | |
|
282 | 0 | if (s->callbacks != NULL && s->callbacks->connected != NULL) { |
283 | 0 | s->callbacks->connected(SSH_SOCKET_CONNECTED_ERROR, |
284 | 0 | err, |
285 | 0 | s->callbacks->userdata); |
286 | 0 | } |
287 | |
|
288 | 0 | return -1; |
289 | 0 | } |
290 | | /* Then we are in a more standard kind of error */ |
291 | | /* force a read to get an explanation */ |
292 | 0 | revents |= POLLIN; |
293 | 0 | } |
294 | 25.6k | if ((revents & POLLIN) && s->state == SSH_SOCKET_CONNECTED) { |
295 | 22.0k | s->read_wontblock = 1; |
296 | 22.0k | buffer = ssh_buffer_allocate(s->in_buffer, MAX_BUF_SIZE); |
297 | 22.0k | if (buffer) { |
298 | 22.0k | nread = ssh_socket_unbuffered_read(s, buffer, MAX_BUF_SIZE); |
299 | 22.0k | } |
300 | 22.0k | if (nread < 0) { |
301 | 0 | ssh_buffer_pass_bytes_end(s->in_buffer, MAX_BUF_SIZE); |
302 | 0 | if (p != NULL) { |
303 | 0 | ssh_poll_remove_events(p, POLLIN); |
304 | 0 | } |
305 | |
|
306 | 0 | if (s->callbacks != NULL && s->callbacks->exception != NULL) { |
307 | 0 | s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, |
308 | 0 | s->last_errno, |
309 | 0 | s->callbacks->userdata); |
310 | 0 | } |
311 | 0 | return -2; |
312 | 0 | } |
313 | | |
314 | | /* Rollback the unused space */ |
315 | 22.0k | ssh_buffer_pass_bytes_end(s->in_buffer, |
316 | 22.0k | (uint32_t)(MAX_BUF_SIZE - nread)); |
317 | | |
318 | 22.0k | if (nread == 0) { |
319 | 6.22k | if (p != NULL) { |
320 | 6.22k | ssh_poll_remove_events(p, POLLIN); |
321 | 6.22k | } |
322 | 6.22k | if (s->callbacks != NULL && s->callbacks->exception != NULL) { |
323 | 6.22k | s->callbacks->exception(SSH_SOCKET_EXCEPTION_EOF, |
324 | 6.22k | 0, |
325 | 6.22k | s->callbacks->userdata); |
326 | 6.22k | } |
327 | 6.22k | return -2; |
328 | 6.22k | } |
329 | | |
330 | 15.8k | if (s->session->socket_counter != NULL) { |
331 | 0 | s->session->socket_counter->in_bytes += nread; |
332 | 0 | } |
333 | | |
334 | | /* Call the callback */ |
335 | 15.8k | if (s->callbacks != NULL && s->callbacks->data != NULL) { |
336 | 15.8k | size_t processed; |
337 | 773k | do { |
338 | 773k | processed = s->callbacks->data(ssh_buffer_get(s->in_buffer), |
339 | 773k | ssh_buffer_get_len(s->in_buffer), |
340 | 773k | s->callbacks->userdata); |
341 | 773k | ssh_buffer_pass_bytes(s->in_buffer, (uint32_t)processed); |
342 | 773k | } while ((processed > 0) && (s->state == SSH_SOCKET_CONNECTED)); |
343 | | |
344 | | /* p may have been freed, so don't use it |
345 | | * anymore in this function */ |
346 | 15.8k | p = NULL; |
347 | 15.8k | } |
348 | 15.8k | } |
349 | | #ifdef _WIN32 |
350 | | if (revents & POLLOUT || revents & POLLWRNORM) { |
351 | | #else |
352 | 19.4k | if (revents & POLLOUT) { |
353 | 19.3k | #endif |
354 | 19.3k | uint32_t len; |
355 | | |
356 | | /* First, POLLOUT is a sign we may be connected */ |
357 | 19.3k | if (s->state == SSH_SOCKET_CONNECTING) { |
358 | 3.57k | SSH_LOG(SSH_LOG_PACKET, "Received POLLOUT in connecting state"); |
359 | 3.57k | ssh_socket_set_connected(s, p); |
360 | | |
361 | 3.57k | rc = ssh_socket_set_blocking(ssh_socket_get_fd(s)); |
362 | 3.57k | if (rc < 0) { |
363 | 0 | return -1; |
364 | 0 | } |
365 | | |
366 | 3.57k | if (s->callbacks != NULL && s->callbacks->connected != NULL) { |
367 | 3.57k | s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, |
368 | 3.57k | 0, |
369 | 3.57k | s->callbacks->userdata); |
370 | 3.57k | } |
371 | | |
372 | 3.57k | return 0; |
373 | 3.57k | } |
374 | | |
375 | | /* So, we can write data */ |
376 | 15.8k | s->write_wontblock = 1; |
377 | 15.8k | if (p != NULL) { |
378 | 0 | ssh_poll_remove_events(p, POLLOUT); |
379 | 0 | } |
380 | | |
381 | | /* If buffered data is pending, write it */ |
382 | 15.8k | len = ssh_buffer_get_len(s->out_buffer); |
383 | 15.8k | if (len > 0) { |
384 | 10.4k | ssh_socket_nonblocking_flush(s); |
385 | 10.4k | } else if (s->callbacks != NULL && s->callbacks->controlflow != NULL) { |
386 | | /* Otherwise advertise the upper level that write can be done */ |
387 | 4.68k | SSH_LOG(SSH_LOG_TRACE, "sending control flow event"); |
388 | 4.68k | s->callbacks->controlflow(SSH_SOCKET_FLOW_WRITEWONTBLOCK, |
389 | 4.68k | s->callbacks->userdata); |
390 | 4.68k | } |
391 | | /* TODO: Find a way to put back POLLOUT when buffering occurs */ |
392 | 15.8k | } |
393 | | |
394 | | /* Return -1 if the poll handler disappeared */ |
395 | 15.8k | if (s->poll_handle == NULL) { |
396 | 652 | return -1; |
397 | 652 | } |
398 | | |
399 | 15.2k | return 0; |
400 | 15.8k | } |
401 | | |
402 | | /** @internal |
403 | | * @brief returns the poll handle corresponding to the socket, |
404 | | * creates it if it does not exist. |
405 | | * @returns allocated and initialized ssh_poll_handle object |
406 | | */ |
407 | | ssh_poll_handle ssh_socket_get_poll_handle(ssh_socket s) |
408 | 38.2k | { |
409 | 38.2k | if (s->poll_handle) { |
410 | 28.1k | return s->poll_handle; |
411 | 28.1k | } |
412 | 10.1k | s->poll_handle = ssh_poll_new(s->fd, 0, ssh_socket_pollcallback, s); |
413 | 10.1k | return s->poll_handle; |
414 | 38.2k | } |
415 | | |
416 | | /** \internal |
417 | | * \brief Deletes a socket object |
418 | | */ |
419 | | void ssh_socket_free(ssh_socket s) |
420 | 26.9k | { |
421 | 26.9k | if (s == NULL) { |
422 | 0 | return; |
423 | 0 | } |
424 | 26.9k | ssh_socket_close(s); |
425 | 26.9k | SSH_BUFFER_FREE(s->in_buffer); |
426 | 26.9k | SSH_BUFFER_FREE(s->out_buffer); |
427 | 26.9k | SAFE_FREE(s); |
428 | 26.9k | } |
429 | | |
430 | | int ssh_socket_unix(ssh_socket s, const char *path) |
431 | 0 | { |
432 | 0 | struct sockaddr_un sunaddr; |
433 | 0 | char err_msg[SSH_ERRNO_MSG_MAX] = {0}; |
434 | 0 | socket_t fd; |
435 | 0 | sunaddr.sun_family = AF_UNIX; |
436 | 0 | snprintf(sunaddr.sun_path, sizeof(sunaddr.sun_path), "%s", path); |
437 | |
|
438 | 0 | fd = socket(AF_UNIX, SOCK_STREAM, 0); |
439 | 0 | if (fd == SSH_INVALID_SOCKET) { |
440 | 0 | ssh_set_error(s->session, SSH_FATAL, |
441 | 0 | "Error from socket(AF_UNIX, SOCK_STREAM, 0): %s", |
442 | 0 | ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
443 | 0 | return -1; |
444 | 0 | } |
445 | | |
446 | 0 | #ifndef _WIN32 |
447 | 0 | if (fcntl(fd, F_SETFD, 1) == -1) { |
448 | 0 | ssh_set_error(s->session, SSH_FATAL, |
449 | 0 | "Error from fcntl(fd, F_SETFD, 1): %s", |
450 | 0 | ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
451 | 0 | CLOSE_SOCKET(fd); |
452 | 0 | return -1; |
453 | 0 | } |
454 | 0 | #endif |
455 | | |
456 | 0 | if (connect(fd, (struct sockaddr *) &sunaddr, sizeof(sunaddr)) < 0) { |
457 | 0 | ssh_set_error(s->session, SSH_FATAL, "Error from connect(%s): %s", |
458 | 0 | path, |
459 | 0 | ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
460 | 0 | CLOSE_SOCKET(fd); |
461 | 0 | return -1; |
462 | 0 | } |
463 | 0 | ssh_socket_set_fd(s,fd); |
464 | 0 | return 0; |
465 | 0 | } |
466 | | |
467 | | /** \internal |
468 | | * \brief closes a socket |
469 | | */ |
470 | | void ssh_socket_close(ssh_socket s) |
471 | 44.4k | { |
472 | 44.4k | if (ssh_socket_is_open(s)) { |
473 | | #ifdef _WIN32 |
474 | | CLOSE_SOCKET(s->fd); |
475 | | s->last_errno = WSAGetLastError(); |
476 | | #else |
477 | 6.60k | CLOSE_SOCKET(s->fd); |
478 | 6.60k | s->last_errno = errno; |
479 | 6.60k | #endif |
480 | 6.60k | } |
481 | | |
482 | 44.4k | if (s->poll_handle != NULL) { |
483 | 6.60k | ssh_poll_free(s->poll_handle); |
484 | 6.60k | s->poll_handle = NULL; |
485 | 6.60k | } |
486 | | |
487 | 44.4k | s->state = SSH_SOCKET_CLOSED; |
488 | | |
489 | 44.4k | #ifndef _WIN32 |
490 | | /* If the proxy command still runs try to kill it */ |
491 | 44.4k | if (s->proxy_pid != 0) { |
492 | 0 | int status; |
493 | 0 | pid_t pid = s->proxy_pid; |
494 | |
|
495 | 0 | s->proxy_pid = 0; |
496 | 0 | kill(pid, SIGTERM); |
497 | 0 | while (waitpid(pid, &status, 0) == -1) { |
498 | 0 | if (errno != EINTR) { |
499 | 0 | char err_msg[SSH_ERRNO_MSG_MAX] = {0}; |
500 | 0 | SSH_LOG(SSH_LOG_TRACE, "waitpid failed: %s", |
501 | 0 | ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
502 | 0 | return; |
503 | 0 | } |
504 | 0 | } |
505 | 0 | if (!WIFEXITED(status)) { |
506 | 0 | SSH_LOG(SSH_LOG_TRACE, "Proxy command exited abnormally"); |
507 | 0 | return; |
508 | 0 | } |
509 | 0 | SSH_LOG(SSH_LOG_TRACE, "Proxy command returned %d", WEXITSTATUS(status)); |
510 | 0 | } |
511 | 44.4k | #endif |
512 | 44.4k | } |
513 | | |
514 | | /** |
515 | | * @internal |
516 | | * @brief sets the file descriptor of the socket. |
517 | | * @param[out] s ssh_socket to update |
518 | | * @param[in] fd file descriptor to set |
519 | | * @warning this function updates both the input and output |
520 | | * file descriptors |
521 | | */ |
522 | | void ssh_socket_set_fd(ssh_socket s, socket_t fd) |
523 | 10.1k | { |
524 | 10.1k | ssh_poll_handle h = NULL; |
525 | | |
526 | 10.1k | s->fd = fd; |
527 | | |
528 | 10.1k | if (s->poll_handle) { |
529 | 0 | ssh_poll_set_fd(s->poll_handle,fd); |
530 | 10.1k | } else { |
531 | 10.1k | s->state = SSH_SOCKET_CONNECTING; |
532 | 10.1k | h = ssh_socket_get_poll_handle(s); |
533 | 10.1k | if (h == NULL) { |
534 | 0 | return; |
535 | 0 | } |
536 | | |
537 | | /* POLLOUT is the event to wait for in a nonblocking connect */ |
538 | 10.1k | ssh_poll_set_events(h, POLLOUT); |
539 | | #ifdef _WIN32 |
540 | | ssh_poll_add_events(h, POLLWRNORM); |
541 | | #endif |
542 | 10.1k | } |
543 | 10.1k | } |
544 | | |
545 | | /** \internal |
546 | | * \brief returns the input file descriptor of the socket |
547 | | */ |
548 | | socket_t ssh_socket_get_fd(ssh_socket s) |
549 | 3.57k | { |
550 | 3.57k | return s->fd; |
551 | 3.57k | } |
552 | | |
553 | | /** \internal |
554 | | * \brief returns nonzero if the socket is open |
555 | | */ |
556 | | int ssh_socket_is_open(ssh_socket s) |
557 | 202k | { |
558 | 202k | return s->fd != SSH_INVALID_SOCKET; |
559 | 202k | } |
560 | | |
561 | | /** \internal |
562 | | * \brief read len bytes from socket into buffer |
563 | | */ |
564 | | static ssize_t ssh_socket_unbuffered_read(ssh_socket s, |
565 | | void *buffer, |
566 | | uint32_t len) |
567 | 22.0k | { |
568 | 22.0k | ssize_t rc = -1; |
569 | | |
570 | 22.0k | if (s->data_except) { |
571 | 0 | return -1; |
572 | 0 | } |
573 | 22.0k | if (s->fd_is_socket) { |
574 | 22.0k | rc = recv(s->fd, buffer, len, 0); |
575 | 22.0k | } else { |
576 | 0 | rc = read(s->fd, buffer, len); |
577 | 0 | } |
578 | | #ifdef _WIN32 |
579 | | s->last_errno = WSAGetLastError(); |
580 | | #else |
581 | 22.0k | s->last_errno = errno; |
582 | 22.0k | #endif |
583 | 22.0k | s->read_wontblock = 0; |
584 | | |
585 | 22.0k | if (rc < 0) { |
586 | 0 | s->data_except = 1; |
587 | 22.0k | } else { |
588 | 22.0k | SSH_LOG(SSH_LOG_TRACE, "read %zd", rc); |
589 | 22.0k | } |
590 | | |
591 | 22.0k | return rc; |
592 | 22.0k | } |
593 | | |
594 | | /** \internal |
595 | | * \brief writes len bytes from buffer to socket |
596 | | */ |
597 | | static ssize_t ssh_socket_unbuffered_write(ssh_socket s, |
598 | | const void *buffer, |
599 | | uint32_t len) |
600 | 14.1k | { |
601 | 14.1k | ssize_t w = -1; |
602 | 14.1k | int flags = 0; |
603 | | |
604 | 14.1k | #ifdef MSG_NOSIGNAL |
605 | 14.1k | flags |= MSG_NOSIGNAL; |
606 | 14.1k | #endif |
607 | | |
608 | 14.1k | if (s->data_except) { |
609 | 0 | return -1; |
610 | 0 | } |
611 | | |
612 | 14.1k | if (s->fd_is_socket) { |
613 | 14.1k | w = send(s->fd, buffer, len, flags); |
614 | 14.1k | } else { |
615 | 0 | w = write(s->fd, buffer, len); |
616 | 0 | } |
617 | | #ifdef _WIN32 |
618 | | s->last_errno = WSAGetLastError(); |
619 | | #else |
620 | 14.1k | s->last_errno = errno; |
621 | 14.1k | #endif |
622 | 14.1k | s->write_wontblock = 0; |
623 | | /* Reactive the POLLOUT detector in the poll multiplexer system */ |
624 | 14.1k | if (s->poll_handle) { |
625 | 14.1k | SSH_LOG(SSH_LOG_PACKET, "Enabling POLLOUT for socket"); |
626 | 14.1k | ssh_poll_add_events(s->poll_handle, POLLOUT); |
627 | 14.1k | } |
628 | 14.1k | if (w < 0) { |
629 | 29 | s->data_except = 1; |
630 | 29 | } |
631 | | |
632 | 14.1k | SSH_LOG(SSH_LOG_TRACE, "wrote %zd", w); |
633 | 14.1k | return w; |
634 | 14.1k | } |
635 | | |
636 | | /** \internal |
637 | | * \brief returns nonzero if the current socket is in the fd_set |
638 | | */ |
639 | | int ssh_socket_fd_isset(ssh_socket s, fd_set *set) |
640 | 0 | { |
641 | 0 | if(s->fd == SSH_INVALID_SOCKET) { |
642 | 0 | return 0; |
643 | 0 | } |
644 | 0 | return FD_ISSET(s->fd,set); |
645 | 0 | } |
646 | | |
647 | | /** \internal |
648 | | * \brief sets the current fd in a fd_set and updates the max_fd |
649 | | */ |
650 | | |
651 | | void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd) |
652 | 0 | { |
653 | 0 | if (s->fd == SSH_INVALID_SOCKET) { |
654 | 0 | return; |
655 | 0 | } |
656 | | |
657 | 0 | FD_SET(s->fd,set); |
658 | |
|
659 | 0 | if (s->fd >= 0 && |
660 | 0 | s->fd >= *max_fd && |
661 | 0 | s->fd != SSH_INVALID_SOCKET) { |
662 | 0 | *max_fd = s->fd + 1; |
663 | 0 | } |
664 | 0 | } |
665 | | |
666 | | /** \internal |
667 | | * \brief buffered write of data |
668 | | * \returns SSH_OK, or SSH_ERROR |
669 | | * \warning has no effect on socket before a flush |
670 | | */ |
671 | | int ssh_socket_write(ssh_socket s, const void *buffer, uint32_t len) |
672 | 112k | { |
673 | 112k | if (len > 0) { |
674 | 112k | if (ssh_buffer_add_data(s->out_buffer, buffer, len) < 0) { |
675 | 0 | ssh_set_error_oom(s->session); |
676 | 0 | return SSH_ERROR; |
677 | 0 | } |
678 | 112k | ssh_socket_nonblocking_flush(s); |
679 | 112k | } |
680 | | |
681 | 112k | return SSH_OK; |
682 | 112k | } |
683 | | |
684 | | |
685 | | /** \internal |
686 | | * \brief starts a nonblocking flush of the output buffer |
687 | | * |
688 | | */ |
689 | | int ssh_socket_nonblocking_flush(ssh_socket s) |
690 | 122k | { |
691 | 122k | ssh_session session = s->session; |
692 | 122k | uint32_t len; |
693 | | |
694 | 122k | if (!ssh_socket_is_open(s)) { |
695 | 618 | session->alive = 0; |
696 | 618 | if (s->callbacks && s->callbacks->exception) { |
697 | 618 | s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, |
698 | 618 | s->last_errno, |
699 | 618 | s->callbacks->userdata); |
700 | 618 | } else { |
701 | 0 | char err_msg[SSH_ERRNO_MSG_MAX] = {0}; |
702 | 0 | ssh_set_error(session, |
703 | 0 | SSH_FATAL, |
704 | 0 | "Writing packet: error on socket (or connection " |
705 | 0 | "closed): %s", |
706 | 0 | ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
707 | 0 | } |
708 | | |
709 | 618 | return SSH_ERROR; |
710 | 618 | } |
711 | | |
712 | 121k | len = ssh_buffer_get_len(s->out_buffer); |
713 | 121k | if (!s->write_wontblock && s->poll_handle && len > 0) { |
714 | | /* force the poll system to catch pollout events */ |
715 | 107k | ssh_poll_add_events(s->poll_handle, POLLOUT); |
716 | | |
717 | 107k | return SSH_AGAIN; |
718 | 107k | } |
719 | | |
720 | 14.1k | if (s->write_wontblock && len > 0) { |
721 | 14.1k | ssize_t bwritten; |
722 | | |
723 | 14.1k | bwritten = ssh_socket_unbuffered_write(s, |
724 | 14.1k | ssh_buffer_get(s->out_buffer), |
725 | 14.1k | len); |
726 | 14.1k | if (bwritten < 0) { |
727 | 29 | session->alive = 0; |
728 | 29 | ssh_socket_close(s); |
729 | | |
730 | 29 | if (s->callbacks && s->callbacks->exception) { |
731 | 29 | s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, |
732 | 29 | s->last_errno, |
733 | 29 | s->callbacks->userdata); |
734 | 29 | } else { |
735 | 0 | char err_msg[SSH_ERRNO_MSG_MAX] = {0}; |
736 | 0 | ssh_set_error(session, |
737 | 0 | SSH_FATAL, |
738 | 0 | "Writing packet: error on socket (or connection " |
739 | 0 | "closed): %s", |
740 | 0 | ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
741 | 0 | } |
742 | | |
743 | 29 | return SSH_ERROR; |
744 | 29 | } |
745 | | |
746 | 14.1k | ssh_buffer_pass_bytes(s->out_buffer, (uint32_t)bwritten); |
747 | 14.1k | if (s->session->socket_counter != NULL) { |
748 | 0 | s->session->socket_counter->out_bytes += bwritten; |
749 | 0 | } |
750 | 14.1k | } |
751 | | |
752 | | /* Is there some data pending? */ |
753 | 14.1k | len = ssh_buffer_get_len(s->out_buffer); |
754 | 14.1k | if (s->poll_handle && len > 0) { |
755 | 0 | SSH_LOG(SSH_LOG_TRACE, |
756 | 0 | "did not send all the data, queuing pollout event"); |
757 | | /* force the poll system to catch pollout events */ |
758 | 0 | ssh_poll_add_events(s->poll_handle, POLLOUT); |
759 | |
|
760 | 0 | return SSH_AGAIN; |
761 | 0 | } |
762 | | |
763 | | /* all data written */ |
764 | 14.1k | return SSH_OK; |
765 | 14.1k | } |
766 | | |
767 | | void ssh_socket_set_write_wontblock(ssh_socket s) |
768 | 3.57k | { |
769 | 3.57k | s->write_wontblock = 1; |
770 | 3.57k | } |
771 | | |
772 | | void ssh_socket_set_read_wontblock(ssh_socket s) |
773 | 0 | { |
774 | 0 | s->read_wontblock = 1; |
775 | 0 | } |
776 | | |
777 | | void ssh_socket_set_except(ssh_socket s) |
778 | 0 | { |
779 | 0 | s->data_except = 1; |
780 | 0 | } |
781 | | |
782 | | int ssh_socket_data_available(ssh_socket s) |
783 | 0 | { |
784 | 0 | return s->read_wontblock; |
785 | 0 | } |
786 | | |
787 | | int ssh_socket_data_writable(ssh_socket s) |
788 | 0 | { |
789 | 0 | return s->write_wontblock; |
790 | 0 | } |
791 | | |
792 | | /** @internal |
793 | | * @brief returns the number of outgoing bytes currently buffered |
794 | | * @param s the socket |
795 | | * @returns numbers of bytes buffered, or 0 if the socket isn't connected |
796 | | */ |
797 | | int ssh_socket_buffered_write_bytes(ssh_socket s) |
798 | 0 | { |
799 | 0 | if (s==NULL || s->out_buffer == NULL) { |
800 | 0 | return 0; |
801 | 0 | } |
802 | | |
803 | 0 | return ssh_buffer_get_len(s->out_buffer); |
804 | 0 | } |
805 | | |
806 | | |
807 | | int ssh_socket_get_status(ssh_socket s) |
808 | 0 | { |
809 | 0 | int r = 0; |
810 | |
|
811 | 0 | if (ssh_buffer_get_len(s->in_buffer) > 0) { |
812 | 0 | r |= SSH_READ_PENDING; |
813 | 0 | } |
814 | |
|
815 | 0 | if (ssh_buffer_get_len(s->out_buffer) > 0) { |
816 | 0 | r |= SSH_WRITE_PENDING; |
817 | 0 | } |
818 | |
|
819 | 0 | if (s->data_except) { |
820 | 0 | r |= SSH_CLOSED_ERROR; |
821 | 0 | } |
822 | |
|
823 | 0 | return r; |
824 | 0 | } |
825 | | |
826 | | int ssh_socket_get_poll_flags(ssh_socket s) |
827 | 0 | { |
828 | 0 | int r = 0; |
829 | 0 | if (s->poll_handle != NULL && (ssh_poll_get_events (s->poll_handle) & POLLIN) > 0) { |
830 | 0 | r |= SSH_READ_PENDING; |
831 | 0 | } |
832 | 0 | if (s->poll_handle != NULL && (ssh_poll_get_events (s->poll_handle) & POLLOUT) > 0) { |
833 | 0 | r |= SSH_WRITE_PENDING; |
834 | 0 | } |
835 | 0 | return r; |
836 | 0 | } |
837 | | |
838 | | #ifdef _WIN32 |
839 | | int ssh_socket_set_nonblocking(socket_t fd) |
840 | | { |
841 | | u_long nonblocking = 1; |
842 | | return ioctlsocket(fd, FIONBIO, &nonblocking); |
843 | | } |
844 | | |
845 | | int ssh_socket_set_blocking(socket_t fd) |
846 | | { |
847 | | u_long nonblocking = 0; |
848 | | return ioctlsocket(fd, FIONBIO, &nonblocking); |
849 | | } |
850 | | |
851 | | #else /* _WIN32 */ |
852 | | int ssh_socket_set_nonblocking(socket_t fd) |
853 | 0 | { |
854 | 0 | return fcntl(fd, F_SETFL, O_NONBLOCK); |
855 | 0 | } |
856 | | |
857 | | int ssh_socket_set_blocking(socket_t fd) |
858 | 3.57k | { |
859 | 3.57k | return fcntl(fd, F_SETFL, 0); |
860 | 3.57k | } |
861 | | #endif /* _WIN32 */ |
862 | | |
863 | | /** |
864 | | * @internal |
865 | | * @brief Launches a socket connection |
866 | | * If the socket connected callback has been defined and |
867 | | * a poll object exists, this call will be non blocking. |
868 | | * @param s socket to connect. |
869 | | * @param host hostname or ip address to connect to. |
870 | | * @param port port number to connect to. |
871 | | * @param bind_addr address to bind to, or NULL for default. |
872 | | * @returns SSH_OK socket is being connected. |
873 | | * @returns SSH_ERROR error while connecting to remote host. |
874 | | */ |
875 | | int ssh_socket_connect(ssh_socket s, |
876 | | const char *host, |
877 | | uint16_t port, |
878 | | const char *bind_addr) |
879 | 0 | { |
880 | 0 | socket_t fd; |
881 | |
|
882 | 0 | if (s->state != SSH_SOCKET_NONE) { |
883 | 0 | ssh_set_error(s->session, SSH_FATAL, |
884 | 0 | "ssh_socket_connect called on socket not unconnected"); |
885 | 0 | return SSH_ERROR; |
886 | 0 | } |
887 | 0 | fd = ssh_connect_host_nonblocking(s->session, host, bind_addr, port); |
888 | 0 | SSH_LOG(SSH_LOG_DEBUG, "Nonblocking connection socket: %d", fd); |
889 | 0 | if (fd == SSH_INVALID_SOCKET) { |
890 | 0 | return SSH_ERROR; |
891 | 0 | } |
892 | 0 | ssh_socket_set_fd(s,fd); |
893 | |
|
894 | 0 | return SSH_OK; |
895 | 0 | } |
896 | | |
897 | | #ifdef WITH_EXEC |
898 | | /** |
899 | | * @internal |
900 | | * @brief executes a command and redirect input and outputs |
901 | | * @param command command to execute |
902 | | * @param in input file descriptor |
903 | | * @param out output file descriptor |
904 | | */ |
905 | | void |
906 | | ssh_execute_command(const char *command, socket_t in, socket_t out) |
907 | | { |
908 | | const char *shell = NULL; |
909 | | const char *args[] = {NULL/*shell*/, "-c", command, NULL}; |
910 | | int devnull; |
911 | | int rc; |
912 | | |
913 | | /* Prepare /dev/null socket for the stderr redirection */ |
914 | | devnull = open("/dev/null", O_WRONLY); |
915 | | if (devnull == -1) { |
916 | | SSH_LOG(SSH_LOG_TRACE, "Failed to open /dev/null"); |
917 | | exit(1); |
918 | | } |
919 | | |
920 | | /* |
921 | | * By default, use the current users shell. This could fail with some |
922 | | * shells like zsh or dash ... |
923 | | */ |
924 | | shell = getenv("SHELL"); |
925 | | if (shell == NULL || shell[0] == '\0') { |
926 | | /* Fall back to the /bin/sh only if the bash is not available. But there are |
927 | | * issues with dash or whatever people tend to link to /bin/sh */ |
928 | | rc = access("/bin/bash", 0); |
929 | | if (rc != 0) { |
930 | | shell = "/bin/sh"; |
931 | | } else { |
932 | | shell = "/bin/bash"; |
933 | | } |
934 | | } |
935 | | args[0] = shell; |
936 | | |
937 | | /* redirect in and out to stdin, stdout */ |
938 | | dup2(in, 0); |
939 | | dup2(out, 1); |
940 | | /* Ignore anything on the stderr */ |
941 | | dup2(devnull, STDERR_FILENO); |
942 | | close(in); |
943 | | close(out); |
944 | | rc = execv(args[0], (char * const *)args); |
945 | | if (rc < 0) { |
946 | | char err_msg[SSH_ERRNO_MSG_MAX] = {0}; |
947 | | |
948 | | SSH_LOG(SSH_LOG_WARN, "Failed to execute command %s: %s", |
949 | | command, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
950 | | } |
951 | | exit(1); |
952 | | } |
953 | | |
954 | | /** |
955 | | * @internal |
956 | | * @brief Open a socket on a ProxyCommand |
957 | | * This call will always be nonblocking. |
958 | | * @param s socket to connect. |
959 | | * @param command Command to execute. |
960 | | * @returns SSH_OK socket is being connected. |
961 | | * @returns SSH_ERROR error while executing the command. |
962 | | */ |
963 | | int |
964 | | ssh_socket_connect_proxycommand(ssh_socket s, const char *command) |
965 | | { |
966 | | socket_t pair[2]; |
967 | | ssh_poll_handle h = NULL; |
968 | | int pid; |
969 | | int rc; |
970 | | |
971 | | if (s->state != SSH_SOCKET_NONE) { |
972 | | return SSH_ERROR; |
973 | | } |
974 | | |
975 | | rc = socketpair(PF_UNIX, SOCK_STREAM, 0, pair); |
976 | | if (rc < 0) { |
977 | | return SSH_ERROR; |
978 | | } |
979 | | |
980 | | SSH_LOG(SSH_LOG_DEBUG, "Executing proxycommand '%s'", command); |
981 | | pid = fork(); |
982 | | if (pid == 0) { |
983 | | ssh_execute_command(command, pair[0], pair[0]); |
984 | | /* Does not return */ |
985 | | } |
986 | | s->proxy_pid = pid; |
987 | | close(pair[0]); |
988 | | SSH_LOG(SSH_LOG_DEBUG, "ProxyCommand connection pipe: [%d,%d]",pair[0],pair[1]); |
989 | | ssh_socket_set_fd(s, pair[1]); |
990 | | s->fd_is_socket = 0; |
991 | | h = ssh_socket_get_poll_handle(s); |
992 | | if (h == NULL) { |
993 | | return SSH_ERROR; |
994 | | } |
995 | | ssh_socket_set_connected(s, h); |
996 | | if (s->callbacks && s->callbacks->connected) { |
997 | | s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, 0, s->callbacks->userdata); |
998 | | } |
999 | | |
1000 | | return SSH_OK; |
1001 | | } |
1002 | | #endif /* WITH_EXEC */ |
1003 | | |
1004 | | #ifndef _WIN32 |
1005 | | #ifdef HAVE_PTHREAD |
1006 | | static int |
1007 | | verify_knownhost(ssh_session session) |
1008 | 0 | { |
1009 | 0 | enum ssh_known_hosts_e state; |
1010 | |
|
1011 | 0 | state = ssh_session_is_known_server(session); |
1012 | |
|
1013 | 0 | switch (state) { |
1014 | 0 | case SSH_KNOWN_HOSTS_OK: |
1015 | 0 | break; /* ok */ |
1016 | 0 | default: |
1017 | 0 | SSH_LOG(SSH_LOG_WARN, "Couldn't verify knownhost during proxyjump."); |
1018 | 0 | return SSH_ERROR; |
1019 | 0 | } |
1020 | | |
1021 | 0 | return SSH_OK; |
1022 | 0 | } |
1023 | | |
1024 | | static void * |
1025 | | jump_thread_func(void *arg) |
1026 | 0 | { |
1027 | 0 | struct jump_thread_data_struct *jump_thread_data = NULL; |
1028 | 0 | struct ssh_jump_info_struct *jis = NULL; |
1029 | 0 | struct ssh_jump_callbacks_struct *cb = NULL; |
1030 | 0 | ssh_session jump_session = NULL; |
1031 | 0 | ssh_channel caa = NULL; |
1032 | 0 | int rc; |
1033 | 0 | ssh_event event = NULL; |
1034 | 0 | ssh_connector connector_in = NULL, connector_out = NULL; |
1035 | 0 | ssh_session session = NULL; |
1036 | 0 | int next_port; |
1037 | 0 | char *next_hostname = NULL; |
1038 | |
|
1039 | 0 | jump_thread_data = (struct jump_thread_data_struct *)arg; |
1040 | 0 | session = jump_thread_data->session; |
1041 | |
|
1042 | 0 | next_port = session->opts.port; |
1043 | 0 | next_hostname = strdup(session->opts.host); |
1044 | |
|
1045 | 0 | jump_session = ssh_new(); |
1046 | 0 | if (jump_session == NULL) { |
1047 | 0 | goto exit; |
1048 | 0 | } |
1049 | | |
1050 | 0 | jump_session->proxy_root = false; |
1051 | | /* Reset the global variable if it was previously 1 */ |
1052 | 0 | if (session->proxy_root) { |
1053 | 0 | proxy_disconnect = 0; |
1054 | 0 | } |
1055 | |
|
1056 | 0 | for (jis = ssh_list_pop_head(struct ssh_jump_info_struct *, |
1057 | 0 | session->opts.proxy_jumps); |
1058 | 0 | jis != NULL; |
1059 | 0 | jis = ssh_list_pop_head(struct ssh_jump_info_struct *, |
1060 | 0 | session->opts.proxy_jumps)) { |
1061 | 0 | rc = ssh_list_append(jump_session->opts.proxy_jumps, jis); |
1062 | 0 | if (rc != SSH_OK) { |
1063 | 0 | ssh_set_error_oom(session); |
1064 | 0 | goto exit; |
1065 | 0 | } |
1066 | 0 | } |
1067 | 0 | for (jis = |
1068 | 0 | ssh_list_pop_head(struct ssh_jump_info_struct *, |
1069 | 0 | session->opts.proxy_jumps_user_cb); |
1070 | 0 | jis != NULL; |
1071 | 0 | jis = ssh_list_pop_head(struct ssh_jump_info_struct *, |
1072 | 0 | session->opts.proxy_jumps_user_cb)) { |
1073 | 0 | rc = ssh_list_append(jump_session->opts.proxy_jumps_user_cb, jis); |
1074 | 0 | if (rc != SSH_OK) { |
1075 | 0 | ssh_set_error_oom(session); |
1076 | 0 | goto exit; |
1077 | 0 | } |
1078 | 0 | } |
1079 | | |
1080 | 0 | ssh_options_set(jump_session, |
1081 | 0 | SSH_OPTIONS_LOG_VERBOSITY, |
1082 | 0 | &session->common.log_verbosity); |
1083 | | |
1084 | | /* Pop the information about the current jump */ |
1085 | 0 | jis = ssh_list_pop_head(struct ssh_jump_info_struct *, |
1086 | 0 | jump_session->opts.proxy_jumps); |
1087 | 0 | if (jis == NULL) { |
1088 | 0 | SSH_LOG(SSH_LOG_WARN, "Inconsistent list of proxy jumps received"); |
1089 | 0 | goto exit; |
1090 | 0 | } |
1091 | | |
1092 | 0 | ssh_options_set(jump_session, SSH_OPTIONS_HOST, jis->hostname); |
1093 | 0 | ssh_options_set(jump_session, SSH_OPTIONS_USER, jis->username); |
1094 | 0 | ssh_options_set(jump_session, SSH_OPTIONS_PORT, &jis->port); |
1095 | | |
1096 | | /* Pop the callbacks for the current jump */ |
1097 | 0 | cb = ssh_list_pop_head(struct ssh_jump_callbacks_struct *, |
1098 | 0 | jump_session->opts.proxy_jumps_user_cb); |
1099 | |
|
1100 | 0 | if (cb != NULL) { |
1101 | 0 | rc = cb->before_connection(jump_session, cb->userdata); |
1102 | 0 | if (rc != SSH_OK) { |
1103 | 0 | SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(jump_session)); |
1104 | 0 | goto exit; |
1105 | 0 | } |
1106 | 0 | } |
1107 | | |
1108 | | /* If there are more jumps then this will make a new thread and call the |
1109 | | * current function again, until there are no jumps. When there are no jumps |
1110 | | * it connects normally. */ |
1111 | 0 | rc = ssh_connect(jump_session); |
1112 | 0 | if (rc != SSH_OK) { |
1113 | 0 | SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(jump_session)); |
1114 | 0 | goto exit; |
1115 | 0 | } |
1116 | | |
1117 | | /* Use the callback or default implementation for verifying knownhost */ |
1118 | 0 | if (cb != NULL && cb->verify_knownhost != NULL) { |
1119 | 0 | rc = cb->verify_knownhost(jump_session, cb->userdata); |
1120 | 0 | } else { |
1121 | 0 | rc = verify_knownhost(jump_session); |
1122 | 0 | } |
1123 | 0 | if (rc != SSH_OK) { |
1124 | 0 | goto exit; |
1125 | 0 | } |
1126 | | |
1127 | | /* Use the callback or publickey method to authenticate */ |
1128 | 0 | if (cb != NULL && cb->authenticate != NULL) { |
1129 | 0 | rc = cb->authenticate(jump_session, cb->userdata); |
1130 | 0 | } else { |
1131 | 0 | rc = ssh_userauth_publickey_auto(jump_session, NULL, NULL); |
1132 | 0 | } |
1133 | 0 | if (rc != SSH_OK) { |
1134 | 0 | SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(jump_session)); |
1135 | 0 | goto exit; |
1136 | 0 | } |
1137 | | |
1138 | 0 | caa = ssh_channel_new(jump_session); |
1139 | 0 | if (caa == NULL) { |
1140 | 0 | goto exit; |
1141 | 0 | } |
1142 | | /* The origin hostname and port are set to match OpenSSH implementation |
1143 | | * they are only used for logging on the server */ |
1144 | 0 | rc = ssh_channel_open_forward(caa, |
1145 | 0 | next_hostname, |
1146 | 0 | next_port, |
1147 | 0 | "127.0.0.1", |
1148 | 0 | 65535); |
1149 | 0 | if (rc != SSH_OK) { |
1150 | 0 | SSH_LOG(SSH_LOG_WARN, |
1151 | 0 | "Error opening port forwarding channel: %s", |
1152 | 0 | ssh_get_error(jump_session)); |
1153 | 0 | goto exit; |
1154 | 0 | } |
1155 | | |
1156 | 0 | event = ssh_event_new(); |
1157 | 0 | if (event == NULL) { |
1158 | 0 | goto exit; |
1159 | 0 | } |
1160 | | |
1161 | 0 | connector_in = ssh_connector_new(jump_session); |
1162 | 0 | if (connector_in == NULL) { |
1163 | 0 | goto exit; |
1164 | 0 | } |
1165 | 0 | ssh_connector_set_out_channel(connector_in, caa, SSH_CONNECTOR_STDINOUT); |
1166 | 0 | ssh_connector_set_in_fd(connector_in, jump_thread_data->fd); |
1167 | 0 | ssh_event_add_connector(event, connector_in); |
1168 | |
|
1169 | 0 | connector_out = ssh_connector_new(jump_session); |
1170 | 0 | if (connector_out == NULL) { |
1171 | 0 | goto exit; |
1172 | 0 | } |
1173 | 0 | ssh_connector_set_out_fd(connector_out, jump_thread_data->fd); |
1174 | 0 | ssh_connector_set_in_channel(connector_out, caa, SSH_CONNECTOR_STDINOUT); |
1175 | 0 | ssh_event_add_connector(event, connector_out); |
1176 | |
|
1177 | 0 | while (ssh_channel_is_open(caa)) { |
1178 | 0 | if (proxy_disconnect == 1) { |
1179 | 0 | break; |
1180 | 0 | } |
1181 | 0 | rc = ssh_event_dopoll(event, 60000); |
1182 | 0 | if (rc == SSH_ERROR) { |
1183 | 0 | SSH_LOG(SSH_LOG_WARN, |
1184 | 0 | "Error in ssh_event_dopoll() during proxy jump"); |
1185 | 0 | break; |
1186 | 0 | } |
1187 | 0 | } |
1188 | |
|
1189 | 0 | exit: |
1190 | 0 | if (connector_in != NULL) { |
1191 | 0 | ssh_event_remove_connector(event, connector_in); |
1192 | 0 | ssh_connector_free(connector_in); |
1193 | 0 | } |
1194 | 0 | if (connector_out != NULL) { |
1195 | 0 | ssh_event_remove_connector(event, connector_out); |
1196 | 0 | ssh_connector_free(connector_out); |
1197 | 0 | } |
1198 | 0 | SAFE_FREE(next_hostname); |
1199 | 0 | if (jis != NULL) { |
1200 | 0 | SAFE_FREE(jis->hostname); |
1201 | 0 | SAFE_FREE(jis->username); |
1202 | 0 | } |
1203 | 0 | SAFE_FREE(jis); |
1204 | |
|
1205 | 0 | ssh_disconnect(jump_session); |
1206 | 0 | ssh_event_free(event); |
1207 | 0 | ssh_free(jump_session); |
1208 | |
|
1209 | 0 | SAFE_FREE(jump_thread_data); |
1210 | |
|
1211 | 0 | pthread_exit(NULL); |
1212 | 0 | } |
1213 | | |
1214 | | int |
1215 | | ssh_socket_connect_proxyjump(ssh_socket s) |
1216 | 0 | { |
1217 | 0 | ssh_poll_handle h = NULL; |
1218 | 0 | int rc; |
1219 | 0 | pthread_t jump_thread; |
1220 | 0 | struct jump_thread_data_struct *jump_thread_data = NULL; |
1221 | 0 | socket_t pair[2]; |
1222 | |
|
1223 | 0 | if (s->state != SSH_SOCKET_NONE) { |
1224 | 0 | ssh_set_error( |
1225 | 0 | s->session, |
1226 | 0 | SSH_FATAL, |
1227 | 0 | "ssh_socket_connect_proxyjump called on socket not unconnected"); |
1228 | 0 | return SSH_ERROR; |
1229 | 0 | } |
1230 | | |
1231 | 0 | jump_thread_data = calloc(1, sizeof(struct jump_thread_data_struct)); |
1232 | 0 | if (jump_thread_data == NULL) { |
1233 | 0 | ssh_set_error_oom(s->session); |
1234 | 0 | return SSH_ERROR; |
1235 | 0 | } |
1236 | | |
1237 | 0 | rc = socketpair(PF_UNIX, SOCK_STREAM, 0, pair); |
1238 | 0 | if (rc == -1) { |
1239 | 0 | char err_msg[SSH_ERRNO_MSG_MAX] = {0}; |
1240 | |
|
1241 | 0 | ssh_set_error(s->session, |
1242 | 0 | SSH_FATAL, |
1243 | 0 | "Creating socket pair failed: %s", |
1244 | 0 | ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
1245 | 0 | SAFE_FREE(jump_thread_data); |
1246 | 0 | return SSH_ERROR; |
1247 | 0 | } |
1248 | | |
1249 | 0 | jump_thread_data->session = s->session; |
1250 | 0 | jump_thread_data->fd = pair[0]; |
1251 | |
|
1252 | 0 | rc = pthread_create(&jump_thread, NULL, jump_thread_func, jump_thread_data); |
1253 | 0 | if (rc != 0) { |
1254 | 0 | char err_msg[SSH_ERRNO_MSG_MAX] = {0}; |
1255 | |
|
1256 | 0 | ssh_set_error(s->session, |
1257 | 0 | SSH_FATAL, |
1258 | 0 | "Creating new thread failed: %s", |
1259 | 0 | ssh_strerror(rc, err_msg, SSH_ERRNO_MSG_MAX)); |
1260 | 0 | SAFE_FREE(jump_thread_data); |
1261 | 0 | return SSH_ERROR; |
1262 | 0 | } |
1263 | 0 | rc = pthread_detach(jump_thread); |
1264 | 0 | if (rc != 0) { |
1265 | 0 | char err_msg[SSH_ERRNO_MSG_MAX] = {0}; |
1266 | |
|
1267 | 0 | ssh_set_error(s->session, |
1268 | 0 | SSH_FATAL, |
1269 | 0 | "Failed to detach thread: %s", |
1270 | 0 | ssh_strerror(rc, err_msg, SSH_ERRNO_MSG_MAX)); |
1271 | 0 | SAFE_FREE(jump_thread_data); |
1272 | 0 | return SSH_ERROR; |
1273 | 0 | } |
1274 | | |
1275 | 0 | SSH_LOG(SSH_LOG_DEBUG, |
1276 | 0 | "ProxyJump connection pipe: [%d,%d]", |
1277 | 0 | pair[0], |
1278 | 0 | pair[1]); |
1279 | 0 | ssh_socket_set_fd(s, pair[1]); |
1280 | 0 | s->fd_is_socket = 1; |
1281 | 0 | h = ssh_socket_get_poll_handle(s); |
1282 | 0 | if (h == NULL) { |
1283 | 0 | return SSH_ERROR; |
1284 | 0 | } |
1285 | 0 | ssh_socket_set_connected(s, h); |
1286 | 0 | if (s->callbacks && s->callbacks->connected) { |
1287 | 0 | s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, |
1288 | 0 | 0, |
1289 | 0 | s->callbacks->userdata); |
1290 | 0 | } |
1291 | |
|
1292 | 0 | return SSH_OK; |
1293 | 0 | } |
1294 | | |
1295 | | #endif /* HAVE_PTHREAD */ |
1296 | | |
1297 | | #endif /* _WIN32 */ |
1298 | | /** @} */ |