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, &not_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, &not_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, &not_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