Coverage Report

Created: 2025-07-18 06:09

/src/libssh/src/pcap.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * This file is part of the SSH Library
3
 *
4
 * Copyright (c) 2009 by Aris Adamantiadis
5
 *
6
 * The SSH Library is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published by
8
 * the Free Software Foundation; either version 2.1 of the License, or (at your
9
 * option) any later version.
10
 *
11
 * The SSH Library is distributed in the hope that it will be useful, but
12
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14
 * License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with the SSH Library; see the file COPYING.  If not, write to
18
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
19
 * MA 02111-1307, USA.
20
 */
21
22
/* pcap.c */
23
#include "config.h"
24
#ifdef WITH_PCAP
25
26
#include <stdio.h>
27
#ifdef _WIN32
28
#include <winsock2.h>
29
#include <ws2tcpip.h>
30
#else
31
#include <netinet/in.h>
32
#include <sys/socket.h>
33
#endif
34
#ifdef HAVE_SYS_TIME_H
35
#include <sys/time.h>
36
#endif /* HAVE_SYS_TIME_H */
37
#include <errno.h>
38
#include <stdlib.h>
39
40
#include "libssh/libssh.h"
41
#include "libssh/pcap.h"
42
#include "libssh/session.h"
43
#include "libssh/buffer.h"
44
#include "libssh/socket.h"
45
46
/**
47
 * @defgroup libssh_pcap The libssh pcap functions
48
 * @ingroup libssh
49
 *
50
 * The pcap file generation
51
 *
52
 * @{
53
 */
54
55
/* The header of a pcap file is the following. We are not going to make it
56
 * very complicated.
57
 * Just for information.
58
 */
59
struct pcap_hdr_s {
60
  uint32_t magic_number;   /* magic number */
61
  uint16_t version_major;  /* major version number */
62
  uint16_t version_minor;  /* minor version number */
63
  int32_t  thiszone;       /* GMT to local correction */
64
  uint32_t sigfigs;        /* accuracy of timestamps */
65
  uint32_t snaplen;        /* max length of captured packets, in octets */
66
  uint32_t network;        /* data link type */
67
};
68
69
#define PCAP_MAGIC 0xa1b2c3d4
70
#define PCAP_VERSION_MAJOR 2
71
#define PCAP_VERSION_MINOR 4
72
73
#define DLT_RAW         12      /* raw IP */
74
75
/* TCP flags */
76
#define TH_FIN        0x01
77
#define TH_SYN        0x02
78
#define TH_RST        0x04
79
#define TH_PUSH       0x08
80
#define TH_ACK        0x10
81
#define TH_URG        0x20
82
83
/* The header of a pcap packet.
84
 * Just for information.
85
 */
86
struct pcaprec_hdr_s {
87
  uint32_t ts_sec;         /* timestamp seconds */
88
  uint32_t ts_usec;        /* timestamp microseconds */
89
  uint32_t incl_len;       /* number of octets of packet saved in file */
90
  uint32_t orig_len;       /* actual length of packet */
91
};
92
93
/** @private
94
 * @brief a pcap context expresses the state of a pcap dump
95
 * in a SSH session only. Multiple pcap contexts may be used into
96
 * a single pcap file.
97
 */
98
struct ssh_pcap_context_struct {
99
  ssh_session session;
100
  ssh_pcap_file file;
101
  int connected;
102
  /* All of this information is useful to generate
103
   * the dummy IP and TCP packets
104
   */
105
  uint32_t ipsource;
106
  uint32_t ipdest;
107
  uint16_t portsource;
108
  uint16_t portdest;
109
  uint32_t outsequence;
110
  uint32_t insequence;
111
};
112
113
/** @private
114
 * @brief a pcap file expresses the state of a pcap file which may
115
 * contain several streams.
116
 */
117
struct ssh_pcap_file_struct {
118
  FILE *output;
119
  uint16_t ipsequence;
120
};
121
122
/**
123
 * @brief create a new ssh_pcap_file object
124
 */
125
ssh_pcap_file ssh_pcap_file_new(void)
126
0
{
127
0
    struct ssh_pcap_file_struct *pcap = NULL;
128
129
0
    pcap = malloc(sizeof(struct ssh_pcap_file_struct));
130
0
    if (pcap == NULL) {
131
0
        return NULL;
132
0
    }
133
0
    ZERO_STRUCTP(pcap);
134
135
0
    return pcap;
136
0
}
137
138
/** @internal
139
 * @brief writes a packet on file
140
 */
141
static int ssh_pcap_file_write(ssh_pcap_file pcap, ssh_buffer packet)
142
0
{
143
0
    int err;
144
0
    uint32_t len;
145
0
    if (pcap == NULL || pcap->output == NULL) {
146
0
        return SSH_ERROR;
147
0
    }
148
0
    len = ssh_buffer_get_len(packet);
149
0
    err = fwrite(ssh_buffer_get(packet), len, 1, pcap->output);
150
0
    if (err < 0) {
151
0
        return SSH_ERROR;
152
0
    } else {
153
0
        return SSH_OK;
154
0
    }
155
0
}
156
157
/** @internal
158
 * @brief prepends a packet with the pcap header and writes packet
159
 * on file
160
 */
161
int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len)
162
0
{
163
0
    ssh_buffer header = ssh_buffer_new();
164
0
    struct timeval now;
165
0
    int err;
166
167
0
    if (header == NULL) {
168
0
        return SSH_ERROR;
169
0
    }
170
171
0
    gettimeofday(&now, NULL);
172
0
    err = ssh_buffer_allocate_size(header,
173
0
                                   sizeof(uint32_t) * 4 +
174
0
                                   ssh_buffer_get_len(packet));
175
0
    if (err < 0) {
176
0
        goto error;
177
0
    }
178
0
    err = ssh_buffer_add_u32(header, htonl(now.tv_sec));
179
0
    if (err < 0) {
180
0
        goto error;
181
0
    }
182
0
    err = ssh_buffer_add_u32(header, htonl(now.tv_usec));
183
0
    if (err < 0) {
184
0
        goto error;
185
0
    }
186
0
    err = ssh_buffer_add_u32(header, htonl(ssh_buffer_get_len(packet)));
187
0
    if (err < 0) {
188
0
        goto error;
189
0
    }
190
0
    err = ssh_buffer_add_u32(header, htonl(original_len));
191
0
    if (err < 0) {
192
0
        goto error;
193
0
    }
194
0
    err = ssh_buffer_add_buffer(header, packet);
195
0
    if (err < 0) {
196
0
        goto error;
197
0
    }
198
0
    err = ssh_pcap_file_write(pcap, header);
199
0
error:
200
0
    SSH_BUFFER_FREE(header);
201
0
    return err;
202
0
}
203
204
/**
205
 * @brief opens a new pcap file and creates header
206
 */
207
int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename)
208
0
{
209
0
    ssh_buffer header = NULL;
210
0
    int err;
211
212
0
    if (pcap == NULL) {
213
0
        return SSH_ERROR;
214
0
    }
215
0
    if (pcap->output) {
216
0
        fclose(pcap->output);
217
0
        pcap->output = NULL;
218
0
    }
219
0
    pcap->output = fopen(filename, "wb");
220
0
    if (pcap->output == NULL) {
221
0
        return SSH_ERROR;
222
0
    }
223
0
    header = ssh_buffer_new();
224
0
    if (header == NULL) {
225
0
        return SSH_ERROR;
226
0
    }
227
0
    err = ssh_buffer_allocate_size(header,
228
0
                                   sizeof(uint32_t) * 5 +
229
0
                                   sizeof(uint16_t) * 2);
230
0
    if (err < 0) {
231
0
        goto error;
232
0
    }
233
0
    err = ssh_buffer_add_u32(header, htonl(PCAP_MAGIC));
234
0
    if (err < 0) {
235
0
        goto error;
236
0
    }
237
0
    err = ssh_buffer_add_u16(header, htons(PCAP_VERSION_MAJOR));
238
0
    if (err < 0) {
239
0
        goto error;
240
0
    }
241
0
    err = ssh_buffer_add_u16(header, htons(PCAP_VERSION_MINOR));
242
0
    if (err < 0) {
243
0
        goto error;
244
0
    }
245
    /* currently hardcode GMT to 0 */
246
0
    err = ssh_buffer_add_u32(header, htonl(0));
247
0
    if (err < 0) {
248
0
        goto error;
249
0
    }
250
    /* accuracy */
251
0
    err = ssh_buffer_add_u32(header, htonl(0));
252
0
    if (err < 0) {
253
0
        goto error;
254
0
    }
255
    /* size of the biggest packet */
256
0
    err = ssh_buffer_add_u32(header, htonl(MAX_PACKET_LEN));
257
0
    if (err < 0) {
258
0
        goto error;
259
0
    }
260
    /* we will write sort-of IP */
261
0
    err = ssh_buffer_add_u32(header, htonl(DLT_RAW));
262
0
    if (err < 0) {
263
0
        goto error;
264
0
    }
265
0
    err = ssh_pcap_file_write(pcap,header);
266
0
error:
267
0
    SSH_BUFFER_FREE(header);
268
0
    return err;
269
0
}
270
271
int ssh_pcap_file_close(ssh_pcap_file pcap)
272
0
{
273
0
    int err;
274
275
0
    if (pcap == NULL || pcap->output == NULL) {
276
0
        return SSH_ERROR;
277
0
    }
278
0
    err = fclose(pcap->output);
279
0
    pcap->output = NULL;
280
0
    if (err != 0) {
281
0
        return SSH_ERROR;
282
0
    } else {
283
0
        return SSH_OK;
284
0
    }
285
0
}
286
287
void ssh_pcap_file_free(ssh_pcap_file pcap)
288
0
{
289
0
    ssh_pcap_file_close(pcap);
290
0
    SAFE_FREE(pcap);
291
0
}
292
293
294
/** @internal
295
 * @brief allocates a new ssh_pcap_context object
296
 */
297
ssh_pcap_context ssh_pcap_context_new(ssh_session session)
298
0
{
299
0
    ssh_pcap_context ctx = (struct ssh_pcap_context_struct *)malloc(sizeof(struct ssh_pcap_context_struct));
300
0
    if (ctx == NULL) {
301
0
        ssh_set_error_oom(session);
302
0
        return NULL;
303
0
    }
304
0
    ZERO_STRUCTP(ctx);
305
0
    ctx->session = session;
306
0
    return ctx;
307
0
}
308
309
void ssh_pcap_context_free(ssh_pcap_context ctx)
310
0
{
311
0
    SAFE_FREE(ctx);
312
0
}
313
314
void ssh_pcap_context_set_file(ssh_pcap_context ctx, ssh_pcap_file pcap)
315
0
{
316
0
    ctx->file = pcap;
317
0
}
318
319
/** @internal
320
 * @brief sets the IP and port parameters in the connection
321
 */
322
static int ssh_pcap_context_connect(ssh_pcap_context ctx)
323
0
{
324
0
    ssh_session session=ctx->session;
325
0
    struct sockaddr_in local = {
326
0
        .sin_family = AF_UNSPEC,
327
0
    };
328
0
    struct sockaddr_in remote = {
329
0
        .sin_family = AF_UNSPEC,
330
0
    };
331
0
    socket_t fd;
332
0
    socklen_t len;
333
0
    int rc;
334
0
    char err_msg[SSH_ERRNO_MSG_MAX] = {0};
335
336
0
    if (session == NULL) {
337
0
        return SSH_ERROR;
338
0
    }
339
340
0
    if (session->socket == NULL) {
341
0
        return SSH_ERROR;
342
0
    }
343
344
0
    fd = ssh_socket_get_fd(session->socket);
345
346
    /* TODO: adapt for windows */
347
0
    if (fd < 0) {
348
0
        return SSH_ERROR;
349
0
    }
350
351
0
    len = sizeof(local);
352
0
    rc = getsockname(fd, (struct sockaddr *)&local, &len);
353
0
    if (rc < 0) {
354
0
        ssh_set_error(session,
355
0
                      SSH_REQUEST_DENIED,
356
0
                      "Getting local IP address: %s",
357
0
                      ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
358
0
        return SSH_ERROR;
359
0
    }
360
361
0
    len = sizeof(remote);
362
0
    rc = getpeername(fd, (struct sockaddr *)&remote, &len);
363
0
    if (rc < 0) {
364
0
        ssh_set_error(session,
365
0
                      SSH_REQUEST_DENIED,
366
0
                      "Getting remote IP address: %s",
367
0
                      ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
368
0
        return SSH_ERROR;
369
0
    }
370
371
0
    if (local.sin_family != AF_INET) {
372
0
        ssh_set_error(session,
373
0
                      SSH_REQUEST_DENIED,
374
0
                      "Only IPv4 supported for pcap logging");
375
0
        return SSH_ERROR;
376
0
    }
377
378
0
    memcpy(&ctx->ipsource, &local.sin_addr, sizeof(ctx->ipsource));
379
0
    memcpy(&ctx->ipdest, &remote.sin_addr, sizeof(ctx->ipdest));
380
0
    memcpy(&ctx->portsource, &local.sin_port, sizeof(ctx->portsource));
381
0
    memcpy(&ctx->portdest, &remote.sin_port, sizeof(ctx->portdest));
382
383
0
    ctx->connected = 1;
384
0
    return SSH_OK;
385
0
}
386
387
0
#define IPHDR_LEN 20
388
0
#define TCPHDR_LEN 20
389
0
#define TCPIPHDR_LEN (IPHDR_LEN + TCPHDR_LEN)
390
/** @internal
391
 * @brief write a SSH packet as a TCP over IP in a pcap file
392
 * @param ctx open pcap context
393
 * @param direction SSH_PCAP_DIRECTION_IN if the packet has been received
394
 * @param direction SSH_PCAP_DIRECTION_OUT if the packet has been emitted
395
 * @param data pointer to the data to write
396
 * @param len data to write in the pcap file. May be smaller than origlen.
397
 * @param origlen number of bytes of complete data.
398
 * @returns SSH_OK write is successful
399
 * @returns SSH_ERROR an error happened.
400
 */
401
int ssh_pcap_context_write(ssh_pcap_context ctx,
402
                           enum ssh_pcap_direction direction,
403
                           void *data,
404
                           uint32_t len,
405
                           uint32_t origlen)
406
0
{
407
0
    ssh_buffer ip;
408
0
    int rc;
409
410
0
    if (ctx == NULL || ctx->file == NULL) {
411
0
        return SSH_ERROR;
412
0
    }
413
0
    if (ctx->connected == 0) {
414
0
        if (ssh_pcap_context_connect(ctx) == SSH_ERROR) {
415
0
            return SSH_ERROR;
416
0
        }
417
0
    }
418
0
    ip = ssh_buffer_new();
419
0
    if (ip == NULL) {
420
0
        ssh_set_error_oom(ctx->session);
421
0
        return SSH_ERROR;
422
0
    }
423
424
    /* build an IP packet */
425
0
    rc = ssh_buffer_pack(ip,
426
0
                         "bbwwwbbw",
427
0
                         4 << 4 | 5, /* V4, 20 bytes */
428
0
                         0,          /* tos */
429
0
                         origlen + TCPIPHDR_LEN, /* total len */
430
0
                         ctx->file->ipsequence,  /* IP id number */
431
0
                         0,          /* fragment offset */
432
0
                         64,         /* TTL */
433
0
                         6,          /* protocol TCP=6 */
434
0
                         0);         /* checksum */
435
436
0
    ctx->file->ipsequence++;
437
0
    if (rc != SSH_OK) {
438
0
        goto error;
439
0
    }
440
0
    if (direction == SSH_PCAP_DIR_OUT) {
441
0
        rc = ssh_buffer_add_u32(ip, ctx->ipsource);
442
0
        if (rc < 0) {
443
0
            goto error;
444
0
        }
445
0
        rc = ssh_buffer_add_u32(ip, ctx->ipdest);
446
0
        if (rc < 0) {
447
0
            goto error;
448
0
        }
449
0
    } else {
450
0
        rc = ssh_buffer_add_u32(ip, ctx->ipdest);
451
0
        if (rc < 0) {
452
0
            goto error;
453
0
        }
454
0
        rc = ssh_buffer_add_u32(ip, ctx->ipsource);
455
0
        if (rc < 0) {
456
0
            goto error;
457
0
        }
458
0
    }
459
    /* TCP */
460
0
    if (direction == SSH_PCAP_DIR_OUT) {
461
0
        rc = ssh_buffer_add_u16(ip, ctx->portsource);
462
0
        if (rc < 0) {
463
0
            goto error;
464
0
        }
465
0
        rc = ssh_buffer_add_u16(ip, ctx->portdest);
466
0
        if (rc < 0) {
467
0
            goto error;
468
0
        }
469
0
    } else {
470
0
        rc = ssh_buffer_add_u16(ip, ctx->portdest);
471
0
        if (rc < 0) {
472
0
            goto error;
473
0
        }
474
0
        rc = ssh_buffer_add_u16(ip, ctx->portsource);
475
0
        if (rc < 0) {
476
0
            goto error;
477
0
        }
478
0
    }
479
    /* sequence number */
480
0
    if (direction == SSH_PCAP_DIR_OUT) {
481
0
        rc = ssh_buffer_pack(ip, "d", ctx->outsequence);
482
0
        if (rc != SSH_OK) {
483
0
            goto error;
484
0
        }
485
0
        ctx->outsequence += origlen;
486
0
    } else {
487
0
        rc = ssh_buffer_pack(ip, "d", ctx->insequence);
488
0
        if (rc != SSH_OK) {
489
0
            goto error;
490
0
        }
491
0
        ctx->insequence += origlen;
492
0
    }
493
    /* ack number */
494
0
    if (direction == SSH_PCAP_DIR_OUT) {
495
0
        rc = ssh_buffer_pack(ip, "d", ctx->insequence);
496
0
        if (rc != SSH_OK) {
497
0
            goto error;
498
0
        }
499
0
    } else {
500
0
        rc = ssh_buffer_pack(ip, "d", ctx->outsequence);
501
0
        if (rc != SSH_OK) {
502
0
            goto error;
503
0
        }
504
0
    }
505
506
0
    rc = ssh_buffer_pack(ip,
507
0
                         "bbwwwP",
508
0
                         5 << 4,             /* header len = 20 = 5 * 32 bits, at offset 4*/
509
0
                         TH_PUSH | TH_ACK,   /* flags */
510
0
                         65535,              /* window */
511
0
                         0,                  /* checksum */
512
0
                         0,                  /* urgent data ptr */
513
0
                         (size_t)len, data); /* actual data */
514
0
    if (rc != SSH_OK) {
515
0
        goto error;
516
0
    }
517
0
    rc = ssh_pcap_file_write_packet(ctx->file, ip, origlen + TCPIPHDR_LEN);
518
519
0
error:
520
0
    SSH_BUFFER_FREE(ip);
521
0
    return rc;
522
0
}
523
524
/** @brief sets the pcap file used to trace the session
525
 * @param current session
526
 * @param pcap a handler to a pcap file. A pcap file may be used in several
527
 * sessions.
528
 * @returns SSH_ERROR in case of error, SSH_OK otherwise.
529
 */
530
int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcap)
531
0
{
532
0
    ssh_pcap_context ctx = ssh_pcap_context_new(session);
533
0
    if (ctx == NULL) {
534
0
        ssh_set_error_oom(session);
535
0
        return SSH_ERROR;
536
0
    }
537
0
    ctx->file = pcap;
538
0
    if (session->pcap_ctx) {
539
0
        ssh_pcap_context_free(session->pcap_ctx);
540
0
    }
541
0
    session->pcap_ctx = ctx;
542
0
    return SSH_OK;
543
0
}
544
/** @} */
545
546
#else /* WITH_PCAP */
547
548
/* Simple stub returning errors when no pcap compiled in */
549
550
#include "libssh/libssh.h"
551
#include "libssh/priv.h"
552
553
int ssh_pcap_file_close(ssh_pcap_file pcap)
554
{
555
    (void)pcap;
556
557
    return SSH_ERROR;
558
}
559
560
void ssh_pcap_file_free(ssh_pcap_file pcap)
561
{
562
    (void)pcap;
563
}
564
565
ssh_pcap_file ssh_pcap_file_new(void)
566
{
567
    return NULL;
568
}
569
int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename)
570
{
571
    (void)pcap;
572
    (void)filename;
573
574
    return SSH_ERROR;
575
}
576
577
int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile)
578
{
579
    (void)pcapfile;
580
581
    ssh_set_error(session, SSH_REQUEST_DENIED, "Pcap support not compiled in");
582
    return SSH_ERROR;
583
}
584
585
#endif