MINOR: fcgi: Add code related to FCGI protocol

This code is independant and is only responsible to encode and decode part of
the FCGI protocol.
This commit is contained in:
Christopher Faulet 2019-08-11 23:08:53 +02:00
parent 86d144c74b
commit 63bbf284a1
3 changed files with 411 additions and 1 deletions

View File

@ -787,7 +787,7 @@ OBJS = src/http_ana.o src/cfgparse-listen.o src/stream.o \
src/protocol.o src/arg.o src/hpack-huff.o src/base64.o src/ring.o \
src/hash.o src/mailers.o src/activity.o src/version.o src/trace.o \
src/mworker.o src/mworker-prog.o src/debug.o src/wdt.o src/dict.o \
src/xprt_handshake.o src/h1_htx.o
src/xprt_handshake.o src/h1_htx.o src/fcgi.o
EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o $(EBTREE_DIR)/eb32sctree.o \
$(EBTREE_DIR)/eb32tree.o $(EBTREE_DIR)/eb64tree.o \

117
include/common/fcgi.h Normal file
View File

@ -0,0 +1,117 @@
/*
* include/common/fcgi.h
* This file contains FastCGI protocol definitions.
*
* Copyright (C) 2019 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, version 2.1
* exclusively.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _COMMON_FCGI_H
#define _COMMON_FCGI_H
#include <inttypes.h>
#include <stdio.h>
#include <common/config.h>
#include <common/standard.h>
#include <common/buf.h>
#include <common/ist.h>
/* FCGI protocol version */
#define FCGI_VERSION 0x1
/* flags for FCGI_BEGIN_REQUEST records */
#define FCGI_KEEP_CONN 0x01
/* FCGI record's type */
enum fcgi_record_type {
FCGI_BEGIN_REQUEST = 1,
FCGI_ABORT_REQUEST = 2,
FCGI_END_REQUEST = 3,
FCGI_PARAMS = 4,
FCGI_STDIN = 5,
FCGI_STDOUT = 6,
FCGI_STDERR = 7,
FCGI_DATA = 8,
FCGI_GET_VALUES = 9,
FCGI_GET_VALUES_RESULT = 10,
FCGI_UNKNOWN_TYPE = 11,
FCGI_ENTRIES
} __attribute__((packed));
enum fcgi_role {
FCGI_RESPONDER = 1,
FCGI_AUTHORIZER = 2, /* Unsupported */
FCGI_FILTER = 3, /* Unsupported */
} __attribute__((packed));
/* Protocol status */
enum fcgi_proto_status {
FCGI_PS_REQUEST_COMPLETE = 0,
FCGI_PS_CANT_MPX_CONN = 1,
FCGI_PS_OVERLOADED = 2,
FCGI_PS_UNKNOWN_ROLE = 3,
FCGI_PS_ENTRIES,
} __attribute__((packed));
struct fcgi_header {
uint8_t vsn;
uint8_t type;
uint16_t id;
uint16_t len;
uint8_t padding;
uint8_t rsv;
};
struct fcgi_param {
struct ist n;
struct ist v;
};
struct fcgi_begin_request {
enum fcgi_role role;
uint8_t flags;
};
struct fcgi_end_request {
uint32_t status;
uint8_t errcode;
};
struct fcgi_unknown_type {
uint8_t type;
};
int fcgi_encode_record_hdr(struct buffer *out, const struct fcgi_header *h);
size_t fcgi_decode_record_hdr(const struct buffer *in, size_t o, struct fcgi_header *h);
int fcgi_encode_begin_request(struct buffer *out, const struct fcgi_begin_request *r);
int fcgi_encode_param(struct buffer *out, const struct fcgi_param *p);
size_t fcgi_decode_param(const struct buffer *in, size_t o, struct fcgi_param *p);
size_t fcgi_aligned_decode_param(const struct buffer *in, size_t o, struct fcgi_param *p);
size_t fcgi_decode_end_request(const struct buffer *in, size_t o, struct fcgi_end_request *r);
#endif /* _COMMON_FCGI_H */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/

293
src/fcgi.c Normal file
View File

@ -0,0 +1,293 @@
/*
* FastCGI protocol processing
*
* Copyright (C) 2019 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <common/fcgi.h>
/* Encodes header of a FCGI record into the chunk <out>. It returns non-zero on
* success and 0 on failure (buffer full). <out> is a chunk, so the wrapping is
* not handled by this function. It is the caller responsibility to ensure
* enough contiguous space is available
*/
int fcgi_encode_record_hdr(struct buffer *out, const struct fcgi_header *h)
{
size_t len = out->data;
if (len + 8 >= b_size(out))
return 0;
out->area[len++] = h->vsn;
out->area[len++] = h->type;
out->area[len++] = ((h->id >> 8) & 0xff);
out->area[len++] = (h->id & 0xff);
out->area[len++] = ((h->len >> 8) & 0xff);
out->area[len++] = (h->len & 0xff);
out->area[len++] = h->padding;
len++; /* rsv */
out->data = len;
return 1;
}
/* Decodes a FCGI record header from offset <o> of buffer <in> into descriptor
* <h>. The buffer may wrap so each byte read must be checked. The header is
* formed like this :
*
* b0 b1 b2 b3 b4 b5 b6 b7
* +-----+------+-----+-----+------+------+--------+-----+
* | vsn | type | id1 | id0 | len1 | len0 | padlen | rsv |
* +-----+------+-----+-----+------+------+--------+-----+
*
* Returns zero if some bytes are missing, otherwise the number of read bytes.
*/
size_t fcgi_decode_record_hdr(const struct buffer *in, size_t o, struct fcgi_header *h)
{
if (b_data(in) < o + 8)
return 0;
h->vsn = (uint8_t)(*b_peek(in, o));
h->type = (uint8_t)(*b_peek(in, o+1));
h->id = ((uint8_t)(*b_peek(in, o+2)) << 8) + (uint8_t)(*b_peek(in, o+3));
h->len = ((uint8_t)(*b_peek(in, o+4)) << 8) + (uint8_t)(*b_peek(in, o+5));
h->padding = (uint8_t)(*b_peek(in, o+6));
/* ignore rsv */
return 8;
}
/* Encodes the payload part of a BEGIN_REQUEST record into the chunk <out>. It
* returns non-zero on success and 0 on failure (buffer full). <out> is a chunk,
* so the wrapping is not handled by this function. It is the caller
* responsibility to ensure enough contiguous space is available
*/
int fcgi_encode_begin_request(struct buffer *out, const struct fcgi_begin_request *r)
{
size_t len = out->data;
if (len + 8 >= b_size(out))
return 0;
out->area[len++] = ((r->role >> 8) & 0xff);
out->area[len++] = (r->role & 0xff);
out->area[len++] = r->flags;
len += 5; /* rsv */
out->data = len;
return 1;
}
/* Encodes a parameter, part of the payload of a PARAM record, into the chunk
* <out>. It returns non-zero on success and 0 on failure (buffer full). <out>
* is a chunk, so the wrapping is not handled by this function. It is the caller
* responsibility to ensure enough contiguous space is available. The
* parameter's name is converted to upper case and non-alphanumeric character
* are replaced by an underscore.
*/
int fcgi_encode_param(struct buffer *out, const struct fcgi_param *p)
{
size_t off, len = out->data;
int nbytes, vbytes;
nbytes = (!(p->n.len >> 7) ? 1 : 4);
vbytes = (!(p->v.len >> 7) ? 1 : 4);
if ((len + nbytes + p->n.len + vbytes + p->v.len) >= b_size(out))
return 0;
if (nbytes == 1)
out->area[len++] = (p->n.len & 0xff);
else {
out->area[len++] = (((p->n.len >> 24) & 0xff) | 0x80);
out->area[len++] = ((p->n.len >> 16) & 0xff);
out->area[len++] = ((p->n.len >> 8) & 0xff);
out->area[len++] = (p->n.len & 0xff);
}
if (vbytes == 1)
out->area[len++] = (p->v.len & 0xff);
else {
out->area[len++] = (((p->v.len >> 24) & 0xff) | 0x80);
out->area[len++] = ((p->v.len >> 16) & 0xff);
out->area[len++] = ((p->v.len >> 8) & 0xff);
out->area[len++] = (p->v.len & 0xff);
}
for (off = 0; off < p->n.len; off++) {
if (isalnum((int)p->n.ptr[off]))
out->area[len++] = ist_uc[(unsigned char)p->n.ptr[off]];
else
out->area[len++] = '_';
}
if (p->v.len) {
ist2bin(out->area + len, p->v);
len += p->v.len;
}
out->data = len;
return 1;
}
/* Decodes a parameter of a PARAM record from offset <o> of buffer <in> into the
* FCGI param <p>. The buffer may wrap so each byte read must be checked.
* Returns zero if some bytes are missing, otherwise the number of read bytes.
*/
size_t fcgi_decode_param(const struct buffer *in, size_t o, struct fcgi_param *p)
{
size_t data = b_data(in);
size_t nlen, vlen, len = 0;
uint8_t b0, b1, b2, b3;
if (data < o + 1)
return 0;
b0 = *b_peek(in, o++);
if (!(b0 >> 7)) {
nlen = b0;
len++;
}
else {
if (data < o + 3)
return 0;
b1 = *b_peek(in, o++);
b2 = *b_peek(in, o++);
b3 = *b_peek(in, o++);
nlen = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
len += 4;
}
if (data < o + 1)
return 0;
b0 = *b_peek(in, o++);
if (!(b0 >> 7)) {
vlen = b0;
len++;
}
else {
if (data < o + 3)
return 0;
b1 = *b_peek(in, o++);
b2 = *b_peek(in, o++);
b3 = *b_peek(in, o++);
vlen = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
len += 4;
}
if (data < nlen + vlen)
return 0;
p->n.ptr = b_peek(in, o);
p->n.len = nlen;
p->v.ptr = b_peek(in, o+nlen);
p->v.len = vlen;
len += nlen + vlen;
return len;
}
/* Decodes a parameter of a PARAM record from offset <o> of buffer <in> into the
* FCGI param <p>. To call this function, the buffer must not wrap. Returns zero
* if some bytes are missing, otherwise the number of read bytes.
*/
size_t fcgi_aligned_decode_param(const struct buffer *in, size_t o, struct fcgi_param *p)
{
size_t data = b_data(in);
size_t nlen, vlen, len = 0;
uint8_t b0, b1, b2, b3;
if (data < o + 1)
return 0;
b0 = in->area[o++];
if (!(b0 >> 7)) {
nlen = b0;
len++;
}
else {
if (data < o + 3)
return 0;
b1 = in->area[o++];
b2 = in->area[o++];
b3 = in->area[o++];
nlen = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
len += 4;
}
if (data < o + 1)
return 0;
b0 = in->area[o++];
if (!(b0 >> 7)) {
vlen = b0;
len++;
}
else {
if (data < o + 3)
return 0;
b1 = in->area[o++];
b2 = in->area[o++];
b3 = in->area[o++];
vlen = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
len += 4;
}
if (data < nlen + vlen)
return 0;
p->n.ptr = in->area + o;
p->n.len = nlen;
p->v.ptr = in->area + o + nlen;
p->v.len = vlen;
len += nlen + vlen;
return len;
}
/* Decodes payload of a END_REQUEST record from offset <o> of buffer <in> into
* the FCGI param <p>. The buffer may wrap so each byte read must be
* checked. Returns zero if some bytes are missing, otherwise the number of read
* bytes.
*/
size_t fcgi_decode_end_request(const struct buffer *in, size_t o, struct fcgi_end_request *rec)
{
uint8_t b0, b1, b2, b3;
if (b_data(in) < o + 8)
return 0;
b0 = *b_peek(in, o++);
b1 = *b_peek(in, o++);
b2 = *b_peek(in, o++);
b3 = *b_peek(in, o++);
rec->status = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
rec->errcode = *b_peek(in, o++);
o += 3; /* ignore rsv */
return 8;
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/