#define FCGI_BEGIN_REQUEST 1 #define FCGI_ABORT_REQUEST 2 #define FCGI_END_REQUEST 3 #define FCGI_PARAMS 4 #define FCGI_STDIN 5 #define FCGI_STDOUT 6 #define FCGI_STDERR 7 #define FCGI_DATA 8 #define FCGI_GET_VALUES 9 #define FCGI_GET_VALUES_RESULT 10 #define FCGI_UNKNOWN_TYPE 11 #define FUFE_OK 0 #define FUFE_EOF -1 /* unexpected protocol-level EOF */ #define FUFE_IO -2 #define FUFE_PROTO -3 #define FUFE_PLEN -4 #define FUFE_CLEN -5 #define FUFE_ABORT -6 /* explicit abort or client-level EOF */ #define FUFE_NOREQ -7 /* protocol-level EOF before we received anything */ #define FUFCGI_MAX_DATA 65535 typedef struct { SV *self; int fd; int maxproc; int keepconn; int reqid; HV *headers; HV *params; /* Single buffer for reading & writing, we only do one thing at a time */ char buf[8 + FUFCGI_MAX_DATA + 255]; /* fits a maximum-length fcgi record */ int len; /* total number of bytes in the buffer */ int off; /* number of bytes consumed */ } fufcgi; typedef struct { unsigned char type; unsigned short id; int len; char *data; } fufcgi_rec; /* Incremental param length & name parser */ typedef enum { FUFC_INIT, FUFC_L1, FUFC_L2, FUFC_L3, FUFC_V0, FUFC_V1, FUFC_V2, FUFC_V3, FUFC_N0, FUFC_NX } fufcgi_paramstate; typedef struct { int namelen; int vallen; int state; int namerd; char *name; char namebuf[128]; /* We don't support longer param names */ } fufcgi_param; /* Returns NULL on error or ptr to value (or 'end' if !done) */ static char *fufcgi_param_parse(fufcgi_param *p, char *buf, char *end) { while (buf < end) { switch (p->state) { case FUFC_INIT: p->vallen = p->namerd = 0; if (*buf & 0x80) { p->namelen = (*buf & 0x1f) << 24; p->state = FUFC_L1; } else { p->namelen = *buf; p->state = FUFC_V0; } break; case FUFC_L1: p->namelen |= ((unsigned char)*buf) << 16; p->state = FUFC_L2; break; case FUFC_L2: p->namelen |= ((unsigned char)*buf) << 8; p->state = FUFC_L3; break; case FUFC_L3: p->namelen |= (unsigned char)*buf; p->state = FUFC_V0; if (p->namelen > (int)sizeof(p->namebuf)) return NULL; break; case FUFC_V0: if (*buf & 0x80) { p->vallen = (*buf & 0x1f) << 24; p->state = FUFC_V1; } else { p->vallen = *buf; p->state = p->namelen ? FUFC_N0 : FUFC_INIT; } break; case FUFC_V1: p->vallen |= ((unsigned char)*buf) << 16; if (p->vallen) return NULL; /* Let's just disallow param values > 64 KiB */ p->state = FUFC_V2; break; case FUFC_V2: p->vallen |= ((unsigned char)*buf) << 8; p->state = FUFC_V3; break; case FUFC_V3: p->vallen |= (unsigned char)*buf; p->state = FUFC_N0; break; case FUFC_N0: if (p->namelen <= end - buf) { p->name = buf; p->state = FUFC_INIT; return buf + p->namelen; } else { p->name = p->namebuf; p->name[0] = *buf; p->namerd = 1; p->state = FUFC_NX; } break; case FUFC_NX: p->name[p->namerd++] = *buf; if (p->namerd == p->namelen) { p->state = FUFC_INIT; return buf + 1; } break; } buf++; } return buf; } static int fufcgi_fill(fufcgi *ctx, int len) { if ((int)sizeof(ctx->buf) - ctx->off < len) { memmove(ctx->buf, ctx->buf+ctx->off, ctx->len - ctx->off); ctx->len -= ctx->off; ctx->off = 0; } while (ctx->len - ctx->off < len) { ssize_t r = read(ctx->fd, ctx->buf+ctx->len, sizeof(ctx->buf) - ctx->len); if (r <= 0) return r == 0 ? FUFE_EOF : FUFE_IO; ctx->len += r; } return FUFE_OK; } static int fufcgi_read_record(fufcgi *ctx, fufcgi_rec *rec) { int r; if ((r = fufcgi_fill(ctx, 8)) != FUFE_OK) return r; if (ctx->buf[ctx->off] != 1) return FUFE_PROTO; /* version */ rec->type = ctx->buf[ctx->off+1]; rec->id = fu_frombeU(16, ctx->buf+ctx->off+2); rec->len = fu_frombeU(16, ctx->buf+ctx->off+4); int pad = (unsigned char)ctx->buf[ctx->off+6]; ctx->off += 8; if ((r = fufcgi_fill(ctx, rec->len + pad)) != FUFE_OK) return r; rec->data = ctx->buf + ctx->off; ctx->off += rec->len + pad; return FUFE_OK; } /* Unbuffered write of a single record, first 8 bytes of 'buf' are filled out * by this function, record contents must come after. */ static int fufcgi_write_record(fufcgi *ctx, fufcgi_rec *hdr, char *buf) { buf[0] = 1; buf[1] = hdr->type; fu_tobeU(16, buf+2, hdr->id); fu_tobeU(16, buf+4, hdr->len); buf[6] = 0; buf[7] = 0; int len = hdr->len + 8; while (len > 0) { int r = write(ctx->fd, buf, len); if (r <= 0) return r == 0 ? FUFE_EOF : FUFE_IO; buf += r; len -= r; } return FUFE_OK; } static int fufcgi_handle_values(fufcgi *ctx, fufcgi_rec *rec, char *buf) { int reslen = 8; char *param = rec->data; char *end = rec->data + rec->len; fufcgi_param p; p.state = FUFC_INIT; while (param < end) { if ((param = fufcgi_param_parse(&p, param, end)) == NULL) return FUFE_PLEN; if (p.state != FUFC_INIT) return FUFE_PROTO; if (p.vallen > end - param) return FUFE_PROTO; if (reslen >= 100) return FUFE_PROTO; /* implies requested params were duplicated */ if (p.namelen == 14 && memcmp(p.name, "FCGI_MAX_CONNS", 14) == 0) { memcpy(buf+reslen, "\x0e\0FCGI_MAX_CONNS", 16); int l = sprintf(buf+reslen+16, "%d", ctx->maxproc); buf[reslen+1] = l; reslen += 16 + l; } else if (p.namelen == 13 && memcmp(p.name, "FCGI_MAX_REQS", 13) == 0) { memcpy(buf+reslen, "\x0d\0FCGI_MAX_REQS", 15); int l = sprintf(buf+reslen+15, "%d", ctx->maxproc); buf[reslen+1] = l; reslen += 15 + l; } else if (p.namelen == 15 && memcmp(p.name, "FCGI_MPXS_CONNS", 15) == 0) { memcpy(buf+reslen, "\x0f\1FCGI_MPXS_CONNS0", 18); reslen += 18; } param += p.vallen; } rec->type = FCGI_GET_VALUES_RESULT; rec->len = reslen - 8; return fufcgi_write_record(ctx, rec, buf); } /* Read a PARAMS/STDIN/ABORT record corresponding to the current id, starts * reading a new request if id=0. */ static int fufcgi_read_req_record(fufcgi *ctx, fufcgi_rec *rec) { int r; char tmp[128]; /* Large enough for a FCGI_GET_VALUES_RESULT */ while (1) { if ((r = fufcgi_read_record(ctx, rec)) != FUFE_OK) return r == FUFE_EOF && ctx->len == 0 ? FUFE_NOREQ : r; switch (rec->type) { case FCGI_PARAMS: case FCGI_STDIN: case FCGI_ABORT_REQUEST: if (rec->id != ctx->reqid) return FUFE_PROTO; return FUFE_OK; case FCGI_BEGIN_REQUEST: if (!rec->id || rec->id == ctx->reqid) return FUFE_PROTO; if (rec->len != 8) return FUFE_PROTO; ctx->keepconn = rec->data[2] & 1; if (rec->data[0] != 0 || rec->data[1] != 1) { /* FCGI_RESPONDER */ memcpy(tmp+8, "\0\0\0\0\3\0\0\0", 8); /* FCGI_UNKNOWN_ROLE */ rec->type = FCGI_END_REQUEST; rec->len = 8; if ((r = fufcgi_write_record(ctx, rec, tmp)) != FUFE_OK) return r; if (!ctx->keepconn) return FUFE_EOF; } else if (ctx->reqid) { memcpy(tmp+8, "\0\0\0\0\1\0\0\0", 8); /* FCGI_CANT_MPX_CONN */ rec->type = FCGI_END_REQUEST; rec->len = 8; if ((r = fufcgi_write_record(ctx, rec, tmp)) != FUFE_OK) return r; if (!ctx->keepconn) return FUFE_EOF; } else { ctx->reqid = rec->id; } break; case FCGI_GET_VALUES: if (rec->id) return FUFE_PROTO; if ((r = fufcgi_handle_values(ctx, rec, tmp)) != FUFE_OK) return r; break; default: memset(tmp+8, 0, 8); tmp[8] = rec->type; rec->type = FCGI_UNKNOWN_TYPE; rec->len = 8; rec->id = 0; if ((r = fufcgi_write_record(ctx, rec, tmp)) != FUFE_OK) return r; break; } } } static int fufcgi_read_params(pTHX_ fufcgi *ctx, fufcgi_rec *rec) { int r; fufcgi_param p; p.state = FUFC_INIT; SV *valsv = NULL; char *val = NULL; int valleft = 0; while (1) { if ((r = fufcgi_read_req_record(ctx, rec)) != FUFE_OK) return r; if (rec->type == FCGI_ABORT_REQUEST) return FUFE_OK; if (rec->type != FCGI_PARAMS) return FUFE_PROTO; if (rec->len == 0) return p.state != FUFC_INIT || valleft ? FUFE_PROTO : FUFE_OK; char *buf = rec->data; char *end = rec->data + rec->len; while (buf < end) { if (valleft) { r = valleft > end - buf ? end - buf : valleft; if (val) { memcpy(val, buf, r); val += r; } valleft -= r; buf += r; if (val && !valleft) { *val = 0; SvCUR_set(valsv, p.vallen); } continue; } if ((buf = fufcgi_param_parse(&p, buf, end)) == NULL) return FUFE_PLEN; if (p.state != FUFC_INIT) break; valsv = NULL; val = NULL; valleft = p.vallen; /* https://www.rfc-editor.org/rfc/rfc3875 */ /* Request header */ if (p.namelen > 5 && memcmp(p.name, "HTTP_", 5) == 0) { p.namelen -= 5; p.name += 5; for (r=0; r= 'A' && p.name[r] <= 'Z' ? p.name[r] | 0x20 : p.name[r]; if (!(p.namelen == 14 && memcmp(p.name, "content-length", 14) == 0) && !(p.namelen == 12 && memcmp(p.name, "content-type", 12) == 0)) { valsv = newSV(p.vallen+1); hv_store(ctx->headers, p.name, p.namelen, valsv, 0); } } else if (p.namelen == 14 && memcmp(p.name, "CONTENT_LENGTH", 14) == 0) { valsv = newSV(p.vallen+1); hv_stores(ctx->headers, "content-length", valsv); } else if (p.namelen == 12 && memcmp(p.name, "CONTENT_TYPE", 12) == 0) { valsv = newSV(p.vallen+1); hv_stores(ctx->headers, "content-type", valsv); } else if (p.namelen == 11 && memcmp(p.name, "REMOTE_ADDR", 11) == 0) { valsv = newSV(p.vallen+1); hv_stores(ctx->params, "ip", valsv); } else if (p.namelen == 12 && memcmp(p.name, "QUERY_STRING", 12) == 0) { valsv = newSV(p.vallen+1); hv_stores(ctx->params, "qs", valsv); } else if (p.namelen == 14 && memcmp(p.name, "REQUEST_METHOD", 14) == 0) { valsv = newSV(p.vallen+1); hv_stores(ctx->params, "method", valsv); /* Not in rfc3875; there's no standardized parameter for the URI, * but every FastCGI-capable web server includes this one */ } else if (p.namelen == 11 && memcmp(p.name, "REQUEST_URI", 11) == 0) { valsv = newSV(p.vallen+1); hv_stores(ctx->params, "path", valsv); } else { /* ignore */ } if (valsv) { SvPOK_only(valsv); val = SvPVX(valsv); *val = 0; /* in case vallen = 0 */ } } } } static int fufcgi_read_req(pTHX_ fufcgi *ctx, SV *headers, SV *params) { if (ctx->reqid) fu_confess("Invalid attempt to read FastCGI request before finishing the previous one"); fufcgi_rec rec; int r; ctx->off = ctx->len = 0; ctx->headers = (HV *)SvRV(headers); ctx->params = (HV *)SvRV(params); if ((r = fufcgi_read_params(aTHX_ ctx, &rec)) != FUFE_OK) return r; int stdinlen = 0; SV **contentlength = hv_fetchs(ctx->headers, "content-length", 0); if (contentlength && *contentlength) { UV uv = 0; char *v = SvPV_nolen(*contentlength); if (*v && !grok_atoUV(v, &uv, NULL)) return FUFE_CLEN; if (uv >= INT_MAX) return FUFE_CLEN; stdinlen = uv; } SV *sv = newSV(stdinlen+1); hv_stores(ctx->params, "body", sv); SvPOK_only(sv); char *stdinbuf = SvPVX(sv); int stdinleft = stdinlen; while (1) { if (rec.type == FCGI_ABORT_REQUEST) return FUFE_ABORT; else if (rec.type == FCGI_PARAMS) { if (rec.len != 0) return FUFE_PROTO; } else if (rec.type == FCGI_STDIN) { if (rec.len == 0) { *stdinbuf = 0; SvCUR_set(sv, stdinlen - stdinleft); return stdinleft == 0 ? FUFE_OK : FUFE_ABORT; } if (rec.len > stdinleft) return FUFE_PROTO; memcpy(stdinbuf, rec.data, rec.len); stdinbuf += rec.len; stdinleft -= rec.len; } else { return FUFE_PROTO; } if ((r = fufcgi_read_req_record(ctx, &rec)) != FUFE_OK) return r; } } static void fufcgi_flush(fufcgi *ctx) { fufcgi_rec hdr; if (ctx->len > 0) { hdr.len = ctx->len; hdr.type = FCGI_STDOUT; hdr.id = ctx->reqid; fufcgi_write_record(ctx, &hdr, ctx->buf); ctx->len = 0; } } static void fufcgi_print(fufcgi *ctx, const char *buf, int len) { int r; while (len > 0) { r = len > FUFCGI_MAX_DATA - ctx->len ? FUFCGI_MAX_DATA - ctx->len : len; memcpy(ctx->buf+8+ctx->len, buf, r); ctx->len += r; len -= r; buf += r; if (ctx->len >= FUFCGI_MAX_DATA) fufcgi_flush(ctx); } } static void fufcgi_done(fufcgi *ctx) { fufcgi_rec hdr; fufcgi_flush(ctx); hdr.len = 0; hdr.type = FCGI_STDOUT; hdr.id = ctx->reqid; fufcgi_write_record(ctx, &hdr, ctx->buf); memcpy(ctx->buf+8, "\0\0\0\0\0\0\0\0", 8); /* FCGI_REQUEST_COMPLETE */ hdr.type = FCGI_END_REQUEST; hdr.len = 8; fufcgi_write_record(ctx, &hdr, ctx->buf); ctx->reqid = ctx->len = ctx->off = 0; }