aboutsummaryrefslogtreecommitdiff
path: root/src/pcap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pcap.c')
-rw-r--r--src/pcap.c434
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: */