1 // +------------------------------------------------------------------+
2 // |             ____ _               _        __  __ _  __           |
3 // |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
4 // |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
5 // |           | |___| | | |  __/ (__|   <    | |  | | . \            |
6 // |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
7 // |                                                                  |
8 // | Copyright Mathias Kettner 2014             mk@mathias-kettner.de |
9 // +------------------------------------------------------------------+
10 //
11 // This file is part of Check_MK.
12 // The official homepage is at http://mathias-kettner.de/check_mk.
13 //
14 // check_mk is free software;  you can redistribute it and/or modify it
15 // under the  terms of the  GNU General Public License  as published by
16 // the Free Software Foundation in version 2.  check_mk is  distributed
17 // in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
18 // out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
19 // PARTICULAR PURPOSE. See the  GNU General Public License for more de-
20 // ails.  You should have  received  a copy of the  GNU  General Public
21 // License along with GNU Make; see the file  COPYING.  If  not,  write
22 // to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
23 // Boston, MA 02110-1301 USA.
24 
25 #include "InputBuffer.h"
26 #include <ctype.h>
27 #include <stdint.h>
28 #include <string.h>
29 #include <sys/select.h>
30 #include <sys/time.h>
31 #include <unistd.h>
32 #include "logger.h"
33 
34 using std::list;
35 using std::string;
36 
37 extern int g_query_timeout_msec;
38 extern int g_idle_timeout_msec;
39 
40 namespace {
41 const size_t initial_buffer_size = 4096;
42 // TODO(sp): Make this configurable?
43 const size_t maximum_buffer_size = 500 * 1024 * 1024;
44 
45 const int read_timeout_usec = 200000;
46 
timeout_reached(const struct timeval * start,int timeout_ms)47 bool timeout_reached(const struct timeval *start, int timeout_ms) {
48     if (timeout_ms == 0) {
49         return false;  // timeout disabled
50     }
51 
52     struct timeval now;
53     gettimeofday(&now, nullptr);
54     int64_t elapsed = (now.tv_sec - start->tv_sec) * 1000000;
55     elapsed += now.tv_usec - start->tv_usec;
56     return elapsed / 1000 >= timeout_ms;
57 }
58 }  // namespace
59 
InputBuffer(int fd,const int * termination_flag)60 InputBuffer::InputBuffer(int fd, const int *termination_flag)
61     : _fd(fd)
62     , _termination_flag(termination_flag)
63     , _readahead_buffer(initial_buffer_size) {
64     _read_index = 0;   // points to data not yet processed
65     _write_index = 0;  // points to end of data in buffer
66 }
67 
68 // read in data enough for one complete request (and maybe more).
readRequest()69 InputBuffer::Result InputBuffer::readRequest() {
70     // Remember when we started waiting for a request. This
71     // is needed for the idle_timeout. A connection may
72     // not be idle longer than that value.
73     struct timeval start_of_idle;  // Waiting for the first line
74     gettimeofday(&start_of_idle, nullptr);
75 
76     // Remember if we have read some part of the query. During
77     // a query the timeout is another (short) than between
78     // queries.
79     bool query_started = false;
80 
81     // _read_index points to the place in the buffer, where the
82     // next valid data begins. This data ends at _write_index.
83     // That data might have been read while reading the previous
84     // request.
85 
86     // r is used to find the end of the line
87     size_t r = _read_index;
88 
89     while (true) {
90         // Try to find end of the current line in buffer
91         while (r < _write_index && _readahead_buffer[r] != '\n') {
92             r++;  // now r is at end of data or at '\n'
93         }
94 
95         // If we cannot find the end of line in the data
96         // already read, then we need to read new data from
97         // the client.
98         if (r == _write_index) {
99             // Is there still space left in the buffer => read in
100             // further data into the buffer.
101             if (_write_index < _readahead_buffer.capacity()) {
102                 Result rd =
103                     readData();  // tries to read in further data into buffer
104                 if (rd == Result::timeout) {
105                     if (query_started) {
106                         logger(LG_INFO,
107                                "Timeout of %d ms exceeded while reading query",
108                                g_query_timeout_msec);
109                         return Result::timeout;
110                     }
111                     // Check if we exceeded the maximum time between two queries
112                     if (timeout_reached(&start_of_idle, g_idle_timeout_msec)) {
113                         logger(LG_INFO,
114                                "Idle timeout of %d ms exceeded. Going to close "
115                                "connection.",
116                                g_idle_timeout_msec);
117                         return Result::timeout;
118                     }
119                 }
120 
121                 // Are we at end of file? That is only an error, if we've
122                 // read an incomplete line. If the last thing we read was
123                 // a linefeed, then we consider the current request to
124                 // be valid, if it is not empty.
125                 else if (
126                     rd == Result::eof &&
127                     r == _read_index /* currently at beginning of a line */) {
128                     if (_request_lines.empty()) {
129                         return Result::eof;  // empty request -> no request
130                     }
131                     // socket has been closed but request is complete
132                     return Result::request_read;
133                     // the current state is now:
134                     // _read_index == r == _write_index => buffer is empty
135                     // that way, if the main program tries to read the
136                     // next request, it will get an IB_UNEXPECTED_EOF
137 
138                 }
139                 // if we are *not* at an end of line while reading
140                 // a request, we got an invalid request.
141                 else if (rd == Result::eof) {
142                     return Result::unexpected_eof;
143 
144                     // Other status codes
145                 } else if (rd == Result::should_terminate) {
146                     return rd;
147                 }
148             }
149             // OK. So no space is left in the buffer. But maybe at the
150             // *beginning* of the buffer is space left again. This is
151             // very probable if _write_index == _readahead_buffer.capacity().
152             // Most
153             // of the buffer's content is already processed. So we simply
154             // shift the yet unprocessed data to the very left of the buffer.
155             else if (_read_index > 0) {
156                 int shift_by = _read_index;  // distance to beginning of buffer
157                 int size =
158                     _write_index - _read_index;  // amount of data to shift
159                 memmove(&_readahead_buffer[0], &_readahead_buffer[_read_index],
160                         size);
161                 _read_index = 0;  // unread data is now at the beginning
162                 _write_index -= shift_by;  // write pointer shifted to the left
163                 r -= shift_by;  // current scan position also shift left
164                 // continue -> still no data in buffer, but it will
165                 // be read, as now is space
166             }
167             // buffer is full, but still no end of line found
168             else {
169                 size_t new_capacity = _readahead_buffer.capacity() * 2;
170                 if (new_capacity > maximum_buffer_size) {
171                     logger(LG_INFO,
172                            "Error: maximum length of request line exceeded");
173                     return Result::line_too_long;
174                 }
175                 _readahead_buffer.resize(new_capacity);
176             }
177         } else  // end of line found
178         {
179             if (_read_index == r) {  // empty line found => end of request
180                 _read_index = r + 1;
181                 // Was ist, wenn noch keine korrekte Zeile gelesen wurde?
182                 if (_request_lines.empty()) {
183                     return Result::empty_request;
184                 }
185                 return Result::request_read;
186 
187             }  // non-empty line: belongs to current request
188             int length = r - _read_index;
189             for (size_t end = r; end > _read_index &&
190                                  (isspace(_readahead_buffer[--end]) != 0);) {
191                 length--;
192             }
193             if (length > 0) {
194                 _request_lines.push_back(
195                     string(&_readahead_buffer[_read_index], length));
196             } else {
197                 logger(LG_INFO,
198                        "Warning ignoring line containing only whitespace");
199             }
200             query_started = true;
201             _read_index = r + 1;
202             r = _read_index;
203         }
204     }
205 }
206 
207 // read at least *some* data. Return IB_TIMEOUT if that
208 // lasts more than g_query_timeout_msec msecs.
readData()209 InputBuffer::Result InputBuffer::readData() {
210     struct timeval start;
211     gettimeofday(&start, nullptr);
212 
213     struct timeval tv;
214     while (*_termination_flag == 0) {
215         if (timeout_reached(&start, g_query_timeout_msec)) {
216             return Result::timeout;
217         }
218 
219         tv.tv_sec = read_timeout_usec / 1000000;
220         tv.tv_usec = read_timeout_usec % 1000000;
221 
222         fd_set fds;
223         FD_ZERO(&fds);
224         FD_SET(_fd, &fds);
225 
226         int retval = select(_fd + 1, &fds, nullptr, nullptr, &tv);
227         if (retval > 0 && FD_ISSET(_fd, &fds)) {
228             ssize_t r = read(_fd, &_readahead_buffer[_write_index],
229                              _readahead_buffer.capacity() - _write_index);
230             if (r < 0) {
231                 return Result::eof;
232             }
233             if (r == 0) {
234                 return Result::eof;
235             }
236             _write_index += r;
237             return Result::data_read;
238         }
239     }
240     return Result::should_terminate;
241 }
242 
empty() const243 bool InputBuffer::empty() const { return _request_lines.empty(); }
244 
nextLine()245 string InputBuffer::nextLine() {
246     string s = _request_lines.front();
247     _request_lines.pop_front();
248     return s;
249 }
250