diff options
Diffstat (limited to 'libssh/channels.c')
-rw-r--r-- | libssh/channels.c | 701 |
1 files changed, 701 insertions, 0 deletions
diff --git a/libssh/channels.c b/libssh/channels.c new file mode 100644 index 0000000..04eeaa9 --- /dev/null +++ b/libssh/channels.c @@ -0,0 +1,701 @@ +/* channels.c */ +/* It has support for ... ssh channels */ +/* +Copyright 2003 Aris Adamantiadis + +This file is part of the SSH Library + +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 <string.h> +#include <stdlib.h> +#include <netdb.h> +#include <unistd.h> +#include <stdio.h> + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#define WINDOWLIMIT 1024 +#define WINDOWBASE 32000 + +CHANNEL *channel_new(SSH_SESSION *session){ + CHANNEL *channel=malloc(sizeof(CHANNEL)); + memset(channel,0,sizeof(CHANNEL)); + channel->session=session; + channel->version=session->version; + channel->stdout_buffer=buffer_new(); + channel->stderr_buffer=buffer_new(); + if(!session->channels){ + session->channels=channel; + channel->next=channel->prev=channel; + return channel; + } + channel->next=session->channels; + channel->prev=session->channels->prev; + channel->next->prev=channel; + channel->prev->next=channel; + return channel; +} + +static u32 channel_new_id(SSH_SESSION *session){ + u32 ret=session->maxchannel; + session->maxchannel++; + return ret; +} + +static int channel_open(CHANNEL *channel,char *type_c,int window, +int maxpacket,BUFFER *payload){ + SSH_SESSION *session=channel->session; + STRING *type=string_from_char(type_c); + u32 foo; + int err; + packet_clear_out(session); + buffer_add_u8(session->out_buffer,SSH2_MSG_CHANNEL_OPEN); + channel->local_channel=channel_new_id(session); + channel->local_maxpacket=maxpacket; + channel->local_window=window; + ssh_say(2,"creating a channel %d with %d window and %d max packet\n", + channel->local_channel, window,maxpacket); + buffer_add_ssh_string(session->out_buffer,type); + buffer_add_u32(session->out_buffer,htonl(channel->local_channel)); + buffer_add_u32(session->out_buffer,htonl(channel->local_window)); + buffer_add_u32(session->out_buffer,htonl(channel->local_maxpacket)); + free(type); + if(payload) + buffer_add_buffer(session->out_buffer,payload); + packet_send(session); + ssh_say(2,"Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %d\n",type_c,channel->local_channel); + err=packet_wait(session,SSH2_MSG_CHANNEL_OPEN_CONFIRMATION,1); + switch(session->in_packet.type){ + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + buffer_get_u32(session->in_buffer,&foo); + if(channel->local_channel!=ntohl(foo)){ + ssh_set_error(session,SSH_FATAL,"server answered with sender chan num %d instead of given %d", + ntohl(foo),channel->local_channel); + return -1; + } + buffer_get_u32(session->in_buffer,&foo); + channel->remote_channel=ntohl(foo); + buffer_get_u32(session->in_buffer,&foo); + channel->remote_window=ntohl(foo); + buffer_get_u32(session->in_buffer,&foo); + channel->remote_maxpacket=ntohl(foo); + ssh_say(3,"Received a CHANNEL_OPEN_CONFIRMATION for channel %d:%d\n" + ,channel->local_channel,channel->remote_channel); + ssh_say(3,"Remote window : %ld, maxpacket : %ld\n", + channel->remote_window, channel->remote_maxpacket); + channel->open=1; + return 0; + case SSH2_MSG_CHANNEL_OPEN_FAILURE: + { + u32 code; + STRING *error_s; + char *error; + buffer_get_u32(session->in_buffer,&foo); + buffer_get_u32(session->in_buffer,&code); + error_s=buffer_get_ssh_string(session->in_buffer); + error=string_to_char(error_s); + ssh_set_error(session,SSH_REQUEST_DENIED,"Channel opening failure : channel %d error (%d) %s", + channel->local_channel,ntohl(code),error); + free(error); + free(error_s); + return -1; + } + default: + ssh_set_error(session,SSH_FATAL,"Received unknown packet %d\n",session->in_packet.type); + return -1; + } + return -1; +} + +static CHANNEL *find_local_channel(SSH_SESSION *session,u32 num){ + // we assume we are always the local + CHANNEL *initchan,*channel; + initchan=session->channels; + if(!initchan) + return NULL; + for(channel=initchan;channel->local_channel!=num;channel=channel->next){ + if(channel->next==initchan) + return NULL; + } + return channel; +} + +static void grow_window(SSH_SESSION *session, CHANNEL *channel){ + u32 new_window=WINDOWBASE; + packet_clear_out(session); + buffer_add_u8(session->out_buffer,SSH2_MSG_CHANNEL_WINDOW_ADJUST); + buffer_add_u32(session->out_buffer,htonl(channel->remote_channel)); + buffer_add_u32(session->out_buffer,htonl(new_window)); + packet_send(session); + ssh_say(3,"growing window (channel %d:%d) to %d bytes\n", + channel->local_channel,channel->remote_channel, + channel->local_window + new_window); + channel->local_window+=new_window; +} + +static CHANNEL *channel_from_msg(SSH_SESSION *session){ + u32 chan; + CHANNEL *channel; + if (buffer_get_u32(session->in_buffer,&chan)!=sizeof(u32)){ + ssh_set_error(session,SSH_FATAL,"Getting channel from message : short read"); + return NULL; + } + channel=find_local_channel(session,ntohl(chan)); + if(!channel) + ssh_set_error(session,SSH_FATAL,"Server specified invalid channel %d",ntohl(chan)); + return channel; +} + +static void channel_rcv_change_window(SSH_SESSION *session){ + u32 bytes; + CHANNEL *channel; + int err; + channel=channel_from_msg(session); + if(!channel) + ssh_say(0,"%s\n",ssh_get_error(session)); + err = buffer_get_u32(session->in_buffer,&bytes); + if(!channel || err!= sizeof(u32)){ + ssh_say(1,"Error getting a window adjust message : invalid packet\n"); + return; + } + bytes=ntohl(bytes); + ssh_say(3,"Adding %d bytes to channel (%d:%d) (from %d bytes)\n",bytes, + channel->local_channel,channel->remote_channel,channel->remote_window); + channel->remote_window+=bytes; +} + +/* is_stderr is set to 1 if the data are extended, ie stderr */ +static void channel_rcv_data(SSH_SESSION *session,int is_stderr){ + STRING *str; + CHANNEL *channel; + channel=channel_from_msg(session); + if(!channel){ + ssh_say(0,"%s",ssh_get_error(session)); + return; + } + if(is_stderr){ + u32 ignore; + /* uint32 data type code. we can ignore it */ + buffer_get_u32(session->in_buffer,&ignore); + } + str=buffer_get_ssh_string(session->in_buffer); + + if(!str){ + ssh_say(0,"Invalid data packet !\n"); + return; + } + ssh_say(3,"adding %d bytes data in %d\n",string_len(str),is_stderr); + /* what shall we do in this case ? let's accept it anyway */ + if(string_len(str)>channel->local_window) + ssh_say(0,"Data packet too big for our window(%d vs %d)",string_len(str),channel->local_window); + channel_default_bufferize(channel,str->string,string_len(str), is_stderr); + if(string_len(str)>=channel->local_window) + channel->local_window-=string_len(str); + else + channel->local_window=0; /* buggy remote */ + if(channel->local_window < WINDOWLIMIT) + grow_window(session,channel); /* i wonder if this is the correct place to do that */ + free(str); +} + +static void channel_rcv_eof(SSH_SESSION *session){ + CHANNEL *channel; + channel=channel_from_msg(session); + if(!channel){ + ssh_say(0,"%s\n",ssh_get_error(session)); + return; + } + ssh_say(2,"Received eof on channel (%d:%d)\n",channel->local_channel, + channel->remote_channel); +// channel->remote_window=0; + channel->remote_eof=1; +} + +static void channel_rcv_close(SSH_SESSION *session){ + CHANNEL *channel; + channel=channel_from_msg(session); + if(!channel){ + ssh_say(0,"%s\n",ssh_get_error(session)); + return; + } + ssh_say(2,"Received close on channel (%d:%d)\n",channel->local_channel, + channel->remote_channel); + if((channel->stdout_buffer && buffer_get_rest_len(channel->stdout_buffer)>0) + || (channel->stderr_buffer && buffer_get_rest_len(channel->stderr_buffer)>0)) + channel->delayed_close=1; + else + channel->open=0; + if(!channel->remote_eof) + ssh_say(2,"Remote host not polite enough to send an eof before close\n"); + channel->remote_eof=1; + /* the remote eof doesn't break things if there was still data into read + * buffer because the eof is ignored until the buffer is empty. */ +} + +static void channel_rcv_request(SSH_SESSION *session){ + STRING *request_s; + char *request; + u32 status; + CHANNEL *channel=channel_from_msg(session); + if(!channel){ + ssh_say(1,"%s\n",ssh_get_error(session)); + return; + } + request_s=buffer_get_ssh_string(session->in_buffer); + if(!request_s){ + ssh_say(0,"Invalid MSG_CHANNEL_REQUEST\n"); + return; + } + buffer_get_u8(session->in_buffer,(u8 *)&status); + request=string_to_char(request_s); + if(!strcmp(request,"exit-status")){ + buffer_get_u32(session->in_buffer,&status); + status=ntohl(status); +/* XXX do something with status, we might need it */ + free(request_s); + free(request); + return ; + } + if(!strcmp(request,"exit-signal")){ + STRING *signal_s; + char *signal; + char *core="(core dumped)"; + u8 i; + signal_s=buffer_get_ssh_string(session->in_buffer); + if(!signal_s){ + ssh_say(0,"Invalid MSG_CHANNEL_REQUEST\n"); + free(request_s); + free(request); + return; + } + signal=string_to_char(signal_s); + buffer_get_u8(session->in_buffer,&i); + if(!i) + core=""; + ssh_say(0,"Remote connection closed by signal SIG%s %s\n",signal,core); + free(signal_s); + free(signal); + free(request_s); + free(request); + return; + } + ssh_say(0,"Unknown request %s\n",request); + free(request_s); + free(request); +} + +/* channel_handle is called by wait_packet, ie, when there is channel informations to handle . */ +void channel_handle(SSH_SESSION *session, int type){ + ssh_say(3,"Channel_handle(%d)\n",type); + switch(type){ + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + channel_rcv_change_window(session); + break; + case SSH2_MSG_CHANNEL_DATA: + channel_rcv_data(session,0); + break; + case SSH2_MSG_CHANNEL_EXTENDED_DATA: + channel_rcv_data(session,1); + break; + case SSH2_MSG_CHANNEL_EOF: + channel_rcv_eof(session); + break; + case SSH2_MSG_CHANNEL_CLOSE: + channel_rcv_close(session); + break; + case SSH2_MSG_CHANNEL_REQUEST: + channel_rcv_request(session); + break; + default: + ssh_say(0,"Unexpected message %d\n",type); + } +} + +/* when data has been received from the ssh server, it can be applied to the known + user function, with help of the callback, or inserted here */ +/* XXX is the window changed ? */ +void channel_default_bufferize(CHANNEL *channel, void *data, int len, int is_stderr){ + ssh_say(3,"placing %d bytes into channel buffer (stderr=%d)\n",len,is_stderr); + if(!is_stderr){ + /* stdout */ + if(!channel->stdout_buffer) + channel->stdout_buffer=buffer_new(); + buffer_add_data(channel->stdout_buffer,data,len); + } else { + /* stderr */ + if(!channel->stderr_buffer) + channel->stderr_buffer=buffer_new(); + buffer_add_data(channel->stderr_buffer,data,len); + } +} + +int channel_open_session(CHANNEL *channel){ +#ifdef HAVE_SSH1 + if(channel->session->version==2) +#endif + return channel_open(channel,"session",64000,32000,NULL); +#ifdef HAVE_SSH1 + else + return channel_open_session1(channel); +#endif +} + +/* tcpip forwarding */ + +int channel_open_forward(CHANNEL *channel,char *remotehost, int remoteport, char *sourcehost, int localport){ + BUFFER *payload=buffer_new(); + STRING *str=string_from_char(remotehost); + int ret; + buffer_add_ssh_string(payload,str); + free(str); + str=string_from_char(sourcehost); + buffer_add_u32(payload,htonl(remoteport)); + buffer_add_ssh_string(payload,str); + free(str); + buffer_add_u32(payload,htonl(localport)); + ret=channel_open(channel,"direct-tcpip",64000,32000,payload); + buffer_free(payload); + return ret; +} + + +void channel_free(CHANNEL *channel){ + SSH_SESSION *session=channel->session; + if(session->alive && channel->open) + channel_close(channel); + /* handle the "my channel is first on session list" case */ + if(session->channels==channel) + session->channels=channel->next; + /* handle the "my channel is the only on session list" case */ + if(channel->next == channel){ + session->channels=NULL; + } else { + channel->prev->next=channel->next; + channel->next->prev=channel->prev; + } + if(channel->stdout_buffer) + buffer_free(channel->stdout_buffer); + if(channel->stderr_buffer) + buffer_free(channel->stderr_buffer); + /* debug trick to catch use after frees */ + memset(channel,'X',sizeof(CHANNEL)); + free(channel); +} + +int channel_send_eof(CHANNEL *channel){ + SSH_SESSION *session=channel->session; + int ret; + packet_clear_out(session); + buffer_add_u8(session->out_buffer,SSH2_MSG_CHANNEL_EOF); + buffer_add_u32(session->out_buffer,htonl(channel->remote_channel)); + ret=packet_send(session); + ssh_say(1,"Sent a EOF on client channel (%d:%d)\n",channel->local_channel, + channel->remote_channel); + channel->local_eof=1; + return ret; +} + +int channel_close(CHANNEL *channel){ + SSH_SESSION *session=channel->session; + int ret=0; + if(!channel->local_eof) + ret=channel_send_eof(channel); + if(ret) + return ret; + packet_clear_out(session); + buffer_add_u8(session->out_buffer,SSH2_MSG_CHANNEL_CLOSE); + buffer_add_u32(session->out_buffer,htonl(channel->remote_channel)); + ret=packet_send(session); + ssh_say(1,"Sent a close on client channel (%d:%d)\n",channel->local_channel, + channel->remote_channel); + if(!ret) + channel->open =0; + return ret; +} + +/* Blocking write */ +/* The exact len is written */ +int channel_write(CHANNEL *channel ,void *data,int len){ + SSH_SESSION *session=channel->session; + int effectivelen; + int origlen=len; + if(channel->local_eof){ + ssh_set_error(session,SSH_REQUEST_DENIED,"Can't write to channel %d:%d" + " after EOF was sent",channel->local_channel,channel->remote_channel); + return -1; + } + if(!channel->open || channel->delayed_close){ + ssh_set_error(session,SSH_REQUEST_DENIED,"Remote channel is closed"); + return -1; + } +#ifdef HAVE_SSH1 + if(channel->version==1) + return channel_write1(channel,data,len); +#endif + while(len >0){ + if(channel->remote_window<len){ + ssh_say(2,"Remote window is %d bytes. going to write %d bytes\n", + channel->remote_window,len); + ssh_say(2,"Waiting for a growing window message...\n"); + // wonder what happens when the channel window is zero + while(channel->remote_window==0){ + // parse every incoming packet + packet_wait(channel->session,0,0); + } + effectivelen=len>channel->remote_window?channel->remote_window:len; + } else + effectivelen=len; + packet_clear_out(session); + buffer_add_u8(session->out_buffer,SSH2_MSG_CHANNEL_DATA); + buffer_add_u32(session->out_buffer,htonl(channel->remote_channel)); + buffer_add_u32(session->out_buffer,htonl(effectivelen)); + buffer_add_data(session->out_buffer,data,effectivelen); + packet_send(session); + ssh_say(2,"channel_write wrote %d bytes\n",effectivelen); + channel->remote_window-=effectivelen; + len -= effectivelen; + data+=effectivelen; + } + return origlen; +} + +int channel_is_open(CHANNEL *channel){ + return (channel->open!=0); +} + +int channel_is_eof(CHANNEL *channel){ + if((channel->stdout_buffer && buffer_get_rest_len(channel->stdout_buffer) + >0) || (channel->stderr_buffer && buffer_get_rest_len( + channel->stderr_buffer)>0)) + return 0; + return (channel->remote_eof!=0); +} + +void channel_set_blocking(CHANNEL *channel, int blocking){ + channel->blocking=blocking; +} + +static int channel_request(CHANNEL *channel,char *request, BUFFER *buffer,int reply){ + STRING *request_s=string_from_char(request); + SSH_SESSION *session=channel->session; + int err; + packet_clear_out(session); + buffer_add_u8(session->out_buffer,SSH2_MSG_CHANNEL_REQUEST); + buffer_add_u32(session->out_buffer,htonl(channel->remote_channel)); + buffer_add_ssh_string(session->out_buffer,request_s); + buffer_add_u8(session->out_buffer,reply?1:0); + if(buffer) + buffer_add_data(session->out_buffer,buffer_get(buffer),buffer_get_len(buffer)); + packet_send(session); + ssh_say(3,"Sent a SSH_MSG_CHANNEL_REQUEST %s\n",request); + free(request_s); + if(!reply) + return 0; + err=packet_wait(session,SSH2_MSG_CHANNEL_SUCCESS,1); + if(err) + if(session->in_packet.type==SSH2_MSG_CHANNEL_FAILURE){ + ssh_say(2,"%s channel request failed\n",request); + ssh_set_error(session,SSH_REQUEST_DENIED,"Channel request %s failed",request); + } + else + ssh_say(3,"Received an unexpected %d message\n",session->in_packet.type); + else + ssh_say(3,"Received a SUCCESS\n"); + return err; +} + +int channel_request_pty_size(CHANNEL *channel, char *terminal, int col, int row){ + STRING *term; + BUFFER *buffer; + int err; +#ifdef HAVE_SSH1 + if(channel->version==1) + return channel_request_pty_size1(channel,terminal, col, row); +#endif + term=string_from_char(terminal); + buffer=buffer_new(); + buffer_add_ssh_string(buffer,term); + buffer_add_u32(buffer,htonl(col)); + buffer_add_u32(buffer,htonl(row)); + buffer_add_u32(buffer,0); + buffer_add_u32(buffer,0); +/* a 0byte string */ + buffer_add_u32(buffer,htonl(1)); + buffer_add_u8(buffer,0); + free(term); + err=channel_request(channel,"pty-req",buffer,1); + buffer_free(buffer); + return err; +} + +int channel_request_pty(CHANNEL *channel){ + return channel_request_pty_size(channel,"xterm",80,24); +} + +int channel_change_pty_size(CHANNEL *channel,int cols,int rows){ + BUFFER *buffer; + int err; +#ifdef HAVE_SSH1 + if(channel->version==1) + return channel_change_pty_size1(channel,cols,rows); +#endif + buffer=buffer_new(); + //buffer_add_u8(buffer,0); + buffer_add_u32(buffer,htonl(cols)); + buffer_add_u32(buffer,htonl(rows)); + buffer_add_u32(buffer,0); + buffer_add_u32(buffer,0); + err=channel_request(channel,"window-change",buffer,0); + buffer_free(buffer); + return err; +} + +int channel_request_shell(CHANNEL *channel){ +#ifdef HAVE_SSH1 + if(channel->version==1) + return channel_request_shell1(channel); +#endif + return channel_request(channel,"shell",NULL,1); +} + +int channel_request_subsystem(CHANNEL *channel, char *system){ + BUFFER* buffer=buffer_new(); + int ret; + STRING *subsystem=string_from_char(system); + buffer_add_ssh_string(buffer,subsystem); + free(subsystem); + ret=channel_request(channel,"subsystem",buffer,1); + buffer_free(buffer); + return ret; +} + +int channel_request_sftp( CHANNEL *channel){ + return channel_request_subsystem(channel, "sftp"); +} + + +int channel_request_env(CHANNEL *channel,char *name, char *value){ + BUFFER *buffer=buffer_new(); + int ret; + STRING *string=string_from_char(name); + buffer_add_ssh_string(buffer,string); + free(string); + string=string_from_char(value); + buffer_add_ssh_string(buffer,string); + free(string); + ret=channel_request(channel,"env",buffer,1); + buffer_free(buffer); + return ret; +} + +int channel_request_exec(CHANNEL *channel, char *cmd){ + BUFFER *buffer; + int ret; +#ifdef HAVE_SSH1 + if(channel->version==1) + return channel_request_exec1(channel, cmd); +#endif + buffer=buffer_new(); + STRING *command=string_from_char(cmd); + buffer_add_ssh_string(buffer,command); + free(command); + ret=channel_request(channel,"exec",buffer,1); + buffer_free(buffer); + return ret; +} + +/* TODO : fix the delayed close thing */ +/* TODO : fix the blocking behaviours */ +/* reads into a channel and put result into buffer */ +/* returns number of bytes read, 0 if eof or such and -1 in case of error */ +/* if bytes != 0, the exact number of bytes are going to be read */ + +int channel_read(CHANNEL *channel, BUFFER *buffer,int bytes,int is_stderr){ + BUFFER *stdbuf=NULL; + int len; + buffer_reinit(buffer); + /* maybe i should always set a buffer to avoid races between channel_default_bufferize and channel_read */ + if(is_stderr) + stdbuf=channel->stderr_buffer; + else + stdbuf=channel->stdout_buffer; + + /* block reading if asked bytes=0 */ + while((buffer_get_rest_len(stdbuf)==0) || (buffer_get_rest_len(stdbuf) < bytes)){ + if(channel->remote_eof && buffer_get_rest_len(stdbuf)==0) + return 0; + if(channel->remote_eof) + break; /* return the resting bytes in buffer */ + if(packet_read(channel->session)||packet_translate(channel->session)) + return -1; + packet_parse(channel->session); + } + + if(bytes==0){ + /* write the ful buffer informations */ + buffer_add_data(buffer,buffer_get_rest(stdbuf),buffer_get_rest_len(stdbuf)); + buffer_reinit(stdbuf); + } else { + len=buffer_get_rest_len(stdbuf); + len= (len>bytes?bytes:len); /* read bytes bytes if len is greater, everything otherwise */ + buffer_add_data(buffer,buffer_get_rest(stdbuf),len); + buffer_pass_bytes(stdbuf,len); + } + return buffer_get_len(buffer); +} + +/* returns the number of bytes available, 0 if nothing is currently available, -1 if error */ +int channel_poll(CHANNEL *channel, int is_stderr){ + BUFFER *buffer; + if(is_stderr) + buffer=channel->stderr_buffer; + else + buffer=channel->stdout_buffer; + + while(buffer_get_rest_len(buffer)==0 && !channel->remote_eof){ + if(ssh_fd_poll(channel->session)){ + if(packet_read(channel->session)||packet_translate(channel->session)) + return -1; + packet_parse(channel->session); + } else + return 0; /* nothing is available has said fd_poll */ + } + return buffer_get_len(buffer); +} + +/* nonblocking read on the specified channel. it will return <=len bytes of data read + atomicly. */ +int channel_read_nonblocking(CHANNEL *channel, char *dest, int len, int is_stderr){ + int to_read=channel_poll(channel,is_stderr); + int lu; + BUFFER *buffer=buffer_new(); + if(to_read<=0){ + buffer_free(buffer); + return to_read; /* may be an error code */ + } + if(to_read>len) + to_read=len; + lu=channel_read(channel,buffer,to_read,is_stderr); + memcpy(dest,buffer_get(buffer),lu>=0?lu:0); + buffer_free(buffer); + return lu; +} + +SSH_SESSION *channel_get_session(CHANNEL *channel){ + return channel->session; +} + |