Files
sesimos/src/lib/fastcgi.c
T

475 lines
15 KiB
C

/**
* sesimos - secure, simple, modern web server
* @brief FastCGI interface implementation
* @file src/lib/fastcgi.c
* @author Lorenz Stechauner
* @date 2020-12-26
*/
#include "../defs.h"
#include "fastcgi.h"
#include "utils.h"
#include "../logger.h"
#include "list.h"
#include "../workers.h"
#include <sys/un.h>
#include <sys/socket.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
char *fastcgi_add_param(char *buf, const char *key, const char *value) {
char *ptr = buf;
unsigned long key_len = strlen(key);
unsigned long val_len = strlen(value);
if (key_len <= 127) {
ptr[0] = (char) (key_len & 0x7F);
ptr++;
} else {
*((int *) ptr) = htonl(0x80000000 | key_len);
ptr += 4;
}
if (val_len <= 127) {
ptr[0] = (char) (val_len & 0x7F);
ptr++;
} else {
*((int *) ptr) = htonl(0x80000000 | val_len);
ptr += 4;
}
memcpy(ptr, key, key_len);
ptr += key_len;
memcpy(ptr, value, val_len);
ptr += val_len;
return ptr;
}
int fastcgi_send_data(fastcgi_cnx_t *cnx, unsigned char type, unsigned short len, void *data) {
// build header
FCGI_Header header = {
.version = FCGI_VERSION_1,
.type = type,
.requestId = htons(cnx->req_id),
.contentLength = htons(len),
.paddingLength = 0,
.reserved = 0,
};
// send FastCGI header with MSG_MORE flag
if (sock_send_x(&cnx->socket, &header, sizeof(header), (len != 0) ? MSG_MORE : 0) == -1) {
error("Unable to send to FastCGI socket");
return -1;
}
// send data (if available)
if (sock_send_x(&cnx->socket, data, len, 0) == -1) {
error("Unable to send to FastCGI socket");
return -1;
}
// return bytes sent totally
return len + (int) sizeof(header);
}
int fastcgi_init(fastcgi_cnx_t *conn, int mode, unsigned int req_num, const sock *client, const http_req *req, const http_uri *uri) {
conn->mode = mode;
conn->header_sent = 0;
conn->req_id = (req_num + 1) & 0xFFFF;
conn->webroot = uri->webroot;
conn->err = NULL;
conn->fd_err_bytes = 0;
conn->fd_out = -1;
conn->fd_err = -1;
sock_init(&conn->out, -1, SOCK_PIPE);
conn->socket.enc = 0;
if ((conn->socket.socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
error("Unable to create unix socket");
return -1;
}
struct sockaddr_un sock_addr = { AF_UNIX };
if (conn->mode == FASTCGI_BACKEND_PHP) {
strcpy(sock_addr.sun_path, PHP_FPM_SOCKET);
}
if (connect(conn->socket.socket, (struct sockaddr *) &sock_addr, sizeof(sock_addr)) != 0) {
error("Unable to connect to FastCGI (unix) socket");
return -1;
}
FCGI_BeginRequestBody begin = {
.role = htons(FCGI_RESPONDER),
.flags = 0,
.reserved = {0},
};
if (fastcgi_send_data(conn, FCGI_BEGIN_REQUEST, sizeof(begin), &begin) == -1)
return -1;
char param_buf[4096], buf0[256], *param_ptr = param_buf;
param_ptr = fastcgi_add_param(param_ptr, "REDIRECT_STATUS", "CGI");
param_ptr = fastcgi_add_param(param_ptr, "DOCUMENT_ROOT", uri->webroot);
param_ptr = fastcgi_add_param(param_ptr, "GATEWAY_INTERFACE", "CGI/1.1");
param_ptr = fastcgi_add_param(param_ptr, "SERVER_SOFTWARE", SERVER_STR);
param_ptr = fastcgi_add_param(param_ptr, "SERVER_PROTOCOL", "HTTP/1.1");
param_ptr = fastcgi_add_param(param_ptr, "SERVER_NAME", http_get_header_field(&req->hdr, "Host"));
if (client->enc) {
param_ptr = fastcgi_add_param(param_ptr, "HTTPS", "on");
}
struct sockaddr_storage addr_storage;
struct sockaddr_in6 *addr;
socklen_t len = sizeof(addr_storage);
getsockname(client->socket, (struct sockaddr *) &addr_storage, &len);
addr = (struct sockaddr_in6 *) &addr_storage;
sprintf(buf0, "%i", ntohs(addr->sin6_port));
param_ptr = fastcgi_add_param(param_ptr, "SERVER_PORT", buf0);
len = sizeof(addr_storage);
getpeername(client->socket, (struct sockaddr *) &addr_storage, &len);
addr = (struct sockaddr_in6 *) &addr_storage;
sprintf(buf0, "%i", ntohs(addr->sin6_port));
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_PORT", buf0);
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_ADDR", conn->r_addr);
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_HOST", conn->r_host != NULL ? conn->r_host : conn->r_addr);
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_IDENT", "");
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_USER", "");
param_ptr = fastcgi_add_param(param_ptr, "REQUEST_METHOD", req->method);
param_ptr = fastcgi_add_param(param_ptr, "REQUEST_URI", req->uri);
param_ptr = fastcgi_add_param(param_ptr, "SCRIPT_NAME", uri->filename + strlen(uri->webroot));
param_ptr = fastcgi_add_param(param_ptr, "SCRIPT_FILENAME", uri->filename);
//param_ptr = fastcgi_add_param(param_ptr, "PATH_TRANSLATED", uri->filename);
param_ptr = fastcgi_add_param(param_ptr, "QUERY_STRING", uri->query != NULL ? uri->query : "");
if (uri->pathinfo != NULL && strlen(uri->pathinfo) > 0) {
sprintf(buf0, "/%s", uri->pathinfo);
} else {
buf0[0] = 0;
}
param_ptr = fastcgi_add_param(param_ptr, "PATH_INFO", buf0);
//param_ptr = fastcgi_add_param(param_ptr, "AUTH_TYPE", "");
const char *content_length = http_get_header_field(&req->hdr, "Content-Length");
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_LENGTH", content_length != NULL ? content_length : "");
const char *content_type = http_get_header_field(&req->hdr, "Content-Type");
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_TYPE", content_type != NULL ? content_type : "");
//if (conn->ctx->geoip[0] != 0) {
// param_ptr = fastcgi_add_param(param_ptr, "REMOTE_INFO", conn->ctx->geoip);
//}
for (int i = 0; i < list_size(req->hdr.fields); i++) {
const http_field *f = &req->hdr.fields[i];
const char *name = http_field_get_name(f);
char *ptr = buf0;
ptr += sprintf(ptr, "HTTP_");
for (int j = 0; j < strlen(name); j++, ptr++) {
char ch = name[j];
if ((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) {
ch = ch;
} else if (ch >= 'a' && ch <= 'z') {
ch &= 0x5F;
} else {
ch = '_';
}
ptr[0] = ch;
ptr[1] = 0;
}
param_ptr = fastcgi_add_param(param_ptr, buf0, http_field_get_value(f));
}
if (fastcgi_send_data(conn, FCGI_PARAMS, param_ptr - param_buf, param_buf) == -1)
return -1;
if (fastcgi_send_data(conn, FCGI_PARAMS, 0, NULL) == -1)
return -1;
int pipes[2][2];
if (pipe(pipes[0]) == -1 || pipe(pipes[1]) == -1)
return -1;
conn->fd_out = pipes[1][1];
conn->out.socket = pipes[1][0];
sock_set_timeout(&conn->out, FASTCGI_TIMEOUT);
conn->fd_err = pipes[0][1];
conn->err = fdopen(pipes[0][0], "r");
return 0;
}
int fastcgi_close_cnx(fastcgi_cnx_t *cnx) {
int e = errno;
if (cnx->err) fclose(cnx->err);
cnx->err = NULL;
sock_close(&cnx->socket);
sock_close(&cnx->out);
if (cnx->fd_err != -1) close(cnx->fd_err);
if (cnx->fd_out != -1) close(cnx->fd_out);
cnx->fd_err = -1;
cnx->fd_out = -1;
errno = e;
return 0;
}
int fastcgi_close_stdin(fastcgi_cnx_t *cnx) {
return (fastcgi_send_data(cnx, FCGI_STDIN, 0, NULL) == -1) ? -1 : 0;
}
int fastcgi_php_error(fastcgi_cnx_t *cnx, char *err_msg) {
char *line = NULL, *line_ptr = NULL, *next_ptr = NULL;
size_t line_len = 0;
int err = 0;
log_lvl_t msg_type = LOG_INFO;
for (long ret; cnx->fd_err_bytes > 0 && (ret = getline(&line, &line_len, cnx->err)) != -1; cnx->fd_err_bytes -= ret) {
if (ret > 0) line[ret - 1] = 0;
line_ptr = line;
if (strstarts(line_ptr, "PHP message: ")) {
line_ptr += 13;
} else if (!strstr(line_ptr, "; PHP message: ")) {
logmsgf(msg_type, "%s", line_ptr);
continue;
}
while (line_ptr) {
if (strstarts(line_ptr, "PHP Warning: ")) {
msg_type = LOG_WARNING;
} else if (strstarts(line_ptr, "PHP Fatal error: ")) {
msg_type = LOG_ERROR;
} else if (strstarts(line_ptr, "PHP Parse error: ")) {
msg_type = LOG_ERROR;
} else if (strstarts(line_ptr, "PHP Notice: ")) {
msg_type = LOG_NOTICE;
} else {
msg_type = LOG_INFO;
}
if ((next_ptr = strstr(line_ptr, "; PHP message: "))) {
next_ptr[0] = 0;
}
logmsgf(msg_type, "%s", line_ptr);
if (err_msg && msg_type <= LOG_ERROR && line_ptr != line) {
strcpy_rem_webroot(err_msg, line_ptr, cnx->webroot);
err = 1;
}
line_ptr = next_ptr ? next_ptr + 15 : NULL;
}
}
// cleanup
free(line);
return err;
}
int fastcgi_recv_frame(fastcgi_cnx_t *cnx) {
FCGI_Header header;
unsigned short req_id, content_len;
if (sock_recv_x(&cnx->socket, &header, sizeof(header), 0) == -1)
return -1;
req_id = ntohs(header.requestId);
content_len = ntohs(header.contentLength);
if (req_id != cnx->req_id) {
warning("Invalid request id from FastCGI socket");
char content[256 * 256];
sock_recv_x(&cnx->socket, content, content_len + header.paddingLength, 0);
return -1;
}
if (header.type == FCGI_STDOUT || header.type == FCGI_STDERR) {
char buf[256];
if (header.type == FCGI_STDOUT && !cnx->header_sent) {
char content[256 * 256];
if (sock_recv_x(&cnx->socket, content, content_len + header.paddingLength, 0) == -1)
return -1;
char *h_pos = strstr(content, "\r\n\r\n");
long header_len = h_pos - content + 4;
if (h_pos != NULL) {
uint64_t len;
len = header_len;
if (write(cnx->fd_out, &len, sizeof(len)) == -1)
return -1;
if (write(cnx->fd_out, content, len) == -1)
return -1;
cnx->header_sent = 1;
len = content_len - header_len;
if (len > 0) {
if (write(cnx->fd_out, &len, sizeof(len)) == -1)
return -1;
if (write(cnx->fd_out, content + header_len, len) == -1)
return -1;
}
return header.type;
}
} else if (header.type == FCGI_STDOUT) {
uint64_t len = content_len;
if (write(cnx->fd_out, &len, sizeof(len)) == -1)
return -1;
}
int fd = cnx->fd_out;
if (header.type == FCGI_STDERR) {
fd = cnx->fd_err;
cnx->fd_err_bytes += content_len + 1;
}
for (long ret, sent = 0; sent < content_len; sent += ret) {
// FIXME if pipe is full thread gets stuck
if ((ret = splice(cnx->socket.socket, 0, fd, 0, content_len - sent, 0)) == -1) {
if (errno == EINTR) {
errno = 0, ret = 0;
continue;
} else {
return -1;
}
}
}
if (header.type == FCGI_STDERR) write(fd, "\n", 1);
if (sock_recv_x(&cnx->socket, buf, header.paddingLength, 0) == -1)
return -1;
return header.type;
}
char content[256 * 256];
if (sock_recv_x(&cnx->socket, content, content_len + header.paddingLength, 0) == -1)
return -1;
if (header.type == FCGI_END_REQUEST) {
FCGI_EndRequestBody *body = (FCGI_EndRequestBody *) content;
cnx->app_status = ntohl(body->appStatus);
if (body->protocolStatus != FCGI_REQUEST_COMPLETE)
error("FastCGI protocol error: %i", body->protocolStatus);
} else {
warning("Unknown FastCGI type: %i", header.type);
return -1;
}
return header.type;
}
int fastcgi_header(fastcgi_cnx_t *cnx, http_res *res, char *err_msg) {
long ret, len;
char content[CLIENT_MAX_HEADER_SIZE];
if ((len = sock_recv_chunk_header(&cnx->out)) == -1) {
res->status = http_get_status(500);
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
error("Unable to receive from FastCGI socket (1)");
return -1;
}
if ((ret = sock_recv_x(&cnx->out, content, len, 0)) == -1) {
res->status = http_get_status(500);
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
error("Unable to receive from FastCGI socket (2)");
return -1;
}
content[ret] = 0;
char *buf = content;
char *h_pos = strstr(content, "\r\n\r\n");
if (h_pos == NULL) {
error("Unable to parse header: End of header not found");
return -1;
}
long header_len = h_pos - content + 4;
for (int i = 0; i < header_len; i++) {
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
error("Unable to parse header: Header contains illegal characters");
return -1;
}
}
if (fastcgi_php_error(cnx, err_msg) != 0) {
res->status = http_get_status(500);
return 1;
}
char *ptr = buf;
while (header_len != (ptr - buf)) {
char *pos0 = strstr(ptr, "\r\n");
if (pos0 == NULL) {
error("Unable to parse header: Invalid header format");
return -1;
}
ret = http_parse_header_field(&res->hdr, ptr, pos0, 0);
if (ret != 0) return (int) ret;
if (pos0[2] == '\r' && pos0[3] == '\n') {
return 0;
}
ptr = pos0 + 2;
}
return 0;
}
int fastcgi_dump(fastcgi_cnx_t *cnx, char *buf, const long len) {
for (long ret, rcv = 0; rcv < len; rcv += ret) {
if ((ret = sock_recv_chunk_header(&cnx->out)) == -1) {
return -1;
}
const long min = ret > len - rcv ? len - rcv : ret;
if ((ret = sock_recv_x(&cnx->out, buf + rcv, min, 0)) <= 0) {
return -1;
}
}
return 0;
}
int fastcgi_receive(fastcgi_cnx_t *cnx, sock *client, unsigned long len) {
char buf[CHUNK_SIZE];
for (long to_send = (long) len, ret; to_send > 0; to_send -= ret) {
if ((ret = sock_recv(client, buf, (to_send > sizeof(buf)) ? sizeof(buf) : to_send, 0)) <= 0) {
error("Unable to receive");
return -1;
}
if (fastcgi_send_data(cnx, FCGI_STDIN, ret, buf) == -1)
return -1;
}
return 0;
}
int fastcgi_receive_chunked(fastcgi_cnx_t *cnx, sock *client) {
for (long ret;;) {
if ((ret = sock_recv_chunk_header(client)) < 0) {
return (int) ret;
} else if (ret == 0) {
break;
}
if ((ret = fastcgi_receive(cnx, client, ret)) < 0)
return (int) ret;
}
return 0;
}