1 /*
2  * nghttp2 - HTTP/2 C Library
3  *
4  * Copyright (c) 2016 Tatsuhiro Tsujikawa
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25 #include "shrpx_api_downstream_connection.h"
26 
27 #include <sys/mman.h>
28 #include <fcntl.h>
29 #include <unistd.h>
30 #include <cstdlib>
31 
32 #include "shrpx_client_handler.h"
33 #include "shrpx_upstream.h"
34 #include "shrpx_downstream.h"
35 #include "shrpx_worker.h"
36 #include "shrpx_connection_handler.h"
37 #include "shrpx_log.h"
38 
39 namespace shrpx {
40 
41 namespace {
42 // List of API endpoints
apis()43 const std::array<APIEndpoint, 2> &apis() {
44   static const auto apis = new std::array<APIEndpoint, 2>{
45       APIEndpoint{
46           StringRef::from_lit("/api/v1beta1/backendconfig"),
47           true,
48           (1 << API_METHOD_POST) | (1 << API_METHOD_PUT),
49           &APIDownstreamConnection::handle_backendconfig,
50       },
51       APIEndpoint{
52           StringRef::from_lit("/api/v1beta1/configrevision"),
53           true,
54           (1 << API_METHOD_GET),
55           &APIDownstreamConnection::handle_configrevision,
56       },
57   };
58 
59   return *apis;
60 }
61 } // namespace
62 
63 namespace {
64 // The method string.  This must be same order of APIMethod.
65 constexpr StringRef API_METHOD_STRING[] = {
66     StringRef::from_lit("GET"),
67     StringRef::from_lit("POST"),
68     StringRef::from_lit("PUT"),
69 };
70 } // namespace
71 
APIDownstreamConnection(Worker * worker)72 APIDownstreamConnection::APIDownstreamConnection(Worker *worker)
73     : worker_(worker), api_(nullptr), fd_(-1), shutdown_read_(false) {}
74 
~APIDownstreamConnection()75 APIDownstreamConnection::~APIDownstreamConnection() {
76   if (fd_ != -1) {
77     close(fd_);
78   }
79 }
80 
attach_downstream(Downstream * downstream)81 int APIDownstreamConnection::attach_downstream(Downstream *downstream) {
82   if (LOG_ENABLED(INFO)) {
83     DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
84   }
85 
86   downstream_ = downstream;
87 
88   return 0;
89 }
90 
detach_downstream(Downstream * downstream)91 void APIDownstreamConnection::detach_downstream(Downstream *downstream) {
92   if (LOG_ENABLED(INFO)) {
93     DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
94   }
95   downstream_ = nullptr;
96 }
97 
send_reply(unsigned int http_status,APIStatusCode api_status,const StringRef & data)98 int APIDownstreamConnection::send_reply(unsigned int http_status,
99                                         APIStatusCode api_status,
100                                         const StringRef &data) {
101   shutdown_read_ = true;
102 
103   auto upstream = downstream_->get_upstream();
104 
105   auto &resp = downstream_->response();
106 
107   resp.http_status = http_status;
108 
109   auto &balloc = downstream_->get_block_allocator();
110 
111   StringRef api_status_str;
112 
113   switch (api_status) {
114   case APIStatusCode::SUCCESS:
115     api_status_str = StringRef::from_lit("Success");
116     break;
117   case APIStatusCode::FAILURE:
118     api_status_str = StringRef::from_lit("Failure");
119     break;
120   default:
121     assert(0);
122   }
123 
124   constexpr auto M1 = StringRef::from_lit("{\"status\":\"");
125   constexpr auto M2 = StringRef::from_lit("\",\"code\":");
126   constexpr auto M3 = StringRef::from_lit("}");
127 
128   // 3 is the number of digits in http_status, assuming it is 3 digits
129   // number.
130   auto buflen = M1.size() + M2.size() + M3.size() + data.size() +
131                 api_status_str.size() + 3;
132 
133   auto buf = make_byte_ref(balloc, buflen);
134   auto p = buf.base;
135 
136   p = std::copy(std::begin(M1), std::end(M1), p);
137   p = std::copy(std::begin(api_status_str), std::end(api_status_str), p);
138   p = std::copy(std::begin(M2), std::end(M2), p);
139   p = util::utos(p, http_status);
140   p = std::copy(std::begin(data), std::end(data), p);
141   p = std::copy(std::begin(M3), std::end(M3), p);
142 
143   buf.len = p - buf.base;
144 
145   auto content_length = util::make_string_ref_uint(balloc, buf.len);
146 
147   resp.fs.add_header_token(StringRef::from_lit("content-length"),
148                            content_length, false, http2::HD_CONTENT_LENGTH);
149 
150   switch (http_status) {
151   case 400:
152   case 405:
153   case 413:
154     resp.fs.add_header_token(StringRef::from_lit("connection"),
155                              StringRef::from_lit("close"), false,
156                              http2::HD_CONNECTION);
157     break;
158   }
159 
160   if (upstream->send_reply(downstream_, buf.base, buf.len) != 0) {
161     return -1;
162   }
163 
164   return 0;
165 }
166 
167 namespace {
lookup_api(const StringRef & path)168 const APIEndpoint *lookup_api(const StringRef &path) {
169   switch (path.size()) {
170   case 26:
171     switch (path[25]) {
172     case 'g':
173       if (util::streq_l("/api/v1beta1/backendconfi", std::begin(path), 25)) {
174         return &apis()[0];
175       }
176       break;
177     }
178     break;
179   case 27:
180     switch (path[26]) {
181     case 'n':
182       if (util::streq_l("/api/v1beta1/configrevisio", std::begin(path), 26)) {
183         return &apis()[1];
184       }
185       break;
186     }
187     break;
188   }
189   return nullptr;
190 }
191 } // namespace
192 
push_request_headers()193 int APIDownstreamConnection::push_request_headers() {
194   auto &req = downstream_->request();
195 
196   auto path =
197       StringRef{std::begin(req.path),
198                 std::find(std::begin(req.path), std::end(req.path), '?')};
199 
200   api_ = lookup_api(path);
201 
202   if (!api_) {
203     send_reply(404, APIStatusCode::FAILURE);
204 
205     return 0;
206   }
207 
208   switch (req.method) {
209   case HTTP_GET:
210     if (!(api_->allowed_methods & (1 << API_METHOD_GET))) {
211       error_method_not_allowed();
212       return 0;
213     }
214     break;
215   case HTTP_POST:
216     if (!(api_->allowed_methods & (1 << API_METHOD_POST))) {
217       error_method_not_allowed();
218       return 0;
219     }
220     break;
221   case HTTP_PUT:
222     if (!(api_->allowed_methods & (1 << API_METHOD_PUT))) {
223       error_method_not_allowed();
224       return 0;
225     }
226     break;
227   default:
228     error_method_not_allowed();
229     return 0;
230   }
231 
232   // This works with req.fs.content_length == -1
233   if (req.fs.content_length >
234       static_cast<int64_t>(get_config()->api.max_request_body)) {
235     send_reply(413, APIStatusCode::FAILURE);
236 
237     return 0;
238   }
239 
240   switch (req.method) {
241   case HTTP_POST:
242   case HTTP_PUT: {
243     char tempname[] = "/tmp/nghttpx-api.XXXXXX";
244 #ifdef HAVE_MKOSTEMP
245     fd_ = mkostemp(tempname, O_CLOEXEC);
246 #else  // !HAVE_MKOSTEMP
247     fd_ = mkstemp(tempname);
248 #endif // !HAVE_MKOSTEMP
249     if (fd_ == -1) {
250       send_reply(500, APIStatusCode::FAILURE);
251 
252       return 0;
253     }
254 #ifndef HAVE_MKOSTEMP
255     util::make_socket_closeonexec(fd_);
256 #endif // HAVE_MKOSTEMP
257     unlink(tempname);
258     break;
259   }
260   }
261 
262   downstream_->set_request_header_sent(true);
263   auto src = downstream_->get_blocked_request_buf();
264   auto dest = downstream_->get_request_buf();
265   src->remove(*dest);
266 
267   return 0;
268 }
269 
error_method_not_allowed()270 int APIDownstreamConnection::error_method_not_allowed() {
271   auto &resp = downstream_->response();
272 
273   size_t len = 0;
274   for (uint8_t i = 0; i < API_METHOD_MAX; ++i) {
275     if (api_->allowed_methods & (1 << i)) {
276       // The length of method + ", "
277       len += API_METHOD_STRING[i].size() + 2;
278     }
279   }
280 
281   assert(len > 0);
282 
283   auto &balloc = downstream_->get_block_allocator();
284 
285   auto iov = make_byte_ref(balloc, len + 1);
286   auto p = iov.base;
287   for (uint8_t i = 0; i < API_METHOD_MAX; ++i) {
288     if (api_->allowed_methods & (1 << i)) {
289       auto &s = API_METHOD_STRING[i];
290       p = std::copy(std::begin(s), std::end(s), p);
291       p = std::copy_n(", ", 2, p);
292     }
293   }
294 
295   p -= 2;
296   *p = '\0';
297 
298   resp.fs.add_header_token(StringRef::from_lit("allow"), StringRef{iov.base, p},
299                            false, -1);
300   return send_reply(405, APIStatusCode::FAILURE);
301 }
302 
push_upload_data_chunk(const uint8_t * data,size_t datalen)303 int APIDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
304                                                     size_t datalen) {
305   if (shutdown_read_ || !api_->require_body) {
306     return 0;
307   }
308 
309   auto &req = downstream_->request();
310   auto &apiconf = get_config()->api;
311 
312   if (static_cast<size_t>(req.recv_body_length) > apiconf.max_request_body) {
313     send_reply(413, APIStatusCode::FAILURE);
314 
315     return 0;
316   }
317 
318   ssize_t nwrite;
319   while ((nwrite = write(fd_, data, datalen)) == -1 && errno == EINTR)
320     ;
321   if (nwrite == -1) {
322     auto error = errno;
323     LOG(ERROR) << "Could not write API request body: errno=" << error;
324     send_reply(500, APIStatusCode::FAILURE);
325 
326     return 0;
327   }
328 
329   // We don't have to call Upstream::resume_read() here, because
330   // request buffer is effectively unlimited.  Actually, we cannot
331   // call it here since it could recursively call this function again.
332 
333   return 0;
334 }
335 
end_upload_data()336 int APIDownstreamConnection::end_upload_data() {
337   if (shutdown_read_) {
338     return 0;
339   }
340 
341   return api_->handler(*this);
342 }
343 
handle_backendconfig()344 int APIDownstreamConnection::handle_backendconfig() {
345   auto &req = downstream_->request();
346 
347   if (req.recv_body_length == 0) {
348     send_reply(200, APIStatusCode::SUCCESS);
349 
350     return 0;
351   }
352 
353   auto rp = mmap(nullptr, req.recv_body_length, PROT_READ, MAP_SHARED, fd_, 0);
354   if (rp == reinterpret_cast<void *>(-1)) {
355     send_reply(500, APIStatusCode::FAILURE);
356     return 0;
357   }
358 
359   auto unmapper = defer(munmap, rp, req.recv_body_length);
360 
361   Config new_config{};
362   new_config.conn.downstream = std::make_shared<DownstreamConfig>();
363   const auto &downstreamconf = new_config.conn.downstream;
364 
365   auto config = get_config();
366   auto &src = config->conn.downstream;
367 
368   downstreamconf->timeout = src->timeout;
369   downstreamconf->connections_per_host = src->connections_per_host;
370   downstreamconf->connections_per_frontend = src->connections_per_frontend;
371   downstreamconf->request_buffer_size = src->request_buffer_size;
372   downstreamconf->response_buffer_size = src->response_buffer_size;
373   downstreamconf->family = src->family;
374 
375   std::set<StringRef> include_set;
376   std::map<StringRef, size_t> pattern_addr_indexer;
377 
378   for (auto first = reinterpret_cast<const uint8_t *>(rp),
379             last = first + req.recv_body_length;
380        first != last;) {
381     auto eol = std::find(first, last, '\n');
382     if (eol == last) {
383       break;
384     }
385 
386     if (first == eol || *first == '#') {
387       first = ++eol;
388       continue;
389     }
390 
391     auto eq = std::find(first, eol, '=');
392     if (eq == eol) {
393       send_reply(400, APIStatusCode::FAILURE);
394       return 0;
395     }
396 
397     auto opt = StringRef{first, eq};
398     auto optval = StringRef{eq + 1, eol};
399 
400     auto optid = option_lookup_token(opt.c_str(), opt.size());
401 
402     switch (optid) {
403     case SHRPX_OPTID_BACKEND:
404       break;
405     default:
406       first = ++eol;
407       continue;
408     }
409 
410     if (parse_config(&new_config, optid, opt, optval, include_set,
411                      pattern_addr_indexer) != 0) {
412       send_reply(400, APIStatusCode::FAILURE);
413       return 0;
414     }
415 
416     first = ++eol;
417   }
418 
419   auto &tlsconf = config->tls;
420   if (configure_downstream_group(&new_config, config->http2_proxy, true,
421                                  tlsconf) != 0) {
422     send_reply(400, APIStatusCode::FAILURE);
423     return 0;
424   }
425 
426   auto conn_handler = worker_->get_connection_handler();
427 
428   conn_handler->send_replace_downstream(downstreamconf);
429 
430   send_reply(200, APIStatusCode::SUCCESS);
431 
432   return 0;
433 }
434 
handle_configrevision()435 int APIDownstreamConnection::handle_configrevision() {
436   auto config = get_config();
437   auto &balloc = downstream_->get_block_allocator();
438 
439   // Construct the following string:
440   //   ,
441   //   "data":{
442   //     "configRevision": N
443   //   }
444   auto data = concat_string_ref(
445       balloc, StringRef::from_lit(R"(,"data":{"configRevision":)"),
446       util::make_string_ref_uint(balloc, config->config_revision),
447       StringRef::from_lit("}"));
448 
449   send_reply(200, APIStatusCode::SUCCESS, data);
450 
451   return 0;
452 }
453 
pause_read(IOCtrlReason reason)454 void APIDownstreamConnection::pause_read(IOCtrlReason reason) {}
455 
resume_read(IOCtrlReason reason,size_t consumed)456 int APIDownstreamConnection::resume_read(IOCtrlReason reason, size_t consumed) {
457   return 0;
458 }
459 
force_resume_read()460 void APIDownstreamConnection::force_resume_read() {}
461 
on_read()462 int APIDownstreamConnection::on_read() { return 0; }
463 
on_write()464 int APIDownstreamConnection::on_write() { return 0; }
465 
on_upstream_change(Upstream * uptream)466 void APIDownstreamConnection::on_upstream_change(Upstream *uptream) {}
467 
poolable() const468 bool APIDownstreamConnection::poolable() const { return false; }
469 
470 const std::shared_ptr<DownstreamAddrGroup> &
get_downstream_addr_group() const471 APIDownstreamConnection::get_downstream_addr_group() const {
472   static std::shared_ptr<DownstreamAddrGroup> s;
473   return s;
474 }
475 
get_addr() const476 DownstreamAddr *APIDownstreamConnection::get_addr() const { return nullptr; }
477 
478 } // namespace shrpx
479