aboutsummaryrefslogtreecommitdiff
path: root/libssh/packet.c
diff options
context:
space:
mode:
authorAris Adamantiadis <aris@0xbadc0de.be>2005-07-05 01:21:44 +0000
committerAris Adamantiadis <aris@0xbadc0de.be>2005-07-05 01:21:44 +0000
commitc65f56aefa50a2e2a78a0e45564526ecc921d74f (patch)
tree11bd53cf92869ccbab30f29253ce30f7078a4a26 /libssh/packet.c
downloadlibssh-c65f56aefa50a2e2a78a0e45564526ecc921d74f.tar.gz
libssh-c65f56aefa50a2e2a78a0e45564526ecc921d74f.tar.xz
libssh-c65f56aefa50a2e2a78a0e45564526ecc921d74f.zip
first import
git-svn-id: svn+ssh://svn.berlios.de/svnroot/repos/libssh/trunk@1 7dcaeef0-15fb-0310-b436-a5af3365683c
Diffstat (limited to 'libssh/packet.c')
-rw-r--r--libssh/packet.c563
1 files changed, 563 insertions, 0 deletions
diff --git a/libssh/packet.c b/libssh/packet.c
new file mode 100644
index 00000000..89132f9c
--- /dev/null
+++ b/libssh/packet.c
@@ -0,0 +1,563 @@
+/* packet.c */
+/* packet building functions */
+/*
+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 <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include "libssh/priv.h"
+#include "libssh/ssh2.h"
+#include "libssh/ssh1.h"
+#include <netdb.h>
+#include <errno.h>
+#include "libssh/crypto.h"
+
+/* XXX include selected mac size */
+static int macsize=SHA_DIGEST_LENGTH;
+
+/* completeread will read blocking until len bytes have been read */
+static int completeread(int fd, void *buffer, int len){
+ int r;
+ int total=0;
+ int toread=len;
+ while((r=read(fd,buffer+total,toread))){
+ if(r==-1)
+ return -1;
+ total += r;
+ toread-=r;
+ if(total==len)
+ return len;
+ if(r==0)
+ return 0;
+ }
+ return total ; /* connection closed */
+}
+
+static int packet_read2(SSH_SESSION *session){
+ u32 len;
+ void *packet=NULL;
+ char mac[30];
+ char buffer[16];
+ int be_read,i;
+ int to_be_read;
+ u8 padding;
+ unsigned int blocksize=(session->current_crypto?session->current_crypto->in_cipher->blocksize:8);
+ session->data_to_read=0; /* clear the dataavailable flag */
+ memset(&session->in_packet,0,sizeof(PACKET));
+ if(session->in_buffer)
+ buffer_free(session->in_buffer);
+ session->in_buffer=buffer_new();
+
+ be_read=completeread(session->fd,buffer,blocksize);
+ if(be_read!=blocksize){
+ if(be_read<=0){
+ session->alive=0;
+ close(session->fd);
+ session->fd=-1;
+ ssh_set_error(session,SSH_FATAL,
+ (be_read==0)?"Connection closed by remote host" : "Error reading socket");
+ return -1;
+ }
+ ssh_set_error(session,SSH_FATAL,"read_packet(): asked %d bytes, received %d",blocksize,be_read);
+ return -1;
+ }
+ len=packet_decrypt_len(session,buffer);
+ buffer_add_data(session->in_buffer,buffer,blocksize);
+
+ if(len> MAX_PACKET_LEN){
+ ssh_set_error(session,SSH_FATAL,"read_packet(): Packet len too high(%uld %.8lx)",len,len);
+ return -1;
+ }
+ to_be_read=len-be_read+sizeof(u32);
+ if(to_be_read<0){
+ /* remote sshd is trying to get me ?*/
+ ssh_set_error(session,SSH_FATAL,"given numbers of bytes left to be read <0 (%d)!",to_be_read);
+ return -1;
+ }
+ /* handle the case in which the whole packet size = blocksize */
+ if(to_be_read !=0){
+ packet=malloc(to_be_read);
+ i=completeread(session->fd,packet,to_be_read);
+ if(i<=0){
+ session->alive=0;
+ close(session->fd);
+ session->fd=-1;
+ ssh_set_error(session,SSH_FATAL,"Server closed connection");
+ return -1;
+ }
+ if(i!=to_be_read){
+ free(packet);
+ packet=NULL;
+ ssh_say(3,"Read only %d, wanted %d\n",i,to_be_read);
+ ssh_set_error(session,SSH_FATAL,"read_packet(): read only %d, wanted %d",i,to_be_read);
+ return -1;
+ }
+ ssh_say(3,"Read a %d bytes packet\n",len);
+ buffer_add_data(session->in_buffer,packet,to_be_read);
+ free(packet);
+ }
+ if(session->current_crypto){
+ packet_decrypt(session,buffer_get(session->in_buffer)+blocksize,buffer_get_len(session->in_buffer)-blocksize);
+ if((i=completeread(session->fd,mac,macsize))!=macsize){
+ if(i<=0){
+ session->alive=0;
+ close(session->fd);
+ session->fd=-1;
+ ssh_set_error(session,SSH_FATAL,"Server closed connection");
+ return -1;
+ }
+ ssh_set_error(session,SSH_FATAL,"read_packet(): wanted %d, had %d",i,macsize);
+ return -1;
+ }
+ if(packet_hmac_verify(session,session->in_buffer,mac)){
+ ssh_set_error(session,SSH_FATAL,"HMAC error");
+ return -1;
+ }
+ }
+ buffer_pass_bytes(session->in_buffer,sizeof(u32)); /*pass the size which has been processed before*/
+ if(!buffer_get_u8(session->in_buffer,&padding)){
+ ssh_set_error(session,SSH_FATAL,"Packet too short to read padding");
+ return -1;
+ }
+ ssh_say(3,"%hhd bytes padding\n",padding);
+ if(padding > buffer_get_rest_len(session->in_buffer)){
+ ssh_set_error(session,SSH_FATAL,"invalid padding: %d (%d resting)",padding,buffer_get_rest_len(session->in_buffer));
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("incrimined packet",buffer_get(session->in_buffer),buffer_get_len(session->in_buffer));
+#endif
+ return -1;
+ }
+ buffer_pass_bytes_end(session->in_buffer,padding);
+#ifdef HAVE_LIBZ
+ if(session->current_crypto && session->current_crypto->do_compress_in){
+ decompress_buffer(session,session->in_buffer);
+ }
+#endif
+ session->recv_seq++;
+ return 0;
+}
+
+#ifdef HAVE_SSH1
+/* a slighty modified packet_read2() for SSH-1 protocol */
+static int packet_read1(SSH_SESSION *session){
+ u32 len;
+ void *packet=NULL;
+// char buffer[16];
+ int be_read,i;
+ int to_be_read;
+ u32 padding;
+ u32 crc;
+ ssh_say(3,"packet_read1()\n");
+// unsigned int blocksize=8;
+ session->data_to_read=0; /* clear the dataavailable flag */
+ memset(&session->in_packet,0,sizeof(PACKET));
+ if(session->in_buffer)
+ buffer_free(session->in_buffer);
+ session->in_buffer=buffer_new();
+
+ be_read=completeread(session->fd,&len,sizeof(u32));
+ if(be_read!=sizeof(u32)){
+ if(be_read<=0){
+ session->alive=0;
+ close(session->fd);
+ session->fd=-1;
+ ssh_set_error(session,SSH_FATAL,
+ (be_read==0)?"Connection closed by remote host" : "Error reading socket");
+ return -1;
+ }
+ ssh_set_error(session,SSH_FATAL,"read_packet(): asked %d bytes, received %d",sizeof(u32),be_read);
+ return -1;
+ }
+ /* len is not encrypted */
+ len=ntohl(len);
+
+ if(len> MAX_PACKET_LEN){
+ ssh_set_error(session,SSH_FATAL,"read_packet(): Packet len too high(%uld %.8lx)",len,len);
+ return -1;
+ }
+ ssh_say(3,"%d bytes packet\n",len);
+ /* SSH-1 has a fixed padding lenght */
+ padding=8-(len % 8);
+ to_be_read=len+padding;
+ /* handle the case in which the whole packet size = blocksize */
+ if(to_be_read !=0){
+ packet=malloc(to_be_read);
+ i=completeread(session->fd,packet,to_be_read);
+ if(i<=0){
+ session->alive=0;
+ close(session->fd);
+ session->fd=-1;
+ ssh_set_error(session,SSH_FATAL,"Server closed connection");
+ return -1;
+ }
+ if(i!=to_be_read){
+ free(packet);
+ packet=NULL;
+ ssh_say(3,"Read only %d, wanted %d\n",i,to_be_read);
+ ssh_set_error(session,SSH_FATAL,"read_packet(): read only %d, wanted %d",i,to_be_read);
+ return -1;
+ }
+ ssh_say(3,"Read a %d bytes packet\n",len);
+ buffer_add_data(session->in_buffer,packet,to_be_read);
+ free(packet);
+ }
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("read packet:",buffer_get(session->in_buffer),
+ buffer_get_len(session->in_buffer));
+#endif
+ if(session->current_crypto){
+ /* we decrypt everything, missing the lenght part (which was previously
+ * read, unencrypted, and is not part of the buffer
+ */
+ packet_decrypt(session,buffer_get(session->in_buffer),buffer_get_len(session->in_buffer));
+ }
+#ifdef DEBUG_CRYPTO
+ssh_print_hexa("read packet decrypted:",buffer_get(session->in_buffer),buffer_get_len(session->in_buffer));
+#endif
+ ssh_say(3,"%d bytes padding\n",padding);
+ if((len+padding) != buffer_get_rest_len(session->in_buffer) || (len+padding) < sizeof(u32)){
+ ssh_say(2,"no crc32 in packet\n");
+ ssh_set_error(session,SSH_FATAL,"no crc32 in packet");
+ return -1;
+ }
+ memcpy(&crc,buffer_get_rest(session->in_buffer)+(len+padding)-sizeof(u32),
+ sizeof(u32));
+ buffer_pass_bytes_end(session->in_buffer,sizeof(u32));
+ crc=ntohl(crc);
+ if(ssh_crc32(buffer_get_rest(session->in_buffer),(len+padding)-sizeof(u32))!=crc){
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("crc32 on",buffer_get_rest(session->in_buffer),
+ len + padding - sizeof(u32));
+#endif
+ ssh_say(2,"invalid crc32\n");
+ ssh_set_error(session,SSH_FATAL,"invalid crc32 : expected %.8lx, "
+ "got %.8lx",crc,
+ ssh_crc32(buffer_get_rest(session->in_buffer),len+padding-sizeof(u32)) );
+ return -1;
+ }
+ buffer_pass_bytes(session->in_buffer,padding); /*pass the padding*/
+ ssh_say(3,"the packet is valid\n");
+/* will do that later
+#ifdef HAVE_LIBZ
+ if(session->current_crypto && session->current_crypto->do_compress_in){
+ decompress_buffer(session,session->in_buffer);
+ }
+#endif
+*/
+ session->recv_seq++;
+ return 0;
+}
+
+#endif /* HAVE_SSH1 */
+
+/* that's where i'd like C to be object ... */
+int packet_read(SSH_SESSION *session){
+#ifdef HAVE_SSH1
+ if(session->version==1)
+ return packet_read1(session);
+ else
+#endif
+ return packet_read2(session);
+}
+
+int packet_translate(SSH_SESSION *session){
+ memset(&session->in_packet,0,sizeof(PACKET));
+ if(!session->in_buffer)
+ return -1;
+ ssh_say(3,"Final size %d\n",buffer_get_rest_len(session->in_buffer));
+ if(!buffer_get_u8(session->in_buffer,&session->in_packet.type)){
+ ssh_set_error(session,SSH_FATAL,"Packet too short to read type");
+ return -1;
+ }
+ ssh_say(3,"type %hhd\n",session->in_packet.type);
+ session->in_packet.valid=1;
+ return 0;
+}
+
+static int atomic_write(int fd, void *buffer, int len){
+ int written;
+ int total=0;
+ do {
+ written=write(fd,buffer,len);
+ if(written==0)
+ return 0;
+ if(written==-1)
+ return total;
+ total+=written;
+ len-=written;
+ buffer+=written;
+ } while (len > 0);
+ return total;
+}
+
+static int packet_send2(SSH_SESSION *session){
+ char padstring[32];
+ u32 finallen;
+ u8 padding;
+ u32 currentlen=buffer_get_len(session->out_buffer);
+ char *hmac;
+ int ret=0;
+ unsigned int blocksize=(session->current_crypto?session->current_crypto->out_cipher->blocksize:8);
+ ssh_say(3,"Writing on the wire a packet having %ld bytes before",currentlen);
+#ifdef HAVE_LIBZ
+ if(session->current_crypto && session->current_crypto->do_compress_out){
+ compress_buffer(session,session->out_buffer);
+ currentlen=buffer_get_len(session->out_buffer);
+ }
+#endif
+ padding=(blocksize- ((currentlen+5) % blocksize));
+ if(padding<4)
+ padding+=blocksize;
+ if(session->current_crypto)
+ ssh_get_random(padstring,padding);
+ else
+ memset(padstring,0,padding);
+ finallen=htonl(currentlen+padding+1);
+ ssh_say(3,",%d bytes after comp + %d padding bytes = %d bytes packet\n",currentlen,padding,(ntohl(finallen)));
+ buffer_add_data_begin(session->out_buffer,&padding,sizeof(u8));
+ buffer_add_data_begin(session->out_buffer,&finallen,sizeof(u32));
+ buffer_add_data(session->out_buffer,padstring,padding);
+ hmac=packet_encrypt(session,buffer_get(session->out_buffer),buffer_get_len(session->out_buffer));
+ if(hmac)
+ buffer_add_data(session->out_buffer,hmac,20);
+ if(atomic_write(session->fd,buffer_get(session->out_buffer),buffer_get_len(session->out_buffer))!=buffer_get_len(session->out_buffer)){
+ session->alive=0;
+ close(session->fd);
+ session->fd=-1;
+ ssh_set_error(session,SSH_FATAL,"Writing packet : error on socket (or connection closed): %s",
+ strerror(errno));
+ ret=-1;
+ }
+ session->send_seq++;
+ buffer_reinit(session->out_buffer);
+ return ret;
+}
+
+#ifdef HAVE_SSH1
+static int packet_send1(SSH_SESSION *session){
+ char padstring[32];
+ u32 finallen;
+ u8 padding;
+ u32 crc;
+ u32 currentlen=buffer_get_len(session->out_buffer)+sizeof(u32);
+ int ret=0;
+ unsigned int blocksize=(session->current_crypto?session->current_crypto->out_cipher->blocksize:8);
+ ssh_say(3,"Writing on the wire a packet having %ld bytes before",currentlen);
+/*
+#ifdef HAVE_LIBZ
+ if(session->current_crypto && session->current_crypto->do_compress_out){
+ compress_buffer(session,session->out_buffer);
+ currentlen=buffer_get_len(session->out_buffer);
+ }
+#endif
+*/
+ padding=blocksize-(currentlen % blocksize);
+ if(session->current_crypto)
+ ssh_get_random(padstring,padding);
+ else
+ memset(padstring,0,padding);
+ finallen=htonl(currentlen);
+ ssh_say(3,",%d bytes after comp + %d padding bytes = %d bytes packet\n",currentlen,padding,(ntohl(finallen)));
+ buffer_add_data_begin(session->out_buffer,&padstring,padding);
+ buffer_add_data_begin(session->out_buffer,&finallen,sizeof(u32));
+ crc=ssh_crc32(buffer_get(session->out_buffer)+sizeof(u32),buffer_get_len(session->out_buffer)-sizeof(u32));
+ buffer_add_u32(session->out_buffer,ntohl(crc));
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("clear packet",buffer_get(session->out_buffer),
+ buffer_get_len(session->out_buffer));
+#endif
+ packet_encrypt(session,buffer_get(session->out_buffer)+sizeof(u32),buffer_get_len(session->out_buffer)-sizeof(u32));
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("encrypted packet",buffer_get(session->out_buffer),
+ buffer_get_len(session->out_buffer));
+#endif
+ if(atomic_write(session->fd,buffer_get(session->out_buffer),buffer_get_len(session->out_buffer))!=buffer_get_len(session->out_buffer)){
+ session->alive=0;
+ close(session->fd);
+ session->fd=-1;
+ ssh_set_error(session,SSH_FATAL,"Writing packet : error on socket (or connection closed): %s",
+ strerror(errno));
+ ret=-1;
+ }
+ session->send_seq++;
+ buffer_reinit(session->out_buffer);
+ return ret;
+}
+
+#endif /* HAVE_SSH1 */
+
+int packet_send(SSH_SESSION *session){
+#ifdef HAVE_SSH1
+ if (session->version==1)
+ return packet_send1(session);
+ else
+#endif
+ return packet_send2(session);
+}
+
+void packet_parse(SSH_SESSION *session){
+ int type=session->in_packet.type;
+ u32 foo;
+ STRING *error_s;
+ char *error=NULL;
+#ifdef HAVE_SSH1
+ if(session->version==1){
+ /* SSH-1 */
+ switch(type){
+ case SSH_MSG_DISCONNECT:
+ ssh_say(2,"Received SSH_MSG_DISCONNECT\n");
+ ssh_set_error(session,SSH_FATAL,"Received SSH_MSG_DISCONNECT");
+ close(session->fd);
+ session->fd=-1;
+ session->alive=0;
+ return;
+ case SSH_SMSG_STDOUT_DATA:
+ case SSH_SMSG_STDERR_DATA:
+ case SSH_SMSG_EXITSTATUS:
+ channel_handle1(session,type);
+ return;
+ default:
+ ssh_say(2,"Unexpected message code %d\n",type);
+ }
+ return;
+ } else {
+#endif /* HAVE_SSH1 */
+ switch(type){
+ case SSH2_MSG_DISCONNECT:
+ buffer_get_u32(session->in_buffer,&foo);
+ error_s=buffer_get_ssh_string(session->in_buffer);
+ if(error_s)
+ error=string_to_char(error_s);
+ ssh_say(2,"Received SSH_MSG_DISCONNECT\n");
+ ssh_set_error(session,SSH_FATAL,"Received SSH_MSG_DISCONNECT : %s",error);
+ if(error_s){
+ free(error_s);
+ free(error);
+ }
+ close(session->fd);
+ session->fd=-1;
+ session->alive=0;
+ return;
+ case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+ case SSH2_MSG_CHANNEL_DATA:
+ case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+ case SSH2_MSG_CHANNEL_REQUEST:
+ case SSH2_MSG_CHANNEL_EOF:
+ case SSH2_MSG_CHANNEL_CLOSE:
+
+ channel_handle(session,type);
+ case SSH2_MSG_IGNORE:
+ return;
+ default:
+ ssh_say(0,"Received unhandled msg %d\n",type);
+ }
+#ifdef HAVE_SSH1
+ }
+#endif
+}
+
+#ifdef HAVE_SSH1
+static int packet_wait1(SSH_SESSION *session,int type,int blocking){
+ ssh_say(3,"packet_wait1 waiting for %d\n",type);
+ while(1){
+ if(packet_read1(session))
+ return -1;
+ if(packet_translate(session))
+ return -1;
+ ssh_say(3,"packet_wait 1 received %d\n",session->in_packet.type);
+ switch(session->in_packet.type){
+ case SSH_MSG_DISCONNECT:
+ packet_parse(session);
+ return -1;
+ case SSH_SMSG_STDOUT_DATA:
+ case SSH_SMSG_STDERR_DATA:
+ case SSH_SMSG_EXITSTATUS:
+ channel_handle1(session,type);
+ break;
+/* case SSH2_MSG_CHANNEL_CLOSE:
+ packet_parse(session);
+ break;;
+ case SSH2_MSG_IGNORE:
+ break;
+*/
+ default:
+ if(type && (type != session->in_packet.type)){
+ ssh_set_error(session,SSH_FATAL,"waitpacket(): Received a %d type packet, was waiting for a %d\n",session->in_packet.type,type);
+ return -1;
+ }
+ return 0;
+ }
+ if(blocking==0)
+ return 0;
+ }
+ return 0;
+}
+#endif /* HAVE_SSH1 */
+static int packet_wait2(SSH_SESSION *session,int type,int blocking){
+ while(1){
+ if(packet_read2(session))
+ return -1;
+ if(packet_translate(session))
+ return -1;
+ switch(session->in_packet.type){
+ case SSH2_MSG_DISCONNECT:
+ packet_parse(session);
+ return -1;
+ case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+ case SSH2_MSG_CHANNEL_DATA:
+ case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+ case SSH2_MSG_CHANNEL_REQUEST:
+ case SSH2_MSG_CHANNEL_EOF:
+ case SSH2_MSG_CHANNEL_CLOSE:
+ packet_parse(session);
+ break;;
+ case SSH2_MSG_IGNORE:
+ break;
+ default:
+ if(type && (type != session->in_packet.type)){
+ ssh_set_error(session,SSH_FATAL,"waitpacket(): Received a %d type packet, was waiting for a %d\n",session->in_packet.type,type);
+ return -1;
+ }
+ return 0;
+ }
+ if(blocking==0)
+ return 0;
+ }
+ return 0;
+}
+int packet_wait(SSH_SESSION *session, int type, int block){
+#ifdef HAVE_SSH1
+ if(session->version==1)
+ return packet_wait1(session,type,block);
+ else
+#endif
+ return packet_wait2(session,type,block);
+}
+
+
+void packet_clear_out(SSH_SESSION *session){
+ if(session->out_buffer)
+ buffer_reinit(session->out_buffer);
+ else
+ session->out_buffer=buffer_new();
+}
+