/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", ¶ms.user.value)?; |
274 | 0 | if let Some(parameters) = ¶ms.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(), ¶m.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 | } |