GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/webapi/fcgi.cc Lines: 0 330 0.0 %
Date: 2018-10-07 00:43:30 Branches: 0 195 0.0 %

Line Branch Exec Source
1
/**
2
 * This file is part of the CernVM File System
3
 */
4
5
#include "cvmfs_config.h"
6
#include "fcgi.h"
7
8
#include <arpa/inet.h>
9
#include <errno.h>
10
#include <netinet/in.h>
11
#include <sys/socket.h>
12
#include <sys/un.h>
13
#include <unistd.h>
14
15
#include <algorithm>
16
#include <cassert>
17
#include <cstdlib>
18
19
#include "logging.h"
20
#include "platform.h"
21
#include "util/posix.h"
22
23
using namespace std;  // NOLINT
24
25
26
const unsigned FastCgi::kMaxContentLength = 64 * 1024 - 1;
27
const char *FastCgi::kValueMaxConns = "FCGI_MAX_CONNS";
28
const char *FastCgi::kValueMaxReqs = "FCGI_MAX_REQS";
29
const char *FastCgi::kValueMpxConns = "FCGI_MPXS_CONNS";
30
31
32
/**
33
 * The HTTP client has closed the connection prematurely.
34
 */
35
void FastCgi::AbortRequest() {
36
  if (request_id_ == -1)
37
    return;
38
  ReplyEndRequest(fd_transport_, request_id_, 1, kStatusReqComplete);
39
  if (!keep_connection_) {
40
    CloseConnection();
41
  } else {
42
    global_request_id_++;
43
    request_id_ = -1;
44
    keep_connection_ = false;
45
    params_.clear();
46
  }
47
}
48
49
50
unsigned FastCgi::AddShortKv(
51
  const string &key,
52
  const string &value,
53
  unsigned buf_size,
54
  unsigned char *buf)
55
{
56
  assert((key.length() <= 127) && (value.length() <= 128));
57
  unsigned nbytes = key.length() + value.length() + 2;
58
  assert(buf_size >= nbytes);
59
60
  buf[0] = static_cast<unsigned char>(key.length() & 0xff);
61
  buf[1] = static_cast<unsigned char>(value.length() & 0xff);
62
  memcpy(&buf[2], key.data(), key.length());
63
  memcpy(&buf[2 + key.length()], value.data(), value.length());
64
  return nbytes;
65
}
66
67
68
/**
69
 * Checks if a network connection was received from the allowed set of IP
70
 * addresses (see section 3.2 of fcgi specification).
71
 */
72
bool FastCgi::CheckValidSource(const struct sockaddr_in &addr_in) {
73
  char *env_allow_from = getenv("FCGI_WEB_SERVER_ADDRS");
74
  if ((env_allow_from == NULL) || (*env_allow_from == '\0'))
75
    return true;
76
77
  char *allow_from = strdupa(env_allow_from);
78
  char *cur;
79
  char *next;
80
  for (cur = allow_from; cur != NULL; cur = next) {
81
    next = strchr(cur, ',');
82
    if (next != NULL)
83
      *next++ = '\0';
84
    if (inet_addr(cur) == addr_in.sin_addr.s_addr)
85
      return true;
86
  }
87
  return false;
88
}
89
90
91
void FastCgi::CloseConnection() {
92
  if (fd_transport_ != -1) {
93
    close(fd_transport_);
94
    fd_transport_ = -1;
95
    request_id_ = -1;
96
    keep_connection_ = false;
97
  }
98
  global_request_id_++;
99
  params_.clear();
100
}
101
102
103
string FastCgi::DumpParams() {
104
  if (request_id_ == -1)
105
    return "";
106
107
  string result;
108
  for (map<string, string>::const_iterator i = params_.begin(),
109
       i_end = params_.end(); i != i_end; ++i)
110
  {
111
    result += i->first + "=" + i->second + "\n";
112
  }
113
  return result;
114
}
115
116
117
/**
118
 * Like CGI applications, exit_code != 0 indicates a failed request that is not
119
 * handled through HTTP error return codes.
120
 */
121
void FastCgi::EndRequest(uint32_t exit_code) {
122
  if (request_id_ == -1)
123
    return;
124
125
  ReplyEndRequest(fd_transport_, request_id_, exit_code, kStatusReqComplete);
126
  if (!keep_connection_) {
127
    CloseConnection();
128
  } else {
129
    global_request_id_++;
130
    request_id_ = -1;
131
    keep_connection_ = false;
132
    params_.clear();
133
  }
134
}
135
136
137
FastCgi::FastCgi()
138
    : fd_sock_(kCgiListnsockFileno)
139
    , is_tcp_socket_(false)
140
    , fd_transport_(-1)
141
    , global_request_id_(1)
142
    , request_id_(-1)
143
    , keep_connection_(false)
144
{ }
145
146
147
FastCgi::~FastCgi() {
148
  if (is_tcp_socket_)
149
    close(fd_sock_);
150
}
151
152
153
bool FastCgi::GetParam(const string &key, string *value) {
154
  map<string, string>::const_iterator it = params_.find(key);
155
  if (it != params_.end()) {
156
    *value = it->second;
157
  }
158
  return false;
159
}
160
161
162
/**
163
 * Returns whether the process is running in FastCGI context or as a normal
164
 * process.
165
 */
166
bool FastCgi::IsFcgi() {
167
  union {
168
    struct sockaddr_in in;
169
    struct sockaddr_un un;
170
  } sa;
171
  socklen_t len = sizeof(sa);
172
173
  if ((getpeername(kCgiListnsockFileno, (struct sockaddr *)&sa, &len) != 0) &&
174
      (errno == ENOTCONN))
175
  {
176
    return true;
177
  }
178
  return false;
179
}
180
181
182
/**
183
 * Used to pre-spawn the CGI process for use with nginx.
184
 */
185
bool FastCgi::MkTcpSocket(const string &ip4_address, uint16_t port) {
186
  fd_sock_ = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
187
  if (fd_sock_ < 0)
188
    return false;
189
190
  struct sockaddr_in in_addr;
191
  memset(&in_addr, 0, sizeof(in_addr));
192
  in_addr.sin_family = AF_INET;
193
  in_addr.sin_addr.s_addr = (ip4_address == "") ?
194
                            INADDR_ANY : inet_addr(ip4_address.c_str());
195
  in_addr.sin_port = htons(port);
196
  int retval = bind(fd_sock_, reinterpret_cast<struct sockaddr *>(&in_addr),
197
                    sizeof(in_addr));
198
  if (retval != 0) {
199
    close(fd_sock_);
200
    fd_sock_ = -1;
201
    return false;
202
  }
203
204
  retval = listen(fd_sock_, 4);
205
  if (retval != 0) {
206
    close(fd_sock_);
207
    fd_sock_ = -1;
208
    return false;
209
  }
210
211
  is_tcp_socket_ = true;
212
  return true;
213
}
214
215
216
/**
217
 * Protocol processing as far as the application is not involved.  What is
218
 * received for stdin is pointed to in buf and length.  A different id is set
219
 * for every request.
220
 */
221
FastCgi::Event FastCgi::NextEvent(
222
  unsigned char **buf,
223
  unsigned *length,
224
  uint64_t *id)
225
{
226
  *buf = NULL;
227
  *length = 0;
228
  *id = global_request_id_;
229
230
  while (true) {
231
    if (fd_transport_ == -1) {
232
      sockaddr_in addr_in;
233
      socklen_t addr_len = sizeof(addr_in);
234
      fd_transport_ =
235
        accept(fd_sock_, reinterpret_cast<struct sockaddr *>(&addr_in),
236
               &addr_len);
237
      if (fd_transport_ < 0)
238
        return kEventExit;
239
240
      // For UNIX sockets, addr_in is garbage (but FCGI_WEB_SERVER_ADDRS is not
241
      // defined)
242
      if (is_tcp_socket_ && !CheckValidSource(addr_in)) {
243
        LogCvmfs(kLogCvmfs, kLogSyslogErr,
244
                 "FastCGI connection from invalid source %s",
245
                 inet_ntoa(addr_in.sin_addr));
246
        close(fd_transport_);
247
        fd_transport_ = -1;
248
        continue;
249
      }
250
    }
251
252
    Header header;
253
    if (!ReadHeader(fd_transport_, &header)) {
254
      CloseConnection();
255
      return kEventTransportError;
256
    }
257
258
    uint16_t role;
259
    bool keep_connection;
260
    switch (header.type) {
261
      case kTypeValues:
262
        if (!ProcessValues(header)) {
263
          CloseConnection();
264
          return kEventTransportError;
265
        }
266
        break;
267
268
      case kTypeBegin:
269
        if (!ReadBeginBody(fd_transport_, &role, &keep_connection)) {
270
          CloseConnection();
271
          return kEventTransportError;
272
        }
273
        if (request_id_ != -1) {
274
          ReplyEndRequest(fd_transport_, header.request_id, 0,
275
                          kStatusCantMpxConn);
276
          continue;
277
        }
278
        if (role != kRoleResponder) {
279
          ReplyEndRequest(fd_transport_, header.request_id, 0,
280
                          kStatusUnknownRole);
281
          if (!keep_connection) CloseConnection();
282
          continue;
283
        }
284
        keep_connection_ = keep_connection;
285
        request_id_ = header.request_id;
286
        break;
287
288
      case kTypeAbort:
289
        if (request_id_ != header.request_id) {
290
          CloseConnection();
291
          return kEventTransportError;
292
        }
293
        return kEventAbortReq;
294
        break;
295
296
      case kTypeParams:
297
        if (request_id_ != header.request_id) {
298
          CloseConnection();
299
          return kEventTransportError;
300
        }
301
        if (!ReadParams(header)) {
302
          CloseConnection();
303
          return kEventTransportError;
304
        }
305
        break;
306
307
      case kTypeStdin:
308
        if (request_id_ != header.request_id) {
309
          CloseConnection();
310
          return kEventTransportError;
311
        }
312
        if (!ReadContent(header.content_length, header.padding_length)) {
313
          CloseConnection();
314
          return kEventTransportError;
315
        }
316
        *buf = content_buf_;
317
        *length = header.content_length;
318
        return kEventStdin;
319
        break;
320
321
      case kTypeData:
322
        // Only used in Filter role
323
        CloseConnection();
324
        return kEventTransportError;
325
326
      default:
327
        // Unexpected type
328
        if (header.request_id == 0) {
329
          ReplyUnknownType(fd_transport_, header.type);
330
        } else {
331
          // we did not sign up for other application records
332
          CloseConnection();
333
          return kEventTransportError;
334
        }
335
        continue;
336
    }  // switch (header.type)
337
  }  // accept loop
338
339
  // Never here
340
  return kEventExit;
341
}
342
343
344
bool FastCgi::ParseKvPair(
345
  const char *data,
346
  unsigned len,
347
  unsigned *nparsed,
348
  std::string *key,
349
  std::string *value)
350
{
351
  if (len == 0) return false;
352
  *nparsed = 0;
353
  uint32_t key_length;
354
  if ((data[0] >> 7) == 1) {
355
    if (len < 4) return false;
356
    const unsigned char *key_length_bx =
357
      reinterpret_cast<const unsigned char *>(data);
358
    key_length = static_cast<uint32_t>(key_length_bx[0] & 0x7f) << 24;
359
    key_length += static_cast<uint32_t>(key_length_bx[1]) << 16;
360
    key_length += static_cast<uint32_t>(key_length_bx[2]) << 8;
361
    key_length += static_cast<uint32_t>(key_length_bx[3]);
362
    *nparsed += 4;
363
  } else {
364
    key_length = data[0];
365
    *nparsed += 1;
366
  }
367
368
  if (*nparsed >= len) return false;
369
  uint32_t value_length;
370
  if ((data[*nparsed] >> 7) == 1) {
371
    if (len < *nparsed + 4) return false;
372
    const unsigned char *value_length_bx =
373
      &(reinterpret_cast<const unsigned char *>(data)[*nparsed]);
374
    value_length = static_cast<uint32_t>(value_length_bx[0] & 0x7f) << 24;
375
    value_length += static_cast<uint32_t>(value_length_bx[1]) << 16;
376
    value_length += static_cast<uint32_t>(value_length_bx[2]) << 8;
377
    value_length += static_cast<uint32_t>(value_length_bx[3]);
378
    *nparsed += 4;
379
  } else {
380
    value_length = data[*nparsed];
381
    *nparsed += 1;
382
  }
383
384
  if (len < *nparsed + key_length + value_length) return false;
385
  *key = string(data + *nparsed, key_length);
386
  *nparsed += key_length;
387
  *value = string(data + *nparsed, value_length);
388
  *nparsed += value_length;
389
  return true;
390
}
391
392
393
/**
394
 * Reads the kTypeValues body and replies with a kTypeValuesResult record.
395
 */
396
bool FastCgi::ProcessValues(const Header &request_header) {
397
  unsigned nbytes = request_header.content_length +
398
                    request_header.padding_length;
399
  // Can't use content_buf_ because kTypeValues requests can come anytime
400
  char *data = reinterpret_cast<char *>(alloca(nbytes));
401
  if (nbytes > 0) {
402
    int received = read(fd_transport_, data, nbytes);
403
    if ((received < 0) || (static_cast<unsigned>(received) != nbytes))
404
      return false;
405
  }
406
407
  // Large enough to answer all three kValue... params
408
  const unsigned ret_buf_len = 64;
409
  unsigned char ret_buf[ret_buf_len];
410
  unsigned ret_pos = 0;
411
  unsigned pos = 0;
412
  bool max_conns = false;
413
  bool max_reqs = false;
414
  bool mpx_conns = false;
415
  while (pos < request_header.content_length) {
416
    unsigned nparsed;
417
    string key;
418
    string value;
419
    if (!ParseKvPair(data + pos, request_header.content_length - pos,
420
                     &nparsed, &key, &value))
421
    {
422
      return false;
423
    }
424
    pos += nparsed;
425
426
    if (!max_conns && (key == kValueMaxConns)) {
427
      ret_pos +=
428
        AddShortKv(key, "1", ret_buf_len - ret_pos, ret_buf + ret_pos);
429
      max_conns = true;
430
    }
431
    if (!max_reqs && (key == kValueMaxReqs)) {
432
      ret_pos +=
433
        AddShortKv(key, "1", ret_buf_len - ret_pos, ret_buf + ret_pos);
434
      max_reqs = true;
435
    }
436
    if (!mpx_conns && (key == kValueMpxConns)) {
437
      ret_pos +=
438
        AddShortKv(key, "0", ret_buf_len - ret_pos, ret_buf + ret_pos);
439
      mpx_conns = true;
440
    }
441
  }
442
443
  RawHeader ret_header;
444
  ret_header.type = kTypeValuesResult;
445
  FlattenUint16(ret_pos,
446
                &ret_header.content_length_b1,
447
                &ret_header.content_length_b0);
448
  SafeWrite(fd_transport_, &ret_header, sizeof(ret_header));
449
  SafeWrite(fd_transport_, ret_buf, ret_pos);
450
  return true;
451
}
452
453
454
bool FastCgi::ReadBeginBody(
455
  int fd_transport,
456
  uint16_t *role,
457
  bool *keep_connection)
458
{
459
  BeginRequestBody body;
460
  int nbytes = read(fd_transport, &body, sizeof(body));
461
  if (nbytes != sizeof(body))
462
    return false;
463
464
  *role = MkUint16(body.role_b1, body.role_b0);
465
  *keep_connection = body.flags & kKeepConn;
466
  return true;
467
}
468
469
470
bool FastCgi::ReadContent(uint16_t content_length, unsigned char padding_length)
471
{
472
  uint32_t nbytes = static_cast<uint32_t>(content_length) +
473
                    static_cast<uint32_t>(padding_length);
474
  if (nbytes > 0) {
475
    int received = read(fd_transport_, content_buf_, nbytes);
476
    return ((received >= 0) && (static_cast<unsigned>(received) == nbytes));
477
  }
478
  return true;
479
}
480
481
482
bool FastCgi::ReadHeader(int fd_transport, Header *header) {
483
  RawHeader raw_header;
484
  int nbytes = read(fd_transport, &raw_header, sizeof(raw_header));
485
  if (nbytes != sizeof(raw_header))
486
    return false;
487
  if (raw_header.version != kVersion1)
488
    return false;
489
490
  header->type = raw_header.type;
491
  header->request_id =
492
    MkUint16(raw_header.request_id_b1, raw_header.request_id_b0);
493
  header->content_length =
494
    MkUint16(raw_header.content_length_b1, raw_header.content_length_b0);
495
  header->padding_length = raw_header.padding_length;
496
  return true;
497
}
498
499
500
bool FastCgi::ReadParams(const Header &first_header) {
501
  uint16_t content_length = first_header.content_length;
502
  if (!ReadContent(content_length, first_header.padding_length))
503
  {
504
    return false;
505
  }
506
  string data;
507
  while (content_length > 0) {
508
    data += string(reinterpret_cast<char *>(content_buf_), content_length);
509
510
    Header header;
511
    if (!ReadHeader(fd_transport_, &header))
512
      return false;
513
    if ((header.type != kTypeParams) || (header.request_id != request_id_))
514
      return false;
515
    content_length = header.content_length;
516
    if (!ReadContent(content_length, header.padding_length))
517
      return false;
518
  }
519
520
  const unsigned len = data.length();
521
  unsigned pos = 0;
522
  while (pos < len) {
523
    unsigned nparsed;
524
    string key;
525
    string value;
526
    if (!ParseKvPair(data.data() + pos, len - pos, &nparsed, &key, &value))
527
      return false;
528
    pos += nparsed;
529
    if (key.length() > 0)
530
      params_[key] = value;
531
  }
532
  return true;
533
}
534
535
536
void FastCgi::ReplyEndRequest(
537
  int fd_transport,
538
  uint16_t req_id,
539
  uint32_t exit_code,
540
  unsigned char status)
541
{
542
  struct {
543
    RawHeader raw_header;
544
    EndRequestBody body;
545
  } reply;
546
  reply.raw_header.type = kTypeEnd;
547
  FlattenUint16(req_id,
548
                &reply.raw_header.request_id_b1,
549
                &reply.raw_header.request_id_b0);
550
  reply.body.protocol_status = status;
551
  reply.body.app_status_b3 =
552
    static_cast<unsigned char>((exit_code >> 24) & 0xff);
553
  reply.body.app_status_b2 =
554
    static_cast<unsigned char>((exit_code >> 16) & 0xff);
555
  reply.body.app_status_b1 =
556
    static_cast<unsigned char>((exit_code >> 8) & 0xff);
557
  reply.body.app_status_b0 = static_cast<unsigned char>(exit_code & 0xff);
558
  SafeWrite(fd_transport, &reply, sizeof(reply));
559
}
560
561
562
bool FastCgi::ReplyStream(
563
  unsigned char type,
564
  const unsigned char *data,
565
  unsigned length)
566
{
567
  assert((type == kTypeStdout) || (type == kTypeStderr));
568
569
  RawHeader raw_header;
570
  raw_header.type = type;
571
  FlattenUint16(request_id_,
572
                &raw_header.request_id_b1, &raw_header.request_id_b0);
573
  if (length == 0) {
574
    return (write(fd_transport_, &raw_header, sizeof(raw_header)) ==
575
            sizeof(raw_header));
576
  }
577
578
  unsigned written = 0;
579
  while (written < length) {
580
    unsigned nbytes = std::min(length - written, kMaxContentLength);
581
    FlattenUint16(nbytes,
582
                  &raw_header.content_length_b1,
583
                  &raw_header.content_length_b0);
584
    int retval = write(fd_transport_, &raw_header, sizeof(raw_header));
585
    if (retval != sizeof(raw_header))
586
      return false;
587
    retval = write(fd_transport_, data + written, nbytes);
588
    if ((retval < 0) || (static_cast<unsigned>(retval) != nbytes))
589
      return false;
590
    written += nbytes;
591
  }
592
  return true;
593
}
594
595
596
void FastCgi::ReplyUnknownType(int fd_transport, unsigned char received_type) {
597
  struct {
598
    RawHeader raw_header;
599
    UnknownTypeBody body;
600
  } reply;
601
  reply.raw_header.type = kTypeUnknown;
602
  reply.body.type = received_type;
603
  SafeWrite(fd_transport, &reply, sizeof(reply));
604
}
605
606
607
void FastCgi::ReturnBadRequest(const std::string &reason) {
608
  const string response = "Status: 400 Bad Request\r\n"
609
    "Content-type: text/plain\r\n\r\n" +
610
    reason + "\n";
611
  SendData(response, true);
612
  EndRequest(0);
613
}
614
615
616
void FastCgi::ReturnNotFound() {
617
  const string response = "Status: 404 Not Found\r\n\r\n";
618
  SendData(response, true);
619
  EndRequest(0);
620
}
621
622
623
/**
624
 * The HTTP body.  Indicate the end of the stream with finish.
625
 */
626
bool FastCgi::SendData(const string &data, bool finish) {
627
  if (request_id_ == -1)
628
    return false;
629
630
  if (!ReplyStream(kTypeStdout, reinterpret_cast<const unsigned char *>(
631
                   data.data()), data.length()))
632
  {
633
    return false;
634
  }
635
  if (finish)
636
    return ReplyStream(kTypeStdout, NULL, 0);
637
  return true;
638
}
639
640
641
/**
642
 * What would normally be sent to stderr. Indicate the end of the stream with
643
 * finish.
644
 */
645
bool FastCgi::SendError(const string &data, bool finish) {
646
  if (request_id_ == -1)
647
    return false;
648
649
  if (!ReplyStream(kTypeStderr, reinterpret_cast<const unsigned char *>(
650
                   data.data()), data.length()))
651
  {
652
    return false;
653
  }
654
  if (finish)
655
    return ReplyStream(kTypeStderr, NULL, 0);
656
  return true;
657
}