diff --git a/.gitignore b/.gitignore index 3523288..5de591d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,49 +1,76 @@ -# http://www.gnu.org/software/automake +# Prerequisites +plugins +build +*._* +*.d +*.deb +.vscode +# Object files +*.o +*.ko +*.obj +*.elf -Makefile.in -/ar-lib -/mdate-sh -/py-compile -/test-driver -/ylwrap +# Linker output +*.ilk +*.map +*.exp -# http://www.gnu.org/software/autoconf +# Precompiled Headers +*.gch +*.pch -autom4te.cache -/autoscan.log -/autoscan-*.log -/aclocal.m4 -/compile -/config.guess -/config.h.in -/config.log -/config.status -/config.sub -/configure -/configure.scan -/depcomp -/install-sh -/missing -/stamp-h1 +# Libraries +*.lib +*.a +*.la +*.lo -# https://www.gnu.org/software/libtool/ +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib -/ltmain.sh +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex -# http://www.gnu.org/software/texinfo +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb -/texinfo.tex - -# http://www.gnu.org/software/m4/ - -m4/libtool.m4 -m4/ltoptions.m4 -m4/ltsugar.m4 -m4/ltversion.m4 -m4/lt~obsolete.m4 - -# Generated Makefile -# (meta build system like autotools, -# can automatically generate from config.status script -# (which is called by configure script)) +# Kernel Module Compile Results +*.mod* +*.cmd +modules.order +Module.symvers +Makefile.old +dkms.con +.DS_Store +.* +*.cache Makefile +antd +compile +config.guess +depcomp +install-sh +missing +libtool +config.log +config.status +config.sub +configure +aclocal.m4 +ltmain.sh +Makefile.in +# others +3rd/lua-5.3.4/lua +3rd/lua-5.3.4/luac diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..243b2f6 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,19 @@ +AUTOMAKE_OPTIONS = foreign + + + +# check for system +if LINUX + AM_CPPFLAGS = -Wl,--no-as-needed +else + AM_CPPFLAGS = -Wl,-undefined,dynamic_lookup +endif + + +AM_CPPFLAGS += -W -Wall -g -std=c99 -fPIC + +lib_LTLIBRARIES = fcgi.la +fcgi_la_LDFLAGS = -module -avoid-version -shared +fcgi_la_SOURCES = fcgi.c proto.c + +EXTRA_DIST = README.md \ No newline at end of file diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..e55947e --- /dev/null +++ b/configure.ac @@ -0,0 +1,69 @@ +# initialise autoconf and set up some basic information about the program we’re packaging +AC_INIT([fcgi], [0.1.0a], [xsang.le@gmail.com]) + +# We’re going to use automake for this project +# [subdir-objects] if needed +AM_INIT_AUTOMAKE() + +# dependencies +# C compiler +AC_PROG_CC +# libtool for linking +AC_PROG_LIBTOOL + +# check for lib antd +AC_CHECK_HEADER([antd/plugin.h],[ + has_antd=yes + # check if the library exists +],[ + AC_MSG_ERROR([Unable to find antd, please install it first]) +]) +AC_CHECK_LIB([antd],[antd_send],[],[ + if test "$has_antd" = "yes"; then + AC_MSG_ERROR([Unable to find antd shared library, please install it first]) + fi +]) + + +# check for pthread +AC_CHECK_LIB([pthread], [pthread_create], [], [ + AC_MSG_ERROR([libpthread is not found])]) + +AC_DEFINE([_GNU_SOURCE], [1],[Use GNU source]) +# AC_CANONICAL_HOST is needed to access the 'host_os' variable + + +AC_CANONICAL_HOST +build_linux=no +build_windows=no +build_mac=no +# Detect the target system +case "${host_os}" in + linux*) + AC_DEFINE([LINUX], [1],[Linux system]) + build_linux=yes + ;; + darwin*) + build_mac=yes + AC_DEFINE([MACOS], [1],[MacOS system]) + ;; + *) + AC_MSG_ERROR(["OS $host_os is not supported"]) + ;; +esac +# case for window: +# cygwin*|mingw*) +# build_windows=yes +# ;; +# Pass the conditionals to automake +AM_CONDITIONAL([LINUX], [test "$build_linux" = "yes"]) +AM_CONDITIONAL([WINDOWS], [test "$build_windows" = "yes"]) +AM_CONDITIONAL([OSX], [test "$build_mac" = "yes"]) + +# find a file called Makefile.in, substitute placeholders +# like @PACKAGE_VERSION@ with values like 0.1.0a, +# and write the results to Makefile. +AC_CONFIG_FILES([Makefile]) + +# output the script: +AC_OUTPUT \ No newline at end of file diff --git a/fcgi.c b/fcgi.c new file mode 100644 index 0000000..2ff795d --- /dev/null +++ b/fcgi.c @@ -0,0 +1,649 @@ +#define PLUGIN_IMPLEMENT 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#include +#include "proto.h" + +#define MAX_PATH_LEN 108 +#define MAX_BACK_LOG 1024 +#define PROCESS_TIMEOUT 200u + +#define FCGI_CLIENT_REQUEST_SENT (0) +#define FCGI_CLIENT_WAIT_FOR_RESPONSE_HEADER (-1) +#define FCGI_CLIENT_WAIT_FOR_RESPONSE_DATA (-2) + +typedef struct { + // FastCGI application path + char app_bin[MAX_PATH_LEN]; + // TCP host or Unix domain socket + char address[MAX_PATH_LEN]; + // only for TCP socket + int port; + // server fd + // int fd; + // pid of the application process + // pid_t pid; +} fcgi_config_t; + +static fcgi_config_t g_config; + +static int read_config() +{ + char * tmp; + (void*) memset(g_config.app_bin, 0, MAX_PATH_LEN); + (void*) memset(g_config.address, 0, MAX_PATH_LEN); + g_config.port = -1; + //g_config.fd = -1; + //g_config.pid = -1; + regmatch_t regex_matches[3]; + // read plugin configuration + if(!__plugin__.config) + { + PLUGIN_PANIC("No plugin configuration found. Please specify it it server config file"); + return -1; + } + tmp = (char*) dvalue(__plugin__.config, "socket"); + if(!tmp) + { + PLUGIN_PANIC("No socket configuration found (socket)"); + return -1; + } + if(strncmp(tmp,"unix:", 5) == 0) + { + if(strlen(tmp + 5) > MAX_PATH_LEN - 1) + { + PLUGIN_PANIC("socket configuration is too long: %s", tmp); + return -1; + } + snprintf(g_config.address, MAX_PATH_LEN,"%s", tmp+5); + LOG("Found Unix domain socket configuration: %s", g_config.address); + } + else if(regex_match("^([a-zA-Z0-9\\-_\\.]+):([0-9]+)$", tmp,3, regex_matches)) + { + if(regex_matches[1].rm_eo - regex_matches[1].rm_so > MAX_PATH_LEN - 1) + { + PLUGIN_PANIC("socket configuration is too long: %s", tmp); + return -1; + } + memcpy(g_config.address, tmp + regex_matches[2].rm_so, regex_matches[2].rm_eo - regex_matches[2].rm_so); + g_config.port = atoi(g_config.address); + (void*) memset(g_config.address, 0, MAX_PATH_LEN); + memcpy(g_config.address, tmp + regex_matches[1].rm_so, regex_matches[1].rm_eo - regex_matches[1].rm_so); + LOG("Found TCP socket configuration: %s:%d", g_config.address, g_config.port); + } + else + { + PLUGIN_PANIC("Unknown socket configuration: %s", tmp); + return -1; + } + tmp = (char*) dvalue(__plugin__.config, "bin"); + if(!tmp) + { + PLUGIN_PANIC("No FastCGI application configuration found (bin)"); + return -1; + } + if(strlen(tmp) > MAX_PATH_LEN - 1) + { + PLUGIN_PANIC("Bin applicqtion configuration is too long: %s", tmp); + return -1; + } + snprintf(g_config.app_bin, MAX_PATH_LEN,"%s", tmp); + LOG("Binary application configuration: %s", g_config.app_bin); + return 0; +} +static int open_un_socket() +{ + struct sockaddr_un address; + address.sun_family = AF_UNIX; + // create the socket + (void)strncpy(address.sun_path, g_config.address, sizeof(address.sun_path)); + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + { + ERROR("Unable to create Unix domain socket: %s", strerror(errno)); + return -1; + } + if(connect(fd, (struct sockaddr*)(&address), sizeof(address)) == -1) + { + ERROR( "Unable to connect to socket '%s': %s", address.sun_path, strerror(errno)); + close(fd); + return -1; + } + LOG("Connected to FastCGI server at %s: %d", g_config.address, fd); + return fd; +} + +static int open_tcp_socket() +{ + struct sockaddr_in servaddr; + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) + { + PLUGIN_PANIC("Cannot create TCP socket %s:d: %s",g_config.address, g_config.port, strerror(errno)); + return -1; + } + + bzero(&servaddr, sizeof(servaddr)); + + // assign IP, PORT + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = inet_addr(g_config.address); + servaddr.sin_port = htons(g_config.port); + + // connect the client socket to server socket + if (connect(fd, (struct sockaddr*)&servaddr, sizeof(servaddr))!= 0) { + ERROR( "Unable to connect to socket '%s:%d': %s", g_config.address, g_config.port, strerror(errno)); + close(fd); + return -1; + } + LOG("Connected to server: %s:%d at [%d]", g_config.address, g_config.port, fd); + return fd; +} + +static int open_socket() +{ + if(g_config.port != -1) + { + return open_tcp_socket(); + } + else + { + return open_un_socket(); + } +} + +void init() +{ + use_raw_body(); + if(read_config() != 0) + return; + // create the socket + //if(create_socket() != 0) + // return; + LOG("FastCGI init successful"); + +} +void destroy() +{ + /*if(g_config.fd) + { + LOG("Close socket: %d", g_config.fd); + close(g_config.fd); + }*/ +} + + +char* read_line(char** buff, int* size) +{ + int i = 0; + while(i <= *size-1 && (*buff)[i] != '\n') i++; + if(i > 0 && i <= *size - 1) + (*buff)[i] = '\0'; + char* line = *buff; + *size = *size - i - 1; + *buff = *buff + i+1; + return line; +} + +static int read_header(antd_client_t* cl, antd_request_t* rq) +{ + FCGI_Header header; + antd_response_header_t rhd; + rhd.header = dict(); + rhd.cookie = list_init(); + rhd.status = 200; + char *k; + char *v; + int len, ret; + regmatch_t matches[3]; + uint8_t * payload; + char* line; + char* ptr; + while(cl->state == FCGI_CLIENT_WAIT_FOR_RESPONSE_HEADER) + { + ret = fcgi_read_header(cl,&header); + if(ret < 0) + { + (void)fcgi_abort_request(cl, cl->sock); + LOG("fcgi_read_header() on %d: %s", cl->sock, strerror(errno)); + return -1; + } + payload = fcgi_read_payload(cl, &header, &ret); + switch(header.type) + { + case FCGI_STDOUT: + // write data to the other side + if(payload && ret > 0) + { + ptr = (char*)payload; + while(ret > 0) + { + line = read_line(&ptr, &ret); + trim(line, '\r'); + if(strlen(line) == 0) + { + cl->state = FCGI_CLIENT_WAIT_FOR_RESPONSE_DATA; + // write out header and the rest of the data + antd_send_header(rq->client, &rhd); + if(ret > 0) + { + if(antd_send(rq->client,ptr, ret) != ret) + { + (void)fcgi_abort_request(cl, cl->sock); + ERROR("Error atnd_send(): %s", strerror(errno)); + free(payload); + return -1; + } + } + break; + } + if(ret < 0) break; + if (regex_match("\\s*Status\\s*:\\s+([0-9]{3})\\s+([a-zA-Z0-9 ]*)", line, 3, matches)) + { + len = matches[1].rm_eo - matches[1].rm_so; + k = (char *)malloc(len + 1); + memset(k, 0, len + 1); + memcpy(k, line + matches[1].rm_so, len); + rhd.status = atoi(k); + LOG("Response status %d", rhd.status); + free(k); + } + else if (regex_match("^([a-zA-Z0-9\\-]+)\\s*:\\s*(.*)$", line, 3, matches)) + { + len = matches[1].rm_eo - matches[1].rm_so; + k = (char *)malloc(len + 1); + memset(k, 0, len + 1); + memcpy(k, line + matches[1].rm_so, len); + verify_header(k); + len = matches[2].rm_eo - matches[2].rm_so; + v = (char *)malloc(len + 1); + memset(v, 0, len + 1); + memcpy(v, line + matches[2].rm_so, len); + LOG("Header [%s] -> [%s]", k, v); + if (strcmp(k, "Set-Cookie") == 0) + { + list_put_ptr(&rhd.cookie, v); + } + else + { + dput(rhd.header, k, v); + } + free(k); + } + else + { + LOG("Ignore invalid header: %s", line); + } + + } + } + break; + case FCGI_STDERR: + if(payload && ret > 0) + { + ERROR("%s", (char*) payload); + } + break; + case FCGI_END_REQUEST: + LOG("End request received, this should not happen %d", cl->sock); + // FCGI_EndRequestBody* body = (FCGI_EndRequestBody*) payload; + if(payload) free(payload); + return -1; + default: + LOG("Unsupported record type: 0x%02x", header.type); + break; + } + if(payload) free(payload); + } + return 0; +} + +static int read_data(antd_client_t* cl, antd_request_t* rq) +{ + FCGI_Header header; + int ret = fcgi_read_header(cl,&header); + if(ret < 0) + { + (void)fcgi_abort_request(cl, cl->sock); + LOG("fcgi_read_header() on %d: %s", cl->sock, strerror(errno)); + return -1; + } + uint8_t * payload = fcgi_read_payload(cl, &header, &ret); + switch(header.type) + { + case FCGI_STDOUT: + // write data to the other side + if(payload && ret > 0) + { + if(antd_send(rq->client,payload, ret) != ret) + { + (void)fcgi_abort_request(cl, cl->sock); + ERROR("Error atnd_send(): %s", strerror(errno)); + free(payload); + return -1; + } + } + break; + case FCGI_STDERR: + if(payload && ret > 0) + { + ERROR("%s", (char*) payload); + } + break; + case FCGI_END_REQUEST: + LOG("End request received, close connection %d", cl->sock); + if(payload) free(payload); + return -1; + default: + LOG("Unsupported record type: 0x%02x", header.type); + break; + } + if(payload) free(payload); + return 0; +} + +static void *process(void *data) +{ + antd_request_t *rq = (antd_request_t *)data; + antd_client_t* cl = (antd_client_t* ) dvalue(rq->request, "FCGI_CL_DATA"); + struct pollfd pfds[2]; + pfds[0].fd = rq->client->sock; + pfds[0].events = POLLIN; + if(rq->client->ssl) + { + pfds[0].events = POLLIN | POLLOUT; + } + pfds[1].fd = cl->sock; + pfds[1].events = POLLIN; + if(cl->state == FCGI_CLIENT_REQUEST_SENT) + { + (void)fcgi_send_stdin(cl, cl->sock, NULL, 0, 0); + if (ws_enable(rq->request)) + { + cl->state = FCGI_CLIENT_WAIT_FOR_RESPONSE_DATA; + } + else + { + cl->state = FCGI_CLIENT_WAIT_FOR_RESPONSE_HEADER; + } + } + + int rc = poll(pfds, 2, PROCESS_TIMEOUT); + antd_task_t* task; + uint8_t buff[BUFFLEN]; + int ret; + switch (rc) + { + case -1: + ERROR("Error on poll(): %s", strerror(errno)); + (void)fcgi_abort_request(cl, cl->sock); + antd_close(cl); + dput(rq->request, "FCGI_CL_DATA", NULL); + return antd_create_task(NULL, data, NULL, rq->client->last_io); + case 0: + // time out + task = antd_create_task(process, (void *)rq, NULL, time(NULL)); + antd_task_bind_event(task, rq->client->sock, 0, TASK_EVT_ON_WRITABLE | TASK_EVT_ON_READABLE); + antd_task_bind_event(task, cl->sock, 0, TASK_EVT_ON_READABLE); + return task; + // we have data + default: + // If data is on webserver + if ((pfds[0].revents & POLLIN) || (rq->client->ssl && (pfds[0].revents & POLLOUT)) ) + { + while((ret = antd_recv_upto(rq->client,buff, BUFFLEN)) > 0) + { + // write data to the application stdin + if(fcgi_send_stdin(cl, cl->sock,buff, ret, (ret % 8 == 0)? 0 : 8 - (ret % 8) ) != 0) + { + ERROR("Error on fcgi_send_stdin(): %s", strerror(errno)); + (void)fcgi_abort_request(cl, cl->sock); + antd_close(cl); + dput(rq->request, "FCGI_CL_DATA", NULL); + return antd_create_task(NULL, data, NULL, rq->client->last_io); + } + if(cl->state > 0) + cl->state -= ret; + LOG("sending %s: %d", buff, cl->state); + } + if(ret < 0) + { + LOG("antd_recv_upto() on %d: %s",rq->client->sock, strerror(errno)); + (void)fcgi_abort_request(cl, cl->sock); + antd_close(cl); + dput(rq->request, "FCGI_CL_DATA", NULL); + return antd_create_task(NULL, data, NULL, rq->client->last_io); + } + } + else if(pfds[0].revents &(POLLERR | POLLHUP)) + { + ERROR("POLLERR or POLLHUP received on %d", rq->client->sock); + (void)fcgi_abort_request(cl, cl->sock); + antd_close(cl); + dput(rq->request, "FCGI_CL_DATA", NULL); + return antd_create_task(NULL, data, NULL, rq->client->last_io); + } + if(pfds[1].revents & POLLIN) + { + if(cl->state == FCGI_CLIENT_WAIT_FOR_RESPONSE_HEADER) + { + if(read_header(cl, rq) != 0) + { + antd_close(cl); + dput(rq->request, "FCGI_CL_DATA", NULL); + return antd_create_task(NULL, data, NULL, rq->client->last_io); + } + } + else + { + if(read_data(cl,rq) != 0) + { + antd_close(cl); + dput(rq->request, "FCGI_CL_DATA", NULL); + return antd_create_task(NULL, data, NULL, rq->client->last_io); + } + } + } + else if(pfds[1].revents &(POLLERR | POLLHUP)) + { + ERROR("POLLERR or POLLHUP received on %d", cl->sock); + //(void)fcgi_abort_request(cl, cl->sock); + antd_close(cl); + dput(rq->request, "FCGI_CL_DATA", NULL); + return antd_create_task(NULL, data, NULL, rq->client->last_io); + } + task = antd_create_task(process, (void *)rq, NULL, time(NULL)); + antd_task_bind_event(task, rq->client->sock, 0, TASK_EVT_ON_WRITABLE | TASK_EVT_ON_READABLE); + antd_task_bind_event(task, cl->sock, 0, TASK_EVT_ON_READABLE); + return task; + } +} + +static int send_request(antd_client_t *cl, antd_request_t* rq) +{ + int ret = 0; + char *tmp = NULL; + char *sub = NULL; + char *root; + dictionary_t request = (dictionary_t)rq->request; + dictionary_t header = (dictionary_t)dvalue(rq->request, "REQUEST_HEADER"); + ret += fcgi_begin_request(cl, cl->sock, FCGI_RESPONDER, 0); + //ret += fcgi_send_param(cl, cl->sock, "", ""); + + ret += fcgi_send_param(cl, cl->sock, "GATEWAY_INTERFACE", "CGI/1.1"); + ret += fcgi_send_param(cl, cl->sock, "SERVER_SOFTWARE", SERVER_NAME); + root = (char *)dvalue(request, "SERVER_WWW_ROOT"); + tmp = (char *)dvalue(request, "REQUEST_QUERY"); + + if (!tmp) + { + ret += fcgi_send_param(cl, cl->sock, "QUERY_STRING", ""); + } + else + { + ret += fcgi_send_param(cl, cl->sock, "REQUEST_URI", tmp); + sub = strchr(tmp, '?'); + if (sub) + { + sub++; + ret += fcgi_send_param(cl, cl->sock, "QUERY_STRING", sub); + } + else + { + ret += fcgi_send_param(cl, cl->sock, "QUERY_STRING", ""); + } + } + tmp = (char *)dvalue(request, "METHOD"); + if (tmp) + { + ret += fcgi_send_param(cl, cl->sock, "REQUEST_METHOD", tmp); + } + tmp = (char *)dvalue(header, "Content-Type"); + if (tmp) + { + ret += fcgi_send_param(cl, cl->sock, "CONTENT_TYPE", tmp); + } + else + { + ret += fcgi_send_param(cl, cl->sock, "CONTENT_TYPE", ""); + } + tmp = (char *)dvalue(header, "Content-Length"); + if (tmp) + { + cl->state = atoi(tmp); + ret += fcgi_send_param(cl, cl->sock, "CONTENT_LENGTH", tmp); + } + else + { + ret += fcgi_send_param(cl, cl->sock, "CONTENT_LENGTH", ""); + } + ret += fcgi_send_param(cl, cl->sock, "DOCUMENT_ROOT", root); + tmp = (char *)dvalue(request, "REQUEST_PATH"); + if (tmp) + { + sub = tmp; + while (*sub == '/') + sub++; + if (sub) + { + ret += fcgi_send_param(cl, cl->sock, "PATH_INFO", sub); + } + else + { + ret += fcgi_send_param(cl, cl->sock, "PATH_INFO", ""); + } + } + else + { + ret += fcgi_send_param(cl, cl->sock, "PATH_INFO", ""); + } + tmp = (char *)dvalue(request, "REMOTE_ADDR"); + if(tmp) + { + ret += fcgi_send_param(cl, cl->sock, "REMOTE_ADDR", tmp); + ret += fcgi_send_param(cl, cl->sock, "REMOTE_HOST", tmp); + + } + ret += fcgi_send_param(cl, cl->sock, "SERVER_NAME", SERVER_NAME); + ret += fcgi_send_param(cl, cl->sock, "SERVER_PORT", (char *)dvalue(request, "SERVER_PORT")); + ret += fcgi_send_param(cl, cl->sock, "SERVER_PROTOCOL", "HTTP/1.1"); + // add remaining header to the vars + chain_t it; + for_each_assoc(it, header) + { + tmp = __s("HTTP_%s", it->key); + char *s = tmp; + while (*s) + { + if (*s == '-') + *s = '_'; + else if (*s != '_') + *s = toupper((char)*s); + s++; + } + ret += fcgi_send_param(cl, cl->sock, tmp, (char *)it->value); + free(tmp); + } + tmp = (char *)dvalue(request, "RESOURCE_PATH"); + if (tmp) + { + ret += fcgi_send_param(cl, cl->sock, "SCRIPT_NAME", basename(tmp)); + tmp = __s("%s/%s", root, tmp); + ret += fcgi_send_param(cl, cl->sock, "SCRIPT_FILENAME", tmp); + ret += fcgi_send_param(cl, cl->sock, "PATH_TRANSLATED", tmp); + free(tmp); + } + else + { + ret += fcgi_send_param(cl, cl->sock, "SCRIPT_FILENAME", ""); + ret += fcgi_send_param(cl, cl->sock, "PATH_TRANSLATED", ""); + ret += fcgi_send_param(cl, cl->sock, "SCRIPT_NAME", ""); + } + // redirect status for php + ret += fcgi_send_param(cl, cl->sock, "REDIRECT_STATUS", "200"); + ret += fcgi_send_param(cl, cl->sock, "", ""); + return ret; +} + +void* handle(void* data) +{ + antd_request_t *rq = (antd_request_t *)data; + // connect to socket + int fd = open_socket(); + if(fd < 0) + { + antd_error(rq->client, 503, "Service unavailable"); + return antd_create_task(NULL, data, NULL, rq->client->last_io); + } + set_nonblock(fd); + + // write all header to fastCGI server via params + antd_client_t* cl = (antd_client_t*) malloc(sizeof(antd_client_t)); + (void)memset(cl, 0, sizeof(antd_client_t)); + cl->sock = fd; + time(&cl->last_io); + cl->ssl = NULL; + // state is used to store content lenth of the current request + cl->state = FCGI_CLIENT_REQUEST_SENT; + cl->z_status = 0; + cl->z_level = ANTD_CNONE; + cl->zstream = NULL; + rq->client->z_level = ANTD_CNONE; + + // start the request + + if(send_request(cl,rq) != 0) + { + ERROR("Unable to send request to application: %d", fd); + antd_error(rq->client, 500, "Internal server error"); + (void)fcgi_abort_request(cl, cl->sock); + antd_close(cl); + return antd_create_task(NULL, data, NULL, rq->client->last_io); + } + + dput(rq->request, "FCGI_CL_DATA", cl); + + antd_task_t* task = antd_create_task(process, (void *)rq, NULL, time(NULL)); + //antd_task_bind_event(task, rq->client->sock, 0, TASK_EVT_ON_WRITABLE | TASK_EVT_ON_READABLE); + //antd_task_bind_event(task, fd, 0, TASK_EVT_ON_READABLE); + return task; +} diff --git a/proto.c b/proto.c new file mode 100644 index 0000000..85a7d0f --- /dev/null +++ b/proto.c @@ -0,0 +1,279 @@ + +#include +#include +#include +#include "proto.h" + +#define PARAMS_LENGTH_11 ((0<<0) | (0<<1)) +#define PARAMS_LENGTH_14 ((0<<0) | (1<<1)) +#define PARAMS_LENGTH_41 ((1<<0) | (0<<1)) +#define PARAMS_LENGTH_44 ((1<<0) | (1<<1)) + +typedef struct { + unsigned char nameLengthB0; /* nameLengthB0 >> 7 == 0 */ + unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */ + char value[0]; +} FCGI_NameValuePair11; + +typedef struct { + unsigned char nameLengthB0; /* nameLengthB0 >> 7 == 0 */ + unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */ + unsigned char valueLengthB2; + unsigned char valueLengthB1; + unsigned char valueLengthB0; + char value[0]; +} FCGI_NameValuePair14; + +typedef struct { + unsigned char nameLengthB3; /* nameLengthB3 >> 7 == 1 */ + unsigned char nameLengthB2; + unsigned char nameLengthB1; + unsigned char nameLengthB0; + unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */ + char value[0]; +} FCGI_NameValuePair41; + +typedef struct { + unsigned char nameLengthB3; /* nameLengthB3 >> 7 == 1 */ + unsigned char nameLengthB2; + unsigned char nameLengthB1; + unsigned char nameLengthB0; + unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */ + unsigned char valueLengthB2; + unsigned char valueLengthB1; + unsigned char valueLengthB0; + char value[0]; +} FCGI_NameValuePair44; + +typedef union { + FCGI_NameValuePair11 d11; + FCGI_NameValuePair14 d14; + FCGI_NameValuePair41 d41; + FCGI_NameValuePair44 d44; +} FCGI_Params_Body; + +static int fcgi_send_record(antd_client_t* cl, FCGI_Header* header, uint8_t* buff, size_t len) +{ + if(!header) + { + ERROR("Record header should not empty"); + return -1; + } + + // send the header + int ret = antd_send(cl, (uint8_t*)header, sizeof(FCGI_Header)); + if(ret != sizeof(FCGI_Header)) + { + ERROR("fcgi_send_record: Unable to send record header, only %d of %d bytes sent: %s", ret, sizeof(FCGI_Header), strerror(errno)); + return -1; + } + if(!buff) + { + return 0; + } + // send the data + ret = antd_send(cl, (uint8_t*)buff, len); + if(ret != (int)len) + { + ERROR("fcgi_send_record: Unable to send record data, only %d of %d bytes sent", ret, len); + return -1; + } + return 0; +} + +int fcgi_begin_request(antd_client_t* cl, uint16_t id, uint16_t role, uint8_t flags) +{ + FCGI_BeginRequestRecord record; + record.header.version = FCGI_VERSION_1; + record.header.type = FCGI_BEGIN_REQUEST; + record.header.requestIdB1 = id >> 8; + record.header.requestIdB0 = id & 0xFF; + record.header.contentLengthB1 = 0; + record.header.contentLengthB0 = 8; + record.header.paddingLength = 0; + + record.body.roleB1 = role >> 8; + record.body.roleB0 = role & 0xFF; + record.body.flags = flags; + + int ret = antd_send(cl, (uint8_t*)&record, sizeof(record)); + if(ret != sizeof(record)) + { + ERROR("fcgi_begin_request: Unable to send record data, only %d of %d bytes sent", ret, sizeof(record)); + return -1; + } + return 0; +} + +int fcgi_abort_request(antd_client_t* cl, uint16_t id) +{ + FCGI_Header header; + header.version = FCGI_VERSION_1; + header.type = FCGI_BEGIN_REQUEST; + header.requestIdB1 = id >> 8; + header.requestIdB0 = id & 0xFF; + header.contentLengthB1 = 0; + header.contentLengthB0 = 0; + header.paddingLength = 0; + int ret = antd_send(cl, (uint8_t*)&header, sizeof(header)); + if(ret != sizeof(header)) + { + ERROR("fcgi_abort_request: Unable to send record data, only %d of %d bytes sent", ret, sizeof(header)); + return -1; + } + return 0; +} + +int fcgi_send_param(antd_client_t* cl, int id, const char* key, const char* value) +{ + size_t k_length = strlen(key); + size_t v_length = strlen(value); + //LOG("sending [%s] -> [%s]", key, value); + FCGI_Params_Body* body = NULL; + uint8_t* buff = NULL; + size_t clen = k_length + v_length; + if(clen > 0) + { + size_t max_buff_len = sizeof(FCGI_Params_Body) + k_length + v_length + 8; + + buff = (uint8_t*)malloc(max_buff_len); + if(!buff) + { + ERROR("Unable to allocate PARAMS record buffer memory: %s", strerror(errno)); + return -1; + } + } + + body = (FCGI_Params_Body*) buff; + + FCGI_Header header; + header.version = FCGI_VERSION_1; + header.type = FCGI_PARAMS; + header.requestIdB1 = id >> 8; + header.requestIdB0 = id & 0xFF; + + if(clen > 0) + { + uint8_t encoding_type = (((k_length & 0xFF) >> 7) << 0) | (((v_length & 0xFF)>>7) << 1); + switch(encoding_type) + { + case PARAMS_LENGTH_11: + body->d11.nameLengthB0 = k_length; + body->d11.valueLengthB0 = v_length; + memcpy(body->d11.value, key, k_length); + memcpy(body->d11.value+k_length, value, v_length); + clen += 2; + break; + case PARAMS_LENGTH_14: + body->d14.nameLengthB0 = k_length; + body->d14.valueLengthB3 = (v_length >> 24) | 0x80; + body->d14.valueLengthB2 = (v_length >> 16) & 0xFF; + body->d14.valueLengthB1 = (v_length >> 8) & 0xFF; + body->d14.valueLengthB0 = v_length & 0xFF; + + memcpy(body->d14.value, key, k_length); + memcpy(body->d14.value+k_length, value, v_length); + clen += 5; + break; + case PARAMS_LENGTH_41: + body->d41.valueLengthB0 = v_length; + body->d41.nameLengthB3 = (k_length >> 24) | 0x80; + body->d41.nameLengthB2 = (k_length >> 16) & 0xFF; + body->d41.nameLengthB1 = (k_length >> 8) & 0xFF; + body->d41.nameLengthB0 = k_length & 0xFF; + memcpy(body->d41.value, key, k_length); + memcpy(body->d41.value+k_length, value, v_length); + clen += 5; + break; + case PARAMS_LENGTH_44: + body->d44.nameLengthB3 = (k_length >> 24) | 0x80; + body->d44.nameLengthB2 = (k_length >> 16) & 0xFF; + body->d44.nameLengthB1 = (k_length >> 8) & 0xFF; + body->d44.nameLengthB0 = k_length & 0xFF; + body->d44.valueLengthB3 = (v_length >> 24) | 0x80; + body->d44.valueLengthB2 = (v_length >> 16) & 0xFF; + body->d44.valueLengthB1 = (v_length >> 8) & 0xFF; + body->d44.valueLengthB0 = v_length & 0xFF; + memcpy(body->d44.value, key, k_length); + memcpy(body->d44.value+k_length, value, v_length); + clen += 8; + break; + default: + // this should never happends + free(buff); + return -1; + } + } + + + header.contentLengthB1 = clen >> 8; + header.contentLengthB0 = clen & 0xFF; + header.paddingLength = (clen % 8 == 0)? 0 : 8 - (clen % 8); + + // send the record + int ret = fcgi_send_record(cl, &header, buff, clen + header.paddingLength); + if(buff) + free(buff); + return ret; +} + + +int fcgi_send_stdin(antd_client_t* cl, int id, uint8_t* padded_data, size_t len, uint8_t paddlen) +{ + FCGI_Header header; + header.version = FCGI_VERSION_1; + header.type = FCGI_STDIN; + header.requestIdB1 = id >> 8; + header.requestIdB0 = id & 0xFF; + header.contentLengthB1 = len >> 8; + header.contentLengthB0 = len & 0xFF; + header.paddingLength = paddlen; + // send the record + return fcgi_send_record(cl, &header, padded_data, len + paddlen); +} + + +int fcgi_read_header(antd_client_t* cl, FCGI_Header* header) +{ + uint8_t* buff = (uint8_t*) header; + int ret = antd_recv(cl, buff, sizeof(FCGI_Header)); + if(ret != sizeof(FCGI_Header)) + { + ERROR("Unable to read header: received %d bytes out of %d bytes", ret, sizeof(FCGI_Header)); + return -1; + } + return 0; +} + +int fcgi_read_data(antd_client_t* cl, FCGI_Header* header, uint8_t* buffer) +{ + int len = ((header->contentLengthB1 << 8) | header->contentLengthB0) + header->paddingLength; + int ret = antd_recv(cl, buffer, len); + if(ret != len) + { + ERROR("Unable to read record body: received %d bytes out of %d bytes", ret, len); + return -1; + } + return 0; +} + +uint8_t* fcgi_read_payload(antd_client_t* cl, FCGI_Header* header, int* size) +{ + int len = ((header->contentLengthB1 << 8) | header->contentLengthB0) + header->paddingLength; + uint8_t* buff = (uint8_t*) malloc(len + 1); + if(!buff) + { + ERROR("Unable to allocate buffer of size %d", len); + return NULL; + } + int ret = antd_recv(cl, buff, len); + if(ret != len) + { + ERROR("Unable to read record body: received %d bytes out of %d bytes", ret, len); + free(buff); + return NULL; + } + *size = len - header->paddingLength; + buff[*size] = '\0'; + return buff; +} \ No newline at end of file diff --git a/proto.h b/proto.h new file mode 100644 index 0000000..b4b0c3a --- /dev/null +++ b/proto.h @@ -0,0 +1,165 @@ +#ifndef PROTO_H +#define PROTO_H +#include +#include +/* + * Listening socket file number + */ +#define FCGI_LISTENSOCK_FILENO 0 + +typedef struct { + unsigned char version; + unsigned char type; + unsigned char requestIdB1; + unsigned char requestIdB0; + unsigned char contentLengthB1; + unsigned char contentLengthB0; + unsigned char paddingLength; + unsigned char reserved; +} FCGI_Header; + +/* + * Number of bytes in a FCGI_Header. Future versions of the protocol + * will not reduce this number. + */ +#define FCGI_HEADER_LEN 8 + +/* + * Value for version component of FCGI_Header + */ +#define FCGI_VERSION_1 1 + +/* + * Values for type component of FCGI_Header + */ +#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 FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) + +/* + * Value for requestId component of FCGI_Header + */ +#define FCGI_NULL_REQUEST_ID 0 + +typedef struct { + unsigned char roleB1; + unsigned char roleB0; + unsigned char flags; + unsigned char reserved[5]; +} FCGI_BeginRequestBody; + +typedef struct { + FCGI_Header header; + FCGI_BeginRequestBody body; +} FCGI_BeginRequestRecord; + +/* + * Mask for flags component of FCGI_BeginRequestBody + */ +#define FCGI_KEEP_CONN 1 + +/* + * Values for role component of FCGI_BeginRequestBody + */ +#define FCGI_RESPONDER 1 +#define FCGI_AUTHORIZER 2 +#define FCGI_FILTER 3 + +typedef struct { + unsigned char appStatusB3; + unsigned char appStatusB2; + unsigned char appStatusB1; + unsigned char appStatusB0; + unsigned char protocolStatus; + unsigned char reserved[3]; +} FCGI_EndRequestBody; + +typedef struct { + FCGI_Header header; + FCGI_EndRequestBody body; +} FCGI_EndRequestRecord; + +/* + * Values for protocolStatus component of FCGI_EndRequestBody + */ +#define FCGI_REQUEST_COMPLETE 0 +#define FCGI_CANT_MPX_CONN 1 +#define FCGI_OVERLOADED 2 +#define FCGI_UNKNOWN_ROLE 3 + +/* + * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records + */ +#define FCGI_MAX_CONNS "FCGI_MAX_CONNS" +#define FCGI_MAX_REQS "FCGI_MAX_REQS" +#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS" + +typedef struct { + unsigned char type; + unsigned char reserved[7]; +} FCGI_UnknownTypeBody; + +typedef struct { + FCGI_Header header; + FCGI_UnknownTypeBody body; +} FCGI_UnknownTypeRecord; + +/** +* The role component sets the role the Web server expects the application to play. The currently-defined roles are: +* FCGI_RESPONDER +* FCGI_AUTHORIZER +* FCGI_FILTER +* The flags component contains a bit that controls connection shutdown: +* flags & FCGI_KEEP_CONN: If zero, the application closes the connection +* after responding to this request. If not zero, the application does not +* close the connection after responding to this request; the Web server +* retains responsibility for the connection +*/ +int fcgi_begin_request(antd_client_t* cl, uint16_t id, uint16_t role, uint8_t flags); + +/** +* The Web server sends a FCGI_ABORT_REQUEST record to abort a request. After receiving {FCGI_ABORT_REQUEST, R}, +* the application responds as soon as possible with {FCGI_END_REQUEST, R, {FCGI_REQUEST_COMPLETE, appStatus}}. +* This is truly a response from the application, not a low-level acknowledgement from the FastCGI library. +* A Web server aborts a FastCGI request when an HTTP client closes its transport connection while the FastCGI request is running on behalf of that client. +*/ +int fcgi_abort_request(antd_client_t* cl, uint16_t id); + +/** +* FCGI_PARAMS is a stream record type used in sending name-value pairs from the Web server to the application. The name-value pairs are sent down the stream one after the other, in no specified order. +*/ +int fcgi_send_param(antd_client_t* cl, int id, const char* key, const char* value); +/** +* FCGI_STDIN is a stream record type used in sending arbitrary data from the Web server to the application. +* FCGI_DATA is a second stream record type used to send additional data to the application. +* +* The param data should be padded buffer with size len + paddlen +*/ +int fcgi_send_stdin(antd_client_t* cl, int id, uint8_t* padded_data, size_t len, uint8_t paddlen); + +/** +* Read record header from application +*/ +int fcgi_read_header(antd_client_t* cl, FCGI_Header* header); + +/** +* Read error message from stderr +* +* It is the responisbility of the caller to make +* sure that the buffer lenth is big enough for the +* Content data + padded len +*/ +int fcgi_read_data(antd_client_t* cl, FCGI_Header* header, uint8_t* buffer); + +uint8_t* fcgi_read_payload(antd_client_t* cl, FCGI_Header* header, int* size); + +#endif \ No newline at end of file