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_transaction.cc author Tom Peters <thopeter@cisco.com>
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include "http_transaction.h"
25 
26 #include "http_common.h"
27 #include "http_enum.h"
28 #include "http_event.h"
29 #include "http_msg_body.h"
30 #include "http_msg_header.h"
31 #include "http_msg_request.h"
32 #include "http_msg_status.h"
33 #include "http_msg_trailer.h"
34 
35 using namespace HttpCommon;
36 using namespace HttpEnums;
37 using namespace snort;
38 
39 const uint16_t HttpTransaction::transaction_memory_usage_estimate = sizeof(HttpTransaction) +
40     sizeof(HttpMsgRequest) + sizeof(HttpMsgStatus) + (2 * sizeof(HttpMsgHeader)) + sizeof(HttpUri)
41     + (2 * sizeof(HttpInfractions)) + small_things;
42 
delete_section_list(HttpMsgSection * section_list)43 static void delete_section_list(HttpMsgSection* section_list)
44 {
45     while (section_list != nullptr)
46     {
47         HttpMsgSection* tmp = section_list;
48         section_list = section_list->next;
49         delete tmp;
50     }
51 }
52 
HttpTransaction(HttpFlowData * session_data_)53 HttpTransaction::HttpTransaction(HttpFlowData* session_data_): session_data(session_data_)
54 {
55     infractions[0] = nullptr;
56     infractions[1] = nullptr;
57 }
58 
~HttpTransaction()59 HttpTransaction::~HttpTransaction()
60 {
61     delete request;
62     delete status;
63     for (int k = 0; k <= 1; k++)
64     {
65         delete header[k];
66         delete trailer[k];
67         delete infractions[k];
68     }
69     delete_section_list(body_list);
70     delete_section_list(discard_list);
71 }
72 
attach_my_transaction(HttpFlowData * session_data,SourceId source_id)73 HttpTransaction* HttpTransaction::attach_my_transaction(HttpFlowData* session_data, SourceId
74     source_id)
75 {
76     // This factory method:
77     // 1. creates new transactions for all request messages and orphaned response messages
78     // 2. associates requests and responses and supports pipelining
79     // 3. garbage collects unneeded transactions
80     // 4. returns the current transaction
81 
82     // Request section: replace the old request transaction with a new transaction.
83     if (session_data->section_type[source_id] == SEC_REQUEST)
84     {
85         // If the HTTP request and response messages are alternating (usual situation) the old
86         // request transaction will have been moved to the server side when the last response
87         // message was received. This will be nullptr and we don't need to deal with the old
88         // request transaction here.
89         if (session_data->transaction[SRC_CLIENT] != nullptr)
90         {
91             // The old request transaction is still here. Typically that is because the
92             // the current request has arrived before the previous response (pipelining). We need
93             // to add this transaction to our pipeline where it will wait for the matching
94             // response. But there are some special cases to check first.
95             if (session_data->transaction[SRC_CLIENT]->response_seen)
96             {
97                 // The response started before the request finished. When the response took the
98                 // old request transaction it did not leave the usual nullptr because we still
99                 // needed it. Instead the two sides have been sharing the transaction. This is a
100                 // soft delete that eliminates our interest in this transaction without disturbing
101                 // the possibly ongoing response processing.
102                 delete_transaction(session_data->transaction[SRC_CLIENT], session_data);
103             }
104             else if ((session_data->pipeline_overflow) || (session_data->pipeline_underflow))
105             {
106                 // Pipelining previously broke down and both sides are processed separately from
107                 // now on. We just throw things away when we are done with them.
108                 delete_transaction(session_data->transaction[SRC_CLIENT], session_data);
109             }
110             else if (!session_data->add_to_pipeline(session_data->transaction[SRC_CLIENT]))
111             {
112                 // The pipeline is full and just overflowed.
113                 *session_data->infractions[source_id] += INF_PIPELINE_OVERFLOW;
114                 session_data->events[source_id]->create_event(EVENT_PIPELINE_MAX);
115                 delete_transaction(session_data->transaction[SRC_CLIENT], session_data);
116             }
117         }
118         session_data->transaction[SRC_CLIENT] = new HttpTransaction(session_data);
119 
120         // The StreamSplitter generates infractions related to this transaction while splitting the
121         // request line and keeps them in temporary storage in the FlowData. Now we move them here.
122         session_data->transaction[SRC_CLIENT]->infractions[SRC_CLIENT] =
123             session_data->infractions[SRC_CLIENT];
124         session_data->infractions[SRC_CLIENT] = nullptr;
125     }
126     // This transaction has more than one response. This is a new response which is replacing the
127     // interim response. The two responses cannot coexist so we must clean up the interim response.
128     else if ((session_data->section_type[source_id] == SEC_STATUS) &&
129              (session_data->transaction[SRC_SERVER] != nullptr) &&
130               session_data->transaction[SRC_SERVER]->second_response_expected)
131     {
132         session_data->transaction[SRC_SERVER]->second_response_expected = false;
133         session_data->transaction[SRC_SERVER]->discard_section(
134             session_data->transaction[SRC_SERVER]->status);
135         session_data->transaction[SRC_SERVER]->status = nullptr;
136         session_data->transaction[SRC_SERVER]->discard_section(
137             session_data->transaction[SRC_SERVER]->header[SRC_SERVER]);
138         session_data->transaction[SRC_SERVER]->header[SRC_SERVER] = nullptr;
139     }
140     // Status section: delete the current transaction and get a new one from the pipeline. If the
141     // pipeline is empty check for a request transaction and take it. If there is no transaction
142     // available then declare an underflow and create a new transaction specifically for the
143     // response side.
144     else if (session_data->section_type[source_id] == SEC_STATUS)
145     {
146         delete_transaction(session_data->transaction[SRC_SERVER], session_data);
147         if (session_data->pipeline_underflow)
148         {
149             // A previous underflow separated the two sides forever
150             session_data->transaction[SRC_SERVER] = new HttpTransaction(session_data);
151         }
152         else if ((session_data->transaction[SRC_SERVER] = session_data->take_from_pipeline()) ==
153             nullptr)
154         {
155             if ((session_data->transaction[SRC_CLIENT] == nullptr) ||
156                 (session_data->transaction[SRC_CLIENT]->response_seen))
157             {
158                 // Either there is no request at all or there is a request but a previous response
159                 // already took it. Either way we have more responses than requests.
160                 session_data->pipeline_underflow = true;
161                 session_data->transaction[SRC_SERVER] = new HttpTransaction(session_data);
162             }
163 
164             else if (session_data->type_expected[SRC_CLIENT] == SEC_REQUEST)
165             {
166                 // This is the normal case where the requests and responses are alternating (no
167                 // pipelining). Processing of the response is complete so the request just takes
168                 // it.
169                 session_data->transaction[SRC_SERVER] = session_data->transaction[SRC_CLIENT];
170                 session_data->transaction[SRC_CLIENT] = nullptr;
171             }
172             else
173             {
174                 // Response message is starting before the request message has finished. Request
175                 // side is not finished with this transaction so two sides share it
176                 session_data->transaction[SRC_CLIENT]->shared_ownership = true;
177                 session_data->transaction[SRC_SERVER] = session_data->transaction[SRC_CLIENT];
178             }
179         }
180         session_data->transaction[SRC_SERVER]->response_seen = true;
181 
182         // Move in server infractions now that the response is attached here
183         session_data->transaction[SRC_SERVER]->infractions[SRC_SERVER] =
184             session_data->infractions[SRC_SERVER];
185         session_data->infractions[SRC_SERVER] = nullptr;
186     }
187 
188     assert(session_data->transaction[source_id] != nullptr);
189     session_data->transaction[source_id]->active_sections++;
190     return session_data->transaction[source_id];
191 }
192 
discard_section(HttpMsgSection * section)193 void HttpTransaction::discard_section(HttpMsgSection* section)
194 {
195     if (section != nullptr)
196     {
197         section->next = discard_list;
198         discard_list = section;
199     }
200 }
201 
clear_section()202 void HttpTransaction::clear_section()
203 {
204     assert(active_sections > 0);
205     active_sections--;
206 }
207 
garbage_collect()208 void HttpTransaction::garbage_collect()
209 {
210     HttpMsgSection** current = (HttpMsgSection**)&body_list;
211     while (*current != nullptr)
212     {
213         if ((*current)->is_clear())
214         {
215             HttpMsgSection* tmp = *current;
216             *current = (*current)->next;
217             delete tmp;
218         }
219         else
220             current = &(*current)->next;
221     }
222 }
223 
delete_transaction(HttpTransaction * transaction,HttpFlowData * session_data)224 void HttpTransaction::delete_transaction(HttpTransaction* transaction, HttpFlowData* session_data)
225 {
226     if (transaction != nullptr)
227     {
228         if (!transaction->shared_ownership)
229         {
230             if ((transaction->active_sections > 0) && (session_data != nullptr))
231             {
232                 transaction->next = session_data->discard_list;
233                 session_data->discard_list = transaction;
234             }
235             else
236                 delete transaction;
237         }
238         else
239             transaction->shared_ownership = false;
240     }
241 }
242 
set_body(HttpMsgBody * latest_body)243 void HttpTransaction::set_body(HttpMsgBody* latest_body)
244 {
245     latest_body->next = body_list;
246     body_list = latest_body;
247 }
248 
get_infractions(SourceId source_id)249 HttpInfractions* HttpTransaction::get_infractions(SourceId source_id)
250 {
251     return infractions[source_id];
252 }
253 
set_one_hundred_response()254 void HttpTransaction::set_one_hundred_response()
255 {
256     assert(response_seen);
257     if (one_hundred_response)
258     {
259         *infractions[SRC_SERVER] += INF_MULTIPLE_100_RESPONSES;
260         session_data->events[SRC_SERVER]->create_event(EVENT_MULTIPLE_100_RESPONSES);
261     }
262     one_hundred_response = true;
263     second_response_expected = true;
264 }
265