aboutsummaryrefslogtreecommitdiffstats
path: root/wiretap/rfc7468.c
blob: 1c143273e8fb9a3838422cb905a1a020a8dca2f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/* rfc7468.c
 *
 * Implements loading of files in the format specified by RFC 7468.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

#include "rfc7468.h"

#include "file_wrappers.h"
#include "wtap-int.h"

#include <wsutil/buffer.h>

#include <glib.h>

#include <string.h>

static int rfc7468_file_type_subtype = -1;

void register_rfc7468(void);

enum line_type {
    LINE_TYPE_PREEB,
    LINE_TYPE_POSTEB,
    LINE_TYPE_OTHER,
};

const char PREEB_BEGIN[] = "-----BEGIN ";
#define PREEB_BEGIN_LEN (sizeof PREEB_BEGIN - 1)
const char POSTEB_BEGIN[] = "-----END ";
#define POSTEB_BEGIN_LEN (sizeof POSTEB_BEGIN - 1)

static gboolean rfc7468_read_line(FILE_T fh, enum line_type *line_type, Buffer *buf,
    int* err, gchar** err_info)
{
    /* Make the chunk size large enough that most lines can fit in a single chunk.
       Strict RFC 7468 syntax only allows up to 64 characters per line, but we provide
       some leeway to accommodate nonconformant producers and explanatory text.
       The 3 extra bytes are for the trailing CR+LF and NUL terminator. */
    char line_chunk[128 + 3];
    char *line_chunk_end;

    if (!(line_chunk_end = file_getsp(line_chunk, sizeof line_chunk, fh))) {
        *err = file_error(fh, err_info);
        return FALSE;
    }

    // First chunk determines the line type.
    if (memcmp(line_chunk, PREEB_BEGIN, PREEB_BEGIN_LEN) == 0)
        *line_type = LINE_TYPE_PREEB;
    else if (memcmp(line_chunk, POSTEB_BEGIN, POSTEB_BEGIN_LEN) == 0)
        *line_type = LINE_TYPE_POSTEB;
    else
        *line_type = LINE_TYPE_OTHER;

    for (;;) {
        gsize line_chunk_len = line_chunk_end - line_chunk;
        if (line_chunk_len > G_MAXINT - ws_buffer_length(buf)) {
            *err = WTAP_ERR_BAD_FILE;
            *err_info = g_strdup_printf(
                "File contains an encoding larger than the maximum of %d bytes",
                G_MAXINT);
            return FALSE;
        }

        ws_buffer_append(buf, line_chunk, line_chunk_len);

        if (line_chunk_end[-1] == '\n' || file_eof(fh))
            break;

        if (!(line_chunk_end = file_getsp(line_chunk, sizeof line_chunk, fh))) {
            *err = file_error(fh, err_info);
            return FALSE;
        }
    }

    return TRUE;
}

static gboolean rfc7468_read_impl(FILE_T fh, wtap_rec *rec, Buffer *buf,
    int *err, gchar **err_info)
{
    ws_buffer_clean(buf);

    gboolean saw_preeb = FALSE;

    for (;;) {
        enum line_type line_type;

        if (!rfc7468_read_line(fh, &line_type, buf, err, err_info)) {
            if (*err != 0 || !saw_preeb) return FALSE;

            *err = WTAP_ERR_BAD_FILE;
            *err_info = g_strdup("Missing post-encapsulation boundary at end of file");
            return FALSE;
        }

        if (saw_preeb) {
            if (line_type == LINE_TYPE_POSTEB) break;
        } else {
            if (line_type == LINE_TYPE_PREEB) saw_preeb = TRUE;
        }
    }

    rec->rec_type = REC_TYPE_PACKET;
    rec->presence_flags = 0;
    rec->ts.secs = 0;
    rec->ts.nsecs = 0;
    rec->rec_header.packet_header.caplen = (guint32)ws_buffer_length(buf);
    rec->rec_header.packet_header.len = (guint32)ws_buffer_length(buf);

    return TRUE;
}

static gboolean rfc7468_read(wtap *wth, wtap_rec *rec, Buffer *buf,
    int *err, gchar **err_info, gint64 *data_offset)
{
    *data_offset = file_tell(wth->fh);

    return rfc7468_read_impl(wth->fh, rec, buf, err, err_info);
}

static gboolean rfc7468_seek_read(wtap *wth, gint64 seek_off, wtap_rec *rec,
    Buffer *buf, int *err, gchar **err_info)
{
    if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) < 0)
        return FALSE;

    return rfc7468_read_impl(wth->random_fh, rec, buf, err, err_info);
}

wtap_open_return_val rfc7468_open(wtap *wth, int *err, gchar **err_info)
{
    /* To detect whether this file matches our format, we need to find the
       first pre-encapsulation boundary, which may be located anywhere in the file,
       since it may be preceded by explanatory text. However, we don't want to
       read the entire file to find it, since the file may be huge, and detection
       needs to be fast. Therefore, we'll assume that if the boundary exists,
       it's located within a small initial chunk of the file. The size of
       the chunk was chosen arbitrarily. */
    char initial_chunk[2048];
    int initial_chunk_size = file_read(&initial_chunk, sizeof initial_chunk, wth->fh);

    if (initial_chunk_size < 0) {
        *err = file_error(wth->fh, err_info);
        return WTAP_OPEN_ERROR;
    }

    char *chunk_end_ptr = initial_chunk + initial_chunk_size;

    // Try to find a line that starts with PREEB_BEGIN in the initial chunk.
    for (char *line_ptr = initial_chunk; ; ) {
        if ((unsigned)(chunk_end_ptr - line_ptr) < PREEB_BEGIN_LEN)
            return WTAP_OPEN_NOT_MINE;

        if (memcmp(line_ptr, PREEB_BEGIN, PREEB_BEGIN_LEN) == 0)
            break;

        // Try next line.
        char *lf_ptr = memchr(line_ptr, '\n', chunk_end_ptr - line_ptr);
        if (!lf_ptr)
            return WTAP_OPEN_NOT_MINE;
        line_ptr = lf_ptr + 1;
    }

    if (file_seek(wth->fh, 0, SEEK_SET, err) == -1)
        return WTAP_OPEN_ERROR;

    wth->file_type_subtype = rfc7468_file_type_subtype;
    wth->file_encap = WTAP_ENCAP_RFC7468;

    wth->snapshot_length = 0;
    wth->file_tsprec = WTAP_TSPREC_SEC;

    wth->subtype_read = rfc7468_read;
    wth->subtype_seek_read = rfc7468_seek_read;

    return WTAP_OPEN_MINE;
}

static const struct supported_block_type rfc7468_blocks_supported[] = {
    /*
     * We provide one "packet" for each encoded structure in the file,
     * and don't support any options.
     */
    { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
};

static const struct file_type_subtype_info rfc7468_info = {
    "RFC 7468 files", "rfc7468", NULL, NULL,
    FALSE, BLOCKS_SUPPORTED(rfc7468_blocks_supported),
    NULL, NULL, NULL
};

void register_rfc7468(void)
{
    rfc7468_file_type_subtype = wtap_register_file_type_subtype(&rfc7468_info);

    /*
     * Register name for backwards compatibility with the
     * wtap_filetypes table in Lua.
     */
    wtap_register_backwards_compatibility_lua_name("RFC7468",
                                                   rfc7468_file_type_subtype);
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local Variables:
 * c-basic-offset: 4
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=4 tabstop=8 expandtab:
 * :indentSize=4:tabSize=8:noTabs=true:
 */