diff options
author | Andreas Schneider <asn@cynapses.org> | 2010-09-06 14:28:38 +0200 |
---|---|---|
committer | Andreas Schneider <asn@cynapses.org> | 2010-09-06 14:28:38 +0200 |
commit | f7842e3a4b9acea2126ff725f993c299aef0e6db (patch) | |
tree | 18239f819a5edbcfc7f2961c48f3f9297314ef22 /src/socket.c | |
parent | 38421403d2dc45636e597f2a909daa6ae31976de (diff) | |
download | libssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.tar.gz libssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.tar.xz libssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.zip |
misc: Rename libssh/ to src/
Diffstat (limited to 'src/socket.c')
-rw-r--r-- | src/socket.c | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/src/socket.c b/src/socket.c new file mode 100644 index 00000000..2f1f5534 --- /dev/null +++ b/src/socket.c @@ -0,0 +1,719 @@ +/* + * socket.c - socket functions for the library + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#ifdef _WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#if _MSC_VER >= 1400 +#include <io.h> +#undef open +#define open _open +#undef close +#define close _close +#undef read +#define read _read +#undef write +#define write _write +#endif /* _MSC_VER */ +#else /* _WIN32 */ +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#endif /* _WIN32 */ + +#include "libssh/priv.h" +#include "libssh/callbacks.h" +#include "libssh/socket.h" +#include "libssh/buffer.h" +#include "libssh/poll.h" +#include "libssh/session.h" + +#ifndef _WIN32 +extern char **environ; +#endif +/** + * @internal + * + * @defgroup libssh_socket The SSH socket functions. + * @ingroup libssh + * + * Functions for handling sockets. + * + * @{ + */ + +enum ssh_socket_states_e { + SSH_SOCKET_NONE, + SSH_SOCKET_CONNECTING, + SSH_SOCKET_CONNECTED, + SSH_SOCKET_EOF, + SSH_SOCKET_ERROR, + SSH_SOCKET_CLOSED +}; + +struct ssh_socket_struct { + socket_t fd_in; + socket_t fd_out; + int fd_is_socket; + int last_errno; + int data_to_read; /* reading now on socket will + not block */ + int data_to_write; + int data_except; + enum ssh_socket_states_e state; + ssh_buffer out_buffer; + ssh_buffer in_buffer; + ssh_session session; + ssh_socket_callbacks callbacks; + ssh_poll_handle poll_in; + ssh_poll_handle poll_out; +}; + +static int ssh_socket_unbuffered_read(ssh_socket s, void *buffer, uint32_t len); +static int ssh_socket_unbuffered_write(ssh_socket s, const void *buffer, + uint32_t len); + +/** + * \internal + * \brief inits the socket system (windows specific) + */ +int ssh_socket_init(void) { +#ifdef _WIN32 + struct WSAData wsaData; + + /* Initiates use of the Winsock DLL by a process. */ + if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) { + return -1; + } + +#endif + ssh_poll_init(); + + return 0; +} + +/** + * @brief Cleanup the socket system. + */ +void ssh_socket_cleanup(void) { + ssh_poll_cleanup(); +} + + +/** + * \internal + * \brief creates a new Socket object + */ +ssh_socket ssh_socket_new(ssh_session session) { + ssh_socket s; + + s = malloc(sizeof(struct ssh_socket_struct)); + if (s == NULL) { + return NULL; + } + s->fd_in = SSH_INVALID_SOCKET; + s->fd_out= SSH_INVALID_SOCKET; + s->last_errno = -1; + s->fd_is_socket = 1; + s->session = session; + s->in_buffer = ssh_buffer_new(); + if (s->in_buffer == NULL) { + SAFE_FREE(s); + return NULL; + } + s->out_buffer=ssh_buffer_new(); + if (s->out_buffer == NULL) { + ssh_buffer_free(s->in_buffer); + SAFE_FREE(s); + return NULL; + } + s->data_to_read = 0; + s->data_to_write = 0; + s->data_except = 0; + s->poll_in=s->poll_out=NULL; + s->state=SSH_SOCKET_NONE; + return s; +} + +/** + * @internal + * @brief the socket callbacks, i.e. callbacks to be called + * upon a socket event. + * @param s socket to set callbacks on. + * @param callbacks a ssh_socket_callback object reference. + */ + +void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks){ + s->callbacks=callbacks; +} + +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[4096]; + int r,w; + 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; + } + if(revents & POLLERR){ + /* Check if we are in a connecting state */ + if(s->state==SSH_SOCKET_CONNECTING){ + s->state=SSH_SOCKET_ERROR; + getsockopt(fd,SOL_SOCKET,SO_ERROR,(void *)&err,&errlen); + 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->data_to_read=1; + r=ssh_socket_unbuffered_read(s,buffer,sizeof(buffer)); + if(r<0){ + err=-1; + if(p != NULL) + ssh_poll_set_events(p,ssh_poll_get_events(p) & ~POLLIN); + if(s->callbacks && s->callbacks->exception){ + s->callbacks->exception( + SSH_SOCKET_EXCEPTION_ERROR, + s->last_errno,s->callbacks->userdata); + } + } + if(r==0){ + ssh_poll_set_events(p,ssh_poll_get_events(p) & ~POLLIN); + if(s->callbacks && s->callbacks->exception){ + s->callbacks->exception( + SSH_SOCKET_EXCEPTION_EOF, + 0,s->callbacks->userdata); + } + } + if(r>0){ + /* Bufferize the data and then call the callback */ + buffer_add_data(s->in_buffer,buffer,r); + if(s->callbacks && s->callbacks->data){ + r= s->callbacks->data(buffer_get_rest(s->in_buffer), + buffer_get_rest_len(s->in_buffer), + s->callbacks->userdata); + buffer_pass_bytes(s->in_buffer,r); + } + } + } +#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(s->session,SSH_LOG_PACKET,"Received POLLOUT in connecting state"); + s->state = SSH_SOCKET_CONNECTED; + ssh_poll_set_events(p,POLLOUT | POLLIN | POLLERR); + ssh_sock_set_blocking(ssh_socket_get_fd_in(s)); + 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->data_to_write=1; + /* If buffered data is pending, write it */ + if(buffer_get_rest_len(s->out_buffer) > 0){ + w=ssh_socket_unbuffered_write(s, buffer_get_rest(s->out_buffer), + buffer_get_rest_len(s->out_buffer)); + if(w>0) + buffer_pass_bytes(s->out_buffer,w); + } else if(s->callbacks && s->callbacks->controlflow){ + /* Otherwise advertise the upper level that write can be done */ + s->callbacks->controlflow(SSH_SOCKET_FLOW_WRITEWONTBLOCK,s->callbacks->userdata); + } + ssh_poll_remove_events(p,POLLOUT); + /* TODO: Find a way to put back POLLOUT when buffering occurs */ + } + return err; +} + +/** @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) { + return -1; + } + + if (fcntl(fd, F_SETFD, 1) == -1) { + close(fd); + return -1; + } + + if (connect(fd, (struct sockaddr *) &sunaddr, + sizeof(sunaddr)) < 0) { + 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 + closesocket(s->fd_in); + /* fd_in = fd_out under win32 */ + s->last_errno = WSAGetLastError(); +#else + close(s->fd_in); + if(s->fd_out != s->fd_in && s->fd_out != -1) + close(s->fd_out); + s->last_errno = errno; +#endif + s->fd_in = s->fd_out = SSH_INVALID_SOCKET; + } + 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; + } +} + +/** + * @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); +} + +/** + * @internal + * @brief sets the input file descriptor of the socket. + * @param[out] s ssh_socket to update + * @param[in] fd file descriptor to set + */ +void ssh_socket_set_fd_in(ssh_socket s, socket_t fd) { + s->fd_in = fd; + if(s->poll_in) + ssh_poll_set_fd(s->poll_in,fd); +} + +/** + * @internal + * @brief sets the output file descriptor of the socket. + * @param[out] s ssh_socket to update + * @param[in] fd file descriptor to set + */ +void ssh_socket_set_fd_out(ssh_socket s, socket_t fd) { + s->fd_out = fd; + if(s->poll_out) + ssh_poll_set_fd(s->poll_out,fd); +} + + + +/** \internal + * \brief returns the input file descriptor of the socket + */ +socket_t ssh_socket_get_fd_in(ssh_socket s) { + return s->fd_in; +} + +/** \internal + * \brief returns nonzero if the socket is open + */ +int ssh_socket_is_open(ssh_socket s) { + return s->fd_in != SSH_INVALID_SOCKET; +} + +/** \internal + * \brief read len bytes from socket into buffer + */ +static int ssh_socket_unbuffered_read(ssh_socket s, void *buffer, uint32_t len) { + int rc = -1; + + if (s->data_except) { + return -1; + } + if(s->fd_is_socket) + rc = recv(s->fd_in,buffer, len, 0); + else + rc = read(s->fd_in,buffer, len); +#ifdef _WIN32 + s->last_errno = WSAGetLastError(); +#else + s->last_errno = errno; +#endif + s->data_to_read = 0; + + if (rc < 0) { + s->data_except = 1; + } + + return rc; +} + +/** \internal + * \brief writes len bytes from buffer to socket + */ +static int ssh_socket_unbuffered_write(ssh_socket s, const void *buffer, + uint32_t len) { + int w = -1; + + if (s->data_except) { + return -1; + } + if (s->fd_is_socket) + w = send(s->fd_out,buffer, len, 0); + else + w = write(s->fd_out, buffer, len); +#ifdef _WIN32 + s->last_errno = WSAGetLastError(); +#else + s->last_errno = errno; +#endif + s->data_to_write = 0; + /* Reactive the POLLOUT detector in the poll multiplexer system */ + if(s->poll_out){ + ssh_log(s->session, SSH_LOG_PACKET, "Enabling POLLOUT for socket"); + ssh_poll_set_events(s->poll_out,ssh_poll_get_events(s->poll_out) | POLLOUT); + } + if (w < 0) { + s->data_except = 1; + } + + return w; +} + +/** \internal + * \brief returns nonzero if the current socket is in the fd_set + */ +int ssh_socket_fd_isset(ssh_socket s, fd_set *set) { + if(s->fd_in == SSH_INVALID_SOCKET) { + return 0; + } + return FD_ISSET(s->fd_in,set) || FD_ISSET(s->fd_out,set); +} + +/** \internal + * \brief sets the current fd in a fd_set and updates the max_fd + */ + +void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd) { + if (s->fd_in == SSH_INVALID_SOCKET) { + return; + } + + FD_SET(s->fd_in,set); + FD_SET(s->fd_out,set); + + if (s->fd_in >= 0 && s->fd_in != SSH_INVALID_SOCKET) { + *max_fd = s->fd_in + 1; + } + if (s->fd_out >= 0 && s->fd_in != SSH_INVALID_SOCKET) { + *max_fd = s->fd_out + 1; + } +} + +/** \internal + * \brief buffered write of data + * \returns SSH_OK, or SSH_ERROR + * \warning has no effect on socket before a flush + */ +int ssh_socket_write(ssh_socket s, const void *buffer, int len) { + ssh_session session = s->session; + enter_function(); + if(len > 0) { + if (buffer_add_data(s->out_buffer, buffer, len) < 0) { + return SSH_ERROR; + } + ssh_socket_set_towrite(s); + } + leave_function(); + return SSH_OK; +} + + +/** \internal + * \brief starts a nonblocking flush of the output buffer + * + */ +int ssh_socket_nonblocking_flush(ssh_socket s) { + ssh_session session = s->session; + int w; + + enter_function(); + + if (!ssh_socket_is_open(s)) { + session->alive = 0; + /* FIXME use ssh_socket_get_errno */ + ssh_set_error(session, SSH_FATAL, + "Writing packet: error on socket (or connection closed): %s", + strerror(s->last_errno)); + + leave_function(); + return SSH_ERROR; + } + + if (s->data_to_write && buffer_get_rest_len(s->out_buffer) > 0) { + w = ssh_socket_unbuffered_write(s, buffer_get_rest(s->out_buffer), + buffer_get_rest_len(s->out_buffer)); + if (w < 0) { + session->alive = 0; + ssh_socket_close(s); + /* FIXME use ssh_socket_get_errno() */ + /* FIXME use callback for errors */ + ssh_set_error(session, SSH_FATAL, + "Writing packet: error on socket (or connection closed): %s", + strerror(s->last_errno)); + leave_function(); + return SSH_ERROR; + } + buffer_pass_bytes(s->out_buffer, w); + } + + /* Is there some data pending? */ + if (buffer_get_rest_len(s->out_buffer) > 0 && s->poll_out) { + /* force the poll system to catch pollout events */ + ssh_poll_set_events(s->poll_out, ssh_poll_get_events(s->poll_out) |POLLOUT); + leave_function(); + return SSH_AGAIN; + } + + /* all data written */ + leave_function(); + return SSH_OK; +} + +void ssh_socket_set_towrite(ssh_socket s) { + s->data_to_write = 1; +} + +void ssh_socket_set_toread(ssh_socket s) { + s->data_to_read = 1; +} + +void ssh_socket_set_except(ssh_socket s) { + s->data_except = 1; +} + +int ssh_socket_data_available(ssh_socket s) { + return s->data_to_read; +} + +int ssh_socket_data_writable(ssh_socket s) { + return s->data_to_write; +} + +int ssh_socket_get_status(ssh_socket s) { + int r = 0; + + if (s->data_to_read) { + r |= SSH_READ_PENDING; + } + + if (s->data_except) { + r |= SSH_CLOSED_ERROR; + } + + return r; +} + +/** + * @internal + * @brief Launches a socket connection + * If a the socket connected callback has been defined and + * a poll object exists, this call will be non blocking. + * @param s socket to connect. + * @param host hostname or ip address to connect to. + * @param port port number to connect to. + * @param bind_addr address to bind to, or NULL for default. + * @returns SSH_OK socket is being connected. + * @returns SSH_ERROR error while connecting to remote host. + * @bug It only tries connecting to one of the available AI's + * which is problematic for hosts having DNS fail-over. + */ + +int ssh_socket_connect(ssh_socket s, const char *host, int port, const char *bind_addr){ + socket_t fd; + ssh_session session=s->session; + enter_function(); + if(s->state != SSH_SOCKET_NONE) + return SSH_ERROR; + fd=ssh_connect_host_nonblocking(s->session,host,bind_addr,port); + ssh_log(session,SSH_LOG_PROTOCOL,"Nonblocking connection socket: %d",fd); + if(fd == SSH_INVALID_SOCKET) + return SSH_ERROR; + ssh_socket_set_fd(s,fd); + 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 + leave_function(); + return SSH_OK; +} + +#ifndef _WIN32 +/** + * @internal + * @brief executes a command and redirect input and outputs + * @param command command to execute + * @param in input file descriptor + * @param out output file descriptor + */ +void ssh_execute_command(const char *command, socket_t in, socket_t out){ + const char *args[]={"/bin/sh","-c",command,NULL}; + /* redirect in and out to stdin, stdout and stderr */ + dup2(in, 0); + dup2(out,1); + dup2(out,2); + close(in); + close(out); + execve(args[0],(char * const *)args,(char * const *)environ); + exit(1); +} + +/** + * @internal + * @brief Open a socket on a ProxyCommand + * This call will always be nonblocking. + * @param s socket to connect. + * @param command Command to execute. + * @returns SSH_OK socket is being connected. + * @returns SSH_ERROR error while executing the command. + */ + +int ssh_socket_connect_proxycommand(ssh_socket s, const char *command){ + socket_t in_pipe[2]; + socket_t out_pipe[2]; + int pid; + int rc; + ssh_session session=s->session; + enter_function(); + if(s->state != SSH_SOCKET_NONE) + return SSH_ERROR; + + rc = pipe(in_pipe); + if (rc < 0) { + return SSH_ERROR; + } + rc = pipe(out_pipe); + if (rc < 0) { + return SSH_ERROR; + } + + ssh_log(session,SSH_LOG_PROTOCOL,"Executing proxycommand '%s'",command); + pid = fork(); + if(pid == 0){ + ssh_execute_command(command,out_pipe[0],in_pipe[1]); + } + close(in_pipe[1]); + close(out_pipe[0]); + ssh_log(session,SSH_LOG_PROTOCOL,"ProxyCommand connection pipe: [%d,%d]",in_pipe[0],out_pipe[1]); + ssh_socket_set_fd_in(s,in_pipe[0]); + ssh_socket_set_fd_out(s,out_pipe[1]); + s->state=SSH_SOCKET_CONNECTED; + s->fd_is_socket=0; + /* POLLOUT is the event to wait for in a nonblocking connect */ + ssh_poll_set_events(ssh_socket_get_poll_handle_in(s),POLLIN | POLLERR); + ssh_poll_set_events(ssh_socket_get_poll_handle_out(s),POLLOUT); + if(s->callbacks && s->callbacks->connected) + s->callbacks->connected(SSH_SOCKET_CONNECTED_OK,0,s->callbacks->userdata); + leave_function(); + return SSH_OK; +} + +#endif /* _WIN32 */ +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ |