]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: fcgi: Add code related to FCGI protocol
authorChristopher Faulet <cfaulet@haproxy.com>
Sun, 11 Aug 2019 21:08:53 +0000 (23:08 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Tue, 17 Sep 2019 08:18:54 +0000 (10:18 +0200)
This code is independant and is only responsible to encode and decode part of
the FCGI protocol.

Makefile
include/common/fcgi.h [new file with mode: 0644]
src/fcgi.c [new file with mode: 0644]

index 87b5b82f3cf63c8b96c834119bb1259bf6fe2a83..0d760b797d03b72d24b9303be904a210582f6e62 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -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 \
diff --git a/include/common/fcgi.h b/include/common/fcgi.h
new file mode 100644 (file)
index 0000000..3560e0c
--- /dev/null
@@ -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:
+ */
diff --git a/src/fcgi.c b/src/fcgi.c
new file mode 100644 (file)
index 0000000..33aa2a2
--- /dev/null
@@ -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:
+ */