1 //--------------------------------------------------------------------------
2 // Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
3 //
4 // This program is free software; you can redistribute it and/or modify it
5 // under the terms of the GNU General Public License Version 2 as published
6 // by the Free Software Foundation. You may not use, modify or distribute
7 // this program under any other version of the GNU General Public License.
8 //
9 // This program is distributed in the hope that it will be useful, but
10 // WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License along
15 // with this program; if not, write to the Free Software Foundation, Inc.,
16 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 //--------------------------------------------------------------------------
18 // http_stream_splitter_finish.cc author Tom Peters <thopeter@cisco.com>
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "file_api/file_flows.h"
25 #include "pub_sub/http_request_body_event.h"
26
27 #include "http_common.h"
28 #include "http_cutter.h"
29 #include "http_enum.h"
30 #include "http_field.h"
31 #include "http_inspect.h"
32 #include "http_module.h"
33 #include "http_msg_header.h"
34 #include "http_msg_request.h"
35 #include "http_stream_splitter.h"
36 #include "http_test_input.h"
37
38 using namespace HttpCommon;
39 using namespace HttpEnums;
40 using namespace snort;
41
finish(Flow * flow)42 bool HttpStreamSplitter::finish(Flow* flow)
43 {
44 Profile profile(HttpModule::get_profile_stats());
45
46 HttpFlowData* session_data = HttpInspect::http_get_flow_data(flow);
47 if (!session_data)
48 {
49 // assert(false); // FIXIT-M this should not be possible but currently it is
50 return false;
51 }
52
53 #ifdef REG_TEST
54 if (HttpTestManager::use_test_output(HttpTestManager::IN_HTTP))
55 {
56 if (HttpTestManager::use_test_input(HttpTestManager::IN_HTTP))
57 {
58 if (!HttpTestManager::get_test_input_source()->finish())
59 return false;
60 }
61 else
62 {
63 fprintf(HttpTestManager::get_output_file(),
64 "Finish from flow data %" PRIu64 " direction %d\n", session_data->seq_num,
65 source_id);
66 fflush(HttpTestManager::get_output_file());
67 }
68 }
69 #endif
70
71 if (session_data->type_expected[source_id] == SEC_ABORT)
72 {
73 return false;
74 }
75
76 if (session_data->tcp_close[source_id])
77 {
78 // assert(false); // FIXIT-M this should not happen but it does
79 session_data->type_expected[source_id] = SEC_ABORT;
80 return false;
81 }
82
83 session_data->tcp_close[source_id] = true;
84 if (session_data->section_type[source_id] != SEC__NOT_COMPUTE)
85 return true;
86
87 if (session_data->type_expected[source_id] == SEC_BODY_CL)
88 {
89 *session_data->get_infractions(source_id) += INF_TRUNCATED_MSG_BODY_CL;
90 session_data->events[source_id]->create_event(EVENT_TRUNCATED_MSG_BODY_CL);
91 }
92 else if (session_data->type_expected[source_id] == SEC_BODY_CHUNK)
93 {
94 *session_data->get_infractions(source_id) += INF_TRUNCATED_MSG_BODY_CHUNK;
95 session_data->events[source_id]->create_event(EVENT_TRUNCATED_MSG_BODY_CHUNK);
96 }
97
98 // If there is leftover data for which we returned PAF_SEARCH and never flushed, we need to set
99 // up to process because it is about to go to reassemble(). But we don't support partial start
100 // lines.
101 if ((session_data->cutter[source_id] != nullptr) &&
102 (session_data->cutter[source_id]->get_octets_seen() >
103 session_data->partial_raw_bytes[source_id]))
104 {
105 if ((session_data->type_expected[source_id] == SEC_REQUEST) ||
106 (session_data->type_expected[source_id] == SEC_STATUS))
107 {
108 *session_data->get_infractions(source_id) += INF_PARTIAL_START;
109 // FIXIT-M why not use generate_misformatted_http()?
110 session_data->events[source_id]->create_event(EVENT_LOSS_OF_SYNC);
111 return false;
112 }
113
114 uint32_t not_used;
115 prepare_flush(session_data, ¬_used, session_data->type_expected[source_id], 0,
116 session_data->cutter[source_id]->get_num_excess(),
117 session_data->cutter[source_id]->get_num_head_lines(),
118 session_data->cutter[source_id]->get_is_broken_chunk(),
119 session_data->cutter[source_id]->get_num_good_chunks(),
120 session_data->cutter[source_id]->get_octets_seen());
121 delete session_data->cutter[source_id];
122 session_data->cutter[source_id] = nullptr;
123
124 return true;
125 }
126
127 // If the message has been truncated immediately following the start line then we need to
128 // process an empty header section to provide an inspection section. Otherwise the start line
129 // won't go through detection.
130 if ((session_data->type_expected[source_id] == SEC_HEADER) &&
131 (session_data->cutter[source_id] == nullptr))
132 {
133 // Set up to process empty header section
134 uint32_t not_used;
135 prepare_flush(session_data, ¬_used, SEC_HEADER, 0, 0, 0, false, 0, 0);
136 return true;
137 }
138
139 // If there is no more data to process we may need to tell other components
140 if ((session_data->cutter[source_id] != nullptr) &&
141 (session_data->cutter[source_id]->get_octets_seen() ==
142 session_data->partial_raw_bytes[source_id]))
143 {
144 // Wrap up file processing
145 if (session_data->file_depth_remaining[source_id] > 0)
146 {
147 Packet* packet = DetectionEngine::get_current_packet();
148 if (!session_data->mime_state[source_id])
149 {
150 FileFlows* file_flows = FileFlows::get_file_flows(flow);
151 if (!file_flows)
152 return false;
153
154 const FileDirection dir = (source_id == SRC_SERVER) ? FILE_DOWNLOAD :
155 FILE_UPLOAD;
156
157 assert(session_data->transaction[source_id] != nullptr);
158 HttpMsgHeader* header = session_data->transaction[source_id]->
159 get_header(source_id);
160 assert(header);
161
162 uint64_t file_index = header->get_file_cache_index();
163 const uint64_t file_processing_id = header->get_multi_file_processing_id();
164 file_flows->file_process(packet, file_index, nullptr, 0,
165 session_data->file_octets[source_id], dir, file_processing_id,
166 SNORT_FILE_END);
167 #ifdef REG_TEST
168 if (HttpTestManager::use_test_output(HttpTestManager::IN_HTTP))
169 {
170 fprintf(HttpTestManager::get_output_file(),
171 "File processing finalization during finish()\n");
172 fflush(HttpTestManager::get_output_file());
173 }
174 #endif
175 }
176 else
177 {
178 // FIXIT-M The following call does not actually accomplish anything. The MIME
179 // interface needs to be enhanced so that we can communicate end-of-data
180 // without side effects.
181 session_data->mime_state[source_id]->process_mime_data(packet, nullptr, 0, true,
182 SNORT_FILE_POSITION_UNKNOWN);
183 delete session_data->mime_state[source_id];
184 session_data->mime_state[source_id] = nullptr;
185 #ifdef REG_TEST
186 if (HttpTestManager::use_test_output(HttpTestManager::IN_HTTP))
187 {
188 fprintf(HttpTestManager::get_output_file(),
189 "MIME finalization during finish()\n");
190 fflush(HttpTestManager::get_output_file());
191 }
192 #endif
193 }
194 }
195 // If we were publishing a request body need to publish that body is complete
196 if (session_data->publish_depth_remaining[source_id] > 0)
197 {
198 HttpRequestBodyEvent http_request_body_event(nullptr,
199 session_data->publish_octets[source_id], true, session_data);
200 DataBus::publish(HTTP2_REQUEST_BODY_EVENT_KEY, http_request_body_event, flow);
201 #ifdef REG_TEST
202 if (HttpTestManager::use_test_output(HttpTestManager::IN_HTTP))
203 {
204 fprintf(HttpTestManager::get_output_file(),
205 "Request body event published during finish()\n");
206 fflush(HttpTestManager::get_output_file());
207 }
208 #endif
209 }
210 return false;
211 }
212
213 return false;
214 }
215
prep_partial_flush(Flow * flow,uint32_t num_flush)216 void HttpStreamSplitter::prep_partial_flush(Flow* flow, uint32_t num_flush)
217 {
218 Profile profile(HttpModule::get_profile_stats());
219
220 HttpFlowData* session_data = HttpInspect::http_get_flow_data(flow);
221 assert(session_data != nullptr);
222
223 #ifdef REG_TEST
224 if (HttpTestManager::use_test_output(HttpTestManager::IN_HTTP) &&
225 !HttpTestManager::use_test_input(HttpTestManager::IN_HTTP))
226 {
227 fprintf(HttpTestManager::get_output_file(), "Partial flush from flow data %" PRIu64 "\n",
228 session_data->seq_num);
229 fflush(HttpTestManager::get_output_file());
230 }
231 #endif
232
233 // Set up to process partial message section
234 uint32_t not_used;
235 prepare_flush(session_data, ¬_used, session_data->type_expected[source_id], num_flush, 0, 0,
236 session_data->cutter[source_id]->get_is_broken_chunk(),
237 session_data->cutter[source_id]->get_num_good_chunks(),
238 session_data->cutter[source_id]->get_octets_seen() - num_flush);
239 session_data->partial_flush[source_id] = true;
240 }
241
242