Coverage Report

Created: 2026-06-07 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/rust/src/pgsql/logger.rs
Line
Count
Source
1
/* Copyright (C) 2022 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17
18
// Author: Juliana Fajardini <jufajardini@gmail.com>
19
20
//! PostgreSQL parser json logger
21
22
use crate::jsonbuilder::{JsonBuilder, JsonError};
23
use crate::pgsql::parser::*;
24
use crate::pgsql::pgsql::*;
25
use std;
26
27
pub const PGSQL_LOG_PASSWORDS: u32 = BIT_U32!(0);
28
29
8
fn log_pgsql(tx: &PgsqlTransaction, flags: u32, js: &mut JsonBuilder) -> Result<(), JsonError> {
30
8
    js.open_object("pgsql")?;
31
8
    js.set_uint("tx_id", tx.tx_id)?;
32
8
    if let Some(request) = &tx.request {
33
5
        js.set_object("request", &log_request(request, flags)?)?;
34
3
    } else if tx.responses.is_empty() {
35
        SCLogDebug!("Suricata created an empty PGSQL transaction");
36
        // TODO Log anomaly event instead?
37
3
        js.set_bool("request", false)?;
38
3
        js.set_bool("response", false)?;
39
3
        js.close()?;
40
3
        return Ok(());
41
0
    }
42
43
5
    if !tx.responses.is_empty() {
44
0
        js.set_object("response", &log_response_object(tx)?)?;
45
5
    }
46
5
    js.close()?;
47
48
5
    Ok(())
49
8
}
50
51
5
fn log_request(req: &PgsqlFEMessage, flags: u32) -> Result<JsonBuilder, JsonError> {
52
5
    let mut js = JsonBuilder::try_new_object()?;
53
5
    match req {
54
        PgsqlFEMessage::StartupMessage(StartupPacket {
55
            length: _,
56
0
            proto_major,
57
0
            proto_minor,
58
0
            params,
59
        }) => {
60
0
            let proto = format!("{}.{}", proto_major, proto_minor);
61
0
            js.set_string("protocol_version", &proto)?;
62
0
            js.set_object("startup_parameters", &log_startup_parameters(params)?)?;
63
        }
64
        PgsqlFEMessage::SSLRequest(_) => {
65
0
            js.set_string("message", "SSL Request")?;
66
        }
67
        PgsqlFEMessage::SASLInitialResponse(SASLInitialResponsePacket {
68
            identifier: _,
69
            length: _,
70
0
            auth_mechanism,
71
            param_length: _,
72
0
            sasl_param,
73
        }) => {
74
0
            js.set_string("sasl_authentication_mechanism", auth_mechanism.to_str())?;
75
0
            js.set_string_from_bytes("sasl_param", sasl_param)?;
76
        }
77
        PgsqlFEMessage::PasswordMessage(RegularPacket {
78
            identifier: _,
79
            length: _,
80
0
            payload,
81
        }) => {
82
0
            if flags & PGSQL_LOG_PASSWORDS != 0 {
83
0
                js.set_string_from_bytes("password", payload)?;
84
0
            }
85
        }
86
        PgsqlFEMessage::SASLResponse(RegularPacket {
87
            identifier: _,
88
            length: _,
89
0
            payload,
90
        }) => {
91
0
            js.set_string_from_bytes("sasl_response", payload)?;
92
        }
93
        PgsqlFEMessage::SimpleQuery(RegularPacket {
94
            identifier: _,
95
            length: _,
96
1
            payload,
97
        }) => {
98
1
            js.set_string_from_bytes(req.to_str(), payload)?;
99
        }
100
        PgsqlFEMessage::CancelRequest(CancelRequestMessage {
101
0
            pid,
102
0
            backend_key,
103
        }) => {
104
0
            js.set_string("message", "cancel_request")?;
105
0
            js.set_uint("process_id", (*pid).into())?;
106
0
            js.set_uint("secret_key", (*backend_key).into())?;
107
        }
108
        PgsqlFEMessage::Terminate(TerminationMessage {
109
            identifier: _,
110
            length: _,
111
        }) => {
112
3
            js.set_string("message", req.to_str())?;
113
        }
114
        PgsqlFEMessage::UnknownMessageType(RegularPacket {
115
            identifier: _,
116
            length: _,
117
            payload: _,
118
1
        }) => {
119
1
            // We don't want to log these, for now. Cf redmine: #6576
120
1
        }
121
    }
122
5
    js.close()?;
123
5
    Ok(js)
124
5
}
125
126
0
fn log_response_object(tx: &PgsqlTransaction) -> Result<JsonBuilder, JsonError> {
127
0
    let mut jb = JsonBuilder::try_new_object()?;
128
0
    let mut array_open = false;
129
0
    for response in &tx.responses {
130
0
        if let PgsqlBEMessage::ParameterStatus(msg) = response {
131
0
            if !array_open {
132
0
                jb.open_array("parameter_status")?;
133
0
                array_open = true;
134
0
            }
135
0
            jb.append_object(&log_pgsql_param(&msg.param)?)?;
136
        } else {
137
0
            if array_open {
138
0
                jb.close()?;
139
0
                array_open = false;
140
0
            }
141
0
            log_response(response, &mut jb)?;
142
        }
143
    }
144
0
    jb.close()?;
145
0
    Ok(jb)
146
0
}
147
148
0
fn log_response(res: &PgsqlBEMessage, jb: &mut JsonBuilder) -> Result<(), JsonError> {
149
0
    match res {
150
0
        PgsqlBEMessage::SSLResponse(message) => {
151
0
            if let SSLResponseMessage::SSLAccepted = message {
152
0
                jb.set_bool("ssl_accepted", true)?;
153
            } else {
154
0
                jb.set_bool("ssl_accepted", false)?;
155
            }
156
        }
157
        PgsqlBEMessage::NoticeResponse(ErrorNoticeMessage {
158
            identifier: _,
159
            length: _,
160
0
            message_body,
161
        })
162
        | PgsqlBEMessage::ErrorResponse(ErrorNoticeMessage {
163
            identifier: _,
164
            length: _,
165
0
            message_body,
166
        }) => {
167
0
            log_error_notice_field_types(message_body, jb)?;
168
        }
169
        PgsqlBEMessage::AuthenticationMD5Password(AuthenticationMessage {
170
            identifier: _,
171
            length: _,
172
            auth_type: _,
173
0
            payload,
174
        })
175
        | PgsqlBEMessage::AuthenticationSSPI(AuthenticationMessage {
176
            identifier: _,
177
            length: _,
178
            auth_type: _,
179
0
            payload,
180
        })
181
        | PgsqlBEMessage::AuthenticationSASLFinal(AuthenticationMessage {
182
            identifier: _,
183
            length: _,
184
            auth_type: _,
185
0
            payload,
186
        })
187
        | PgsqlBEMessage::CommandComplete(RegularPacket {
188
            identifier: _,
189
            length: _,
190
0
            payload,
191
        }) => {
192
0
            jb.set_string_from_bytes(res.to_str(), payload)?;
193
        }
194
        PgsqlBEMessage::UnknownMessageType(RegularPacket {
195
            identifier: _,
196
            length: _,
197
            payload: _,
198
0
        }) => {
199
0
            // We don't want to log these, for now. Cf redmine: #6576
200
0
        }
201
        PgsqlBEMessage::AuthenticationOk(_)
202
        | PgsqlBEMessage::AuthenticationCleartextPassword(_)
203
        | PgsqlBEMessage::AuthenticationSASL(_)
204
        | PgsqlBEMessage::AuthenticationSASLContinue(_) => {
205
0
            jb.set_string("message", res.to_str())?;
206
        }
207
        PgsqlBEMessage::ParameterStatus(ParameterStatusMessage {
208
            identifier: _,
209
            length: _,
210
            param: _,
211
0
        }) => {
212
0
            // We take care of these elsewhere
213
0
        }
214
        PgsqlBEMessage::BackendKeyData(BackendKeyDataMessage {
215
            identifier: _,
216
            length: _,
217
0
            backend_pid,
218
0
            secret_key,
219
        }) => {
220
0
            jb.set_uint("process_id", (*backend_pid).into())?;
221
0
            jb.set_uint("secret_key", (*secret_key).into())?;
222
        }
223
        PgsqlBEMessage::ReadyForQuery(ReadyForQueryMessage {
224
            identifier: _,
225
            length: _,
226
            transaction_status: _,
227
0
        }) => {
228
0
            // We don't want to log this one
229
0
        }
230
        PgsqlBEMessage::RowDescription(RowDescriptionMessage {
231
            identifier: _,
232
            length: _,
233
0
            field_count,
234
            fields: _,
235
        }) => {
236
0
            jb.set_uint("field_count", (*field_count).into())?;
237
        }
238
        PgsqlBEMessage::ConsolidatedDataRow(ConsolidatedDataRowPacket {
239
            identifier: _,
240
0
            row_cnt,
241
0
            data_size,
242
        }) => {
243
0
            jb.set_uint("data_rows", *row_cnt)?;
244
0
            jb.set_uint("data_size", *data_size)?;
245
        }
246
        PgsqlBEMessage::NotificationResponse(NotificationResponse {
247
            identifier: _,
248
            length: _,
249
0
            pid,
250
0
            channel_name,
251
0
            payload,
252
        }) => {
253
0
            jb.set_uint("pid", (*pid).into())?;
254
0
            jb.set_string_from_bytes("channel_name", channel_name)?;
255
0
            jb.set_string_from_bytes("payload", payload)?;
256
        }
257
    }
258
0
    Ok(())
259
0
}
260
261
0
fn log_error_notice_field_types(
262
0
    error_fields: &Vec<PgsqlErrorNoticeMessageField>, jb: &mut JsonBuilder,
263
0
) -> Result<(), JsonError> {
264
0
    for field in error_fields {
265
0
        jb.set_string_from_bytes(field.field_type.to_str(), &field.field_value)?;
266
    }
267
0
    Ok(())
268
0
}
269
270
0
fn log_startup_parameters(params: &PgsqlStartupParameters) -> Result<JsonBuilder, JsonError> {
271
0
    let mut jb = JsonBuilder::try_new_object()?;
272
    // User is a mandatory field in a pgsql message
273
0
    jb.set_string_from_bytes("user", &params.user.value)?;
274
0
    if let Some(parameters) = &params.optional_params {
275
0
        jb.open_array("optional_parameters")?;
276
0
        for parameter in parameters {
277
0
            jb.append_object(&log_pgsql_param(parameter)?)?;
278
        }
279
0
        jb.close()?;
280
0
    }
281
282
0
    jb.close()?;
283
0
    Ok(jb)
284
0
}
285
286
0
fn log_pgsql_param(param: &PgsqlParameter) -> Result<JsonBuilder, JsonError> {
287
0
    let mut jb = JsonBuilder::try_new_object()?;
288
0
    jb.set_string_from_bytes(param.name.to_str(), &param.value)?;
289
0
    jb.close()?;
290
0
    Ok(jb)
291
0
}
292
293
#[no_mangle]
294
8
pub unsafe extern "C" fn SCPgsqlLogger(
295
8
    tx: *mut std::os::raw::c_void, flags: u32, js: &mut JsonBuilder,
296
8
) -> bool {
297
8
    let tx_pgsql = cast_pointer!(tx, PgsqlTransaction);
298
    SCLogDebug!(
299
        "----------- PGSQL rs_pgsql_logger call. Tx id is {:?}",
300
        tx_pgsql.tx_id
301
    );
302
8
    log_pgsql(tx_pgsql, flags, js).is_ok()
303
8
}