diff options
Diffstat (limited to 'src/pcap.c')
-rw-r--r-- | src/pcap.c | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/src/pcap.c b/src/pcap.c new file mode 100644 index 00000000..56bf3316 --- /dev/null +++ b/src/pcap.c @@ -0,0 +1,434 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 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. + */ + +/* pcap.c */ +#include "config.h" +#ifdef WITH_PCAP + +#include <stdio.h> +#ifdef _WIN32 +#include <ws2tcpip.h> +#else +#include <sys/time.h> +#include <sys/socket.h> +#endif +#include <errno.h> + +#include "libssh/libssh.h" +#include "libssh/pcap.h" +#include "libssh/session.h" +#include "libssh/buffer.h" +#include "libssh/socket.h" + +/** + * @internal + * + * @defgroup libssh_pcap The libssh pcap functions + * @ingroup libssh + * + * The pcap file generation + * + * + * @{ + */ + +/* The header of a pcap file is the following. We are not going to make it + * very complicated. + * Just for information. + */ +struct pcap_hdr_s { + uint32_t magic_number; /* magic number */ + uint16_t version_major; /* major version number */ + uint16_t version_minor; /* minor version number */ + int32_t thiszone; /* GMT to local correction */ + uint32_t sigfigs; /* accuracy of timestamps */ + uint32_t snaplen; /* max length of captured packets, in octets */ + uint32_t network; /* data link type */ +}; + +#define PCAP_MAGIC 0xa1b2c3d4 +#define PCAP_VERSION_MAJOR 2 +#define PCAP_VERSION_MINOR 4 + +#define DLT_RAW 12 /* raw IP */ + +/* TCP flags */ +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PUSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 + +/* The header of a pcap packet. + * Just for information. + */ +struct pcaprec_hdr_s { + uint32_t ts_sec; /* timestamp seconds */ + uint32_t ts_usec; /* timestamp microseconds */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +}; + +/** @private + * @brief a pcap context expresses the state of a pcap dump + * in a SSH session only. Multiple pcap contexts may be used into + * a single pcap file. + */ + +struct ssh_pcap_context_struct { + ssh_session session; + ssh_pcap_file file; + int connected; + /* All of these information are useful to generate + * the dummy IP and TCP packets + */ + uint32_t ipsource; + uint32_t ipdest; + uint16_t portsource; + uint16_t portdest; + uint32_t outsequence; + uint32_t insequence; +}; + +/** @private + * @brief a pcap file expresses the state of a pcap file which may + * contain several streams. + */ +struct ssh_pcap_file_struct { + FILE *output; + uint16_t ipsequence; +}; + +/** + * @brief create a new ssh_pcap_file object + */ +ssh_pcap_file ssh_pcap_file_new(){ + struct ssh_pcap_file_struct *pcap; + + pcap = malloc(sizeof(struct ssh_pcap_file_struct)); + if (pcap == NULL) { + return NULL; + } + ZERO_STRUCTP(pcap); + + return pcap; +} + +/** @internal + * @brief writes a packet on file + */ +static int ssh_pcap_file_write(ssh_pcap_file pcap, ssh_buffer packet){ + int err; + uint32_t len; + if(pcap == NULL || pcap->output==NULL) + return SSH_ERROR; + len=ssh_buffer_get_len(packet); + err=fwrite(ssh_buffer_get_begin(packet),len,1,pcap->output); + if(err<0) + return SSH_ERROR; + else + return SSH_OK; +} + +/** @internal + * @brief prepends a packet with the pcap header and writes packet + * on file + */ +int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len){ + ssh_buffer header=ssh_buffer_new(); + struct timeval now; + int err; + if(header == NULL) + return SSH_ERROR; + gettimeofday(&now,NULL); + buffer_add_u32(header,htonl(now.tv_sec)); + buffer_add_u32(header,htonl(now.tv_usec)); + buffer_add_u32(header,htonl(ssh_buffer_get_len(packet))); + buffer_add_u32(header,htonl(original_len)); + buffer_add_buffer(header,packet); + err=ssh_pcap_file_write(pcap,header); + ssh_buffer_free(header); + return err; +} + +/** + * @brief opens a new pcap file and create header + */ +int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename){ + ssh_buffer header; + int err; + if(pcap == NULL) + return SSH_ERROR; + if(pcap->output){ + fclose(pcap->output); + pcap->output=NULL; + } + pcap->output=fopen(filename,"wb"); + if(pcap->output==NULL) + return SSH_ERROR; + header=ssh_buffer_new(); + if(header==NULL) + return SSH_ERROR; + buffer_add_u32(header,htonl(PCAP_MAGIC)); + buffer_add_u16(header,htons(PCAP_VERSION_MAJOR)); + buffer_add_u16(header,htons(PCAP_VERSION_MINOR)); + /* currently hardcode GMT to 0 */ + buffer_add_u32(header,htonl(0)); + /* accuracy */ + buffer_add_u32(header,htonl(0)); + /* size of the biggest packet */ + buffer_add_u32(header,htonl(MAX_PACKET_LEN)); + /* we will write sort-of IP */ + buffer_add_u32(header,htonl(DLT_RAW)); + err=ssh_pcap_file_write(pcap,header); + ssh_buffer_free(header); + return err; +} + +int ssh_pcap_file_close(ssh_pcap_file pcap){ + int err; + if(pcap ==NULL || pcap->output==NULL) + return SSH_ERROR; + err=fclose(pcap->output); + pcap->output=NULL; + if(err != 0) + return SSH_ERROR; + else + return SSH_OK; +} + +void ssh_pcap_file_free(ssh_pcap_file pcap){ + ssh_pcap_file_close(pcap); + SAFE_FREE(pcap); +} + + +/** @internal + * @brief allocates a new ssh_pcap_context object + */ + +ssh_pcap_context ssh_pcap_context_new(ssh_session session){ + ssh_pcap_context ctx=malloc(sizeof(struct ssh_pcap_context_struct)); + if(ctx==NULL){ + ssh_set_error_oom(session); + return NULL; + } + ZERO_STRUCTP(ctx); + ctx->session=session; + return ctx; +} + +void ssh_pcap_context_free(ssh_pcap_context ctx){ + SAFE_FREE(ctx); +} + +void ssh_pcap_context_set_file(ssh_pcap_context ctx, ssh_pcap_file pcap){ + ctx->file=pcap; +} + +/** @internal + * @brief sets the IP and port parameters in the connection + */ +static int ssh_pcap_context_connect(ssh_pcap_context ctx){ + ssh_session session=ctx->session; + struct sockaddr_in local, remote; + socket_t fd; + socklen_t len; + if(session==NULL) + return SSH_ERROR; + if(session->socket==NULL) + return SSH_ERROR; + fd=ssh_socket_get_fd_in(session->socket); + /* TODO: adapt for windows */ + if(fd<0) + return SSH_ERROR; + len=sizeof(local); + if(getsockname(fd,(struct sockaddr *)&local,&len)<0){ + ssh_set_error(session,SSH_REQUEST_DENIED,"Getting local IP address: %s",strerror(errno)); + return SSH_ERROR; + } + len=sizeof(remote); + if(getpeername(fd,(struct sockaddr *)&remote,&len)<0){ + ssh_set_error(session,SSH_REQUEST_DENIED,"Getting remote IP address: %s",strerror(errno)); + return SSH_ERROR; + } + if(local.sin_family != AF_INET){ + ssh_set_error(session,SSH_REQUEST_DENIED,"Only IPv4 supported for pcap logging"); + return SSH_ERROR; + } + memcpy(&ctx->ipsource,&local.sin_addr,sizeof(ctx->ipsource)); + memcpy(&ctx->ipdest,&remote.sin_addr,sizeof(ctx->ipdest)); + memcpy(&ctx->portsource,&local.sin_port,sizeof(ctx->portsource)); + memcpy(&ctx->portdest,&remote.sin_port,sizeof(ctx->portdest)); + + ctx->connected=1; + return SSH_OK; +} + +#define IPHDR_LEN 20 +#define TCPHDR_LEN 20 +#define TCPIPHDR_LEN (IPHDR_LEN + TCPHDR_LEN) +/** @internal + * @brief write a SSH packet as a TCP over IP in a pcap file + * @param ctx open pcap context + * @param direction SSH_PCAP_DIRECTION_IN if the packet has been received + * @param direction SSH_PCAP_DIRECTION_OUT if the packet has been emitted + * @param data pointer to the data to write + * @param len data to write in the pcap file. May be smaller than origlen. + * @param origlen number of bytes of complete data. + * @returns SSH_OK write is successful + * @returns SSH_ERROR an error happened. + */ +int ssh_pcap_context_write(ssh_pcap_context ctx,enum ssh_pcap_direction direction + , void *data, uint32_t len, uint32_t origlen){ + ssh_buffer ip; + int err; + if(ctx==NULL || ctx->file ==NULL) + return SSH_ERROR; + if(ctx->connected==0) + if(ssh_pcap_context_connect(ctx)==SSH_ERROR) + return SSH_ERROR; + ip=ssh_buffer_new(); + if(ip==NULL){ + ssh_set_error_oom(ctx->session); + return SSH_ERROR; + } + /* build an IP packet */ + /* V4, 20 bytes */ + buffer_add_u8(ip,4 << 4 | 5); + /* tos */ + buffer_add_u8(ip,0); + /* total len */ + buffer_add_u16(ip,htons(origlen + TCPIPHDR_LEN)); + /* IP id number */ + buffer_add_u16(ip,htons(ctx->file->ipsequence)); + ctx->file->ipsequence++; + /* fragment offset */ + buffer_add_u16(ip,htons(0)); + /* TTL */ + buffer_add_u8(ip,64); + /* protocol TCP=6 */ + buffer_add_u8(ip,6); + /* checksum */ + buffer_add_u16(ip,0); + if(direction==SSH_PCAP_DIR_OUT){ + buffer_add_u32(ip,ctx->ipsource); + buffer_add_u32(ip,ctx->ipdest); + } else { + buffer_add_u32(ip,ctx->ipdest); + buffer_add_u32(ip,ctx->ipsource); + } + /* TCP */ + if(direction==SSH_PCAP_DIR_OUT){ + buffer_add_u16(ip,ctx->portsource); + buffer_add_u16(ip,ctx->portdest); + } else { + buffer_add_u16(ip,ctx->portdest); + buffer_add_u16(ip,ctx->portsource); + } + /* sequence number */ + if(direction==SSH_PCAP_DIR_OUT){ + buffer_add_u32(ip,ntohl(ctx->outsequence)); + ctx->outsequence+=origlen; + } else { + buffer_add_u32(ip,ntohl(ctx->insequence)); + ctx->insequence+=origlen; + } + /* ack number */ + if(direction==SSH_PCAP_DIR_OUT){ + buffer_add_u32(ip,ntohl(ctx->insequence)); + } else { + buffer_add_u32(ip,ntohl(ctx->outsequence)); + } + /* header len = 20 = 5 * 32 bits, at offset 4*/ + buffer_add_u8(ip,5 << 4); + /* flags */ + buffer_add_u8(ip,TH_PUSH | TH_ACK); + /* window */ + buffer_add_u16(ip,htons(65535)); + /* checksum */ + buffer_add_u16(ip,htons(0)); + /* urgent data ptr */ + buffer_add_u16(ip,0); + /* actual data */ + buffer_add_data(ip,data,len); + err=ssh_pcap_file_write_packet(ctx->file,ip,origlen + TCPIPHDR_LEN); + ssh_buffer_free(ip); + return err; +} + +/** @brief sets the pcap file used to trace the session + * @param current session + * @param pcap an handler to a pcap file. A pcap file may be used in several + * sessions. + * @returns SSH_ERROR in case of error, SSH_OK otherwise. + */ +int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcap){ + ssh_pcap_context ctx=ssh_pcap_context_new(session); + if(ctx==NULL){ + ssh_set_error_oom(session); + return SSH_ERROR; + } + ctx->file=pcap; + if(session->pcap_ctx) + ssh_pcap_context_free(session->pcap_ctx); + session->pcap_ctx=ctx; + return SSH_OK; +} + + +#else /* WITH_PCAP */ + +/* Simple stub returning errors when no pcap compiled in */ + +#include "libssh/libssh.h" +#include "libssh/priv.h" + +int ssh_pcap_file_close(ssh_pcap_file pcap){ + (void) pcap; + return SSH_ERROR; +} + +void ssh_pcap_file_free(ssh_pcap_file pcap){ + (void) pcap; +} + +ssh_pcap_file ssh_pcap_file_new(void){ + return NULL; +} +int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename){ + (void) pcap; + (void) filename; + return SSH_ERROR; +} + +int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile){ + (void) pcapfile; + ssh_set_error(session,SSH_REQUEST_DENIED,"Pcap support not compiled in"); + return SSH_ERROR; +} + +#endif + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ |