adamc@859: #define _GNU_SOURCE adamc@859: adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: adamc@859: #include adamc@859: adamc@859: #include "urweb.h" adamc@859: #include "request.h" adamc@859: #include "queue.h" adamc@859: adamc@859: #include "fastcgi.h" adamc@859: adamc@859: typedef struct { adamc@859: unsigned char version; adamc@859: unsigned char type; adamc@859: unsigned char requestIdB1; adamc@859: unsigned char requestIdB0; adamc@859: unsigned char contentLengthB1; adamc@859: unsigned char contentLengthB0; adamc@859: unsigned char paddingLength; adamc@859: unsigned char reserved; adamc@859: unsigned char contentData[65535]; adamc@859: } FCGI_Record; adamc@859: adamc@859: typedef struct { adamc@859: FCGI_Record r; adamc@859: int sock; adamc@859: } FCGI_Output; adamc@859: adamc@859: typedef struct { adamc@859: char buf[sizeof(FCGI_Record) + 255]; adamc@859: int available, used, sock; adamc@859: } FCGI_Input; adamc@859: adamc@859: static FCGI_Output *fastcgi_output() { adamc@859: FCGI_Output *o = malloc(sizeof(FCGI_Output)); adamc@859: adamc@859: o->r.version = FCGI_VERSION_1; adamc@859: o->r.paddingLength = 0; adamc@859: o->r.reserved = 0; adamc@859: adamc@859: return o; adamc@859: } adamc@859: adamc@859: static FCGI_Input *fastcgi_input() { adamc@859: FCGI_Input *i = malloc(sizeof(FCGI_Input)); adamc@859: adamc@859: i->available = i->used = 0; adamc@859: adamc@859: return i; adamc@859: } adamc@859: adamc@859: static void fastcgi_input_reset(FCGI_Input *i) { adamc@859: i->available = i->used = 0; adamc@859: } adamc@859: adamc@859: static int fastcgi_send(FCGI_Output *o, adamc@859: unsigned char type, adamc@859: unsigned short requestId, adamc@859: unsigned short contentLength) { adamc@859: o->r.type = type; adamc@859: o->r.requestIdB1 = requestId >> 8; adamc@859: o->r.requestIdB0 = requestId & 255; adamc@859: o->r.contentLengthB1 = contentLength >> 8; adamc@859: o->r.contentLengthB0 = contentLength & 255; adamc@859: return uw_really_send(o->sock, &o->r, sizeof(o->r) - (65535 - contentLength)); adamc@859: } adamc@859: adamc@859: #define LATEST(i) ((FCGI_Record *)(i->buf + i->used)) adamc@859: adamc@859: static FCGI_Record *fastcgi_recv(FCGI_Input *i) { adamc@859: while (1) { adamc@859: ssize_t n; adamc@859: adamc@859: if (i->available >= i->used + sizeof(FCGI_Record) - 65535 adamc@859: && i->available >= i->used + sizeof(FCGI_Record) - 65535 adamc@859: + ((LATEST(i)->contentLengthB1 << 8) & LATEST(i)->contentLengthB0) adamc@859: + LATEST(i)->paddingLength) { adamc@859: FCGI_Record *r = LATEST(i); adamc@859: adamc@859: i->used += sizeof(FCGI_Record) - 65535 adamc@859: + ((LATEST(i)->contentLengthB1 << 8) & LATEST(i)->contentLengthB0) adamc@859: + LATEST(i)->paddingLength; adamc@859: adamc@859: return r; adamc@859: } adamc@859: adamc@859: if (i->used > 0) { adamc@859: memmove(i->buf, i->buf + i->used, i->available - i->used); adamc@859: i->available -= i->used; adamc@859: i->used = 0; adamc@859: } adamc@859: adamc@859: n = recv(i->sock, i->buf + i->available, sizeof(i->buf) - i->available, 0); adamc@859: adamc@859: if (n <= 0) adamc@859: return NULL; adamc@859: adamc@859: i->available += n; adamc@859: } adamc@859: } adamc@859: adamc@859: static char *get_header(void *data, const char *h) { adamc@859: return NULL; adamc@859: } adamc@859: adamc@859: static void on_success(uw_context ctx) { } adamc@859: adamc@859: static void on_failure(uw_context ctx) { adamc@859: uw_write_header(ctx, "Status: 500 Internal Server Error\r\n"); adamc@859: } adamc@859: adamc@859: static void write_stderr(FCGI_Output *o, const char *fmt, ...) { adamc@859: int len; adamc@859: va_list ap; adamc@859: va_start(ap, fmt); adamc@859: adamc@859: len = vsnprintf(o->r.contentData, 65535, fmt, ap); adamc@859: if (len < 0) adamc@859: fprintf(stderr, "vsnprintf() failed in log_error().\n"); adamc@859: else if (fastcgi_send(o, FCGI_STDERR, FCGI_NULL_REQUEST_ID, len)) adamc@859: fprintf(stderr, "fastcgi_send() failed in log_error().\n"); adamc@859: } adamc@859: adamc@859: static void log_error(void *data, const char *fmt, ...) { adamc@859: FCGI_Output *o = (FCGI_Output *)data; adamc@859: va_list ap; adamc@859: va_start(ap, fmt); adamc@859: adamc@859: if (o) { adamc@859: int len = vsnprintf(o->r.contentData, 65535, fmt, ap); adamc@859: if (len < 0) adamc@859: fprintf(stderr, "vsnprintf() failed in log_error().\n"); adamc@859: else if (fastcgi_send(o, FCGI_STDERR, FCGI_NULL_REQUEST_ID, len)) adamc@859: fprintf(stderr, "fastcgi_send() failed in log_error().\n"); adamc@859: } else adamc@859: vfprintf(stderr, fmt, ap); adamc@859: } adamc@859: adamc@859: static void log_debug(void *data, const char *fmt, ...) { adamc@859: } adamc@859: adamc@859: static void *worker(void *data) { adamc@859: int me = *(int *)data; adamc@859: FCGI_Input *in = fastcgi_input(); adamc@859: FCGI_Output *out = fastcgi_output(); adamc@859: uw_context ctx = uw_request_new_context(out, log_error, log_debug); adamc@859: uw_request_context rc = uw_new_request_context(); adamc@859: adamc@859: while (1) { adamc@859: FCGI_Record *r; adamc@859: adamc@859: in->sock = out->sock = uw_dequeue(); adamc@859: adamc@859: if (!(r = fastcgi_recv(in))) { adamc@859: fprintf(stderr, "Error receiving initial message\n"); adamc@859: return NULL; adamc@859: } adamc@859: adamc@859: if (r->type != FCGI_BEGIN_REQUEST) { adamc@859: write_stderr(out, "First message is not BEGIN_REQUEST\n"); adamc@859: goto done; adamc@859: } else if (((FCGI_BeginRequestBody *)&r->contentData)->roleB0 != FCGI_RESPONDER) { adamc@859: write_stderr(out, "First message is not BEGIN_REQUEST\n"); adamc@859: goto done; adamc@859: } adamc@859: adamc@859: if (!(r = fastcgi_recv(in))) { adamc@859: fprintf(stderr, "Error receiving second message\n"); adamc@859: return NULL; adamc@859: } adamc@859: write_stderr(out, "Next message code: %d\n", r->type); adamc@859: adamc@859: done: adamc@859: close(in->sock); adamc@859: fastcgi_input_reset(in); adamc@859: uw_reset(ctx); adamc@859: } adamc@859: } adamc@859: adamc@859: static void help(char *cmd) { adamc@859: printf("Usage: %s [-t ]\n", cmd); adamc@859: } adamc@859: adamc@859: static void sigint(int signum) { adamc@859: printf("Exiting....\n"); adamc@859: exit(0); adamc@859: } adamc@859: adamc@859: static loggers ls = {NULL, log_error, log_debug}; adamc@859: adamc@859: int main(int argc, char *argv[]) { adamc@859: // The skeleton for this function comes from Beej's sockets tutorial. adamc@859: struct sockaddr_in their_addr; // connector's address information adamc@859: int sin_size, yes = 1; adamc@859: int nthreads = 1, i, *names, opt; adamc@859: char *fwsa = getenv("FCGI_WEB_SERVER_ADDRS"), *nthreads_s = getenv("URWEB_NUM_THREADS"); adamc@859: adamc@859: if (nthreads_s) { adamc@859: nthreads = atoi(nthreads_s); adamc@859: if (nthreads <= 0) { adamc@859: fprintf(stderr, "Bad URWEB_NUM_THREADS value\n"); adamc@859: return 1; adamc@859: } adamc@859: } adamc@859: adamc@859: signal(SIGINT, sigint); adamc@859: signal(SIGPIPE, SIG_IGN); adamc@859: signal(SIGUSR1, sigint); adamc@859: signal(SIGTERM, sigint); adamc@859: adamc@859: while ((opt = getopt(argc, argv, "ht:")) != -1) { adamc@859: switch (opt) { adamc@859: case '?': adamc@859: fprintf(stderr, "Unknown command-line option"); adamc@859: help(argv[0]); adamc@859: return 1; adamc@859: adamc@859: case 'h': adamc@859: help(argv[0]); adamc@859: return 0; adamc@859: adamc@859: case 't': adamc@859: nthreads = atoi(optarg); adamc@859: if (nthreads <= 0) { adamc@859: fprintf(stderr, "Invalid thread count\n"); adamc@859: help(argv[0]); adamc@859: return 1; adamc@859: } adamc@859: break; adamc@859: adamc@859: default: adamc@859: fprintf(stderr, "Unexpected getopt() behavior\n"); adamc@859: return 1; adamc@859: } adamc@859: } adamc@859: adamc@859: uw_request_init(NULL, log_error, log_debug); adamc@859: adamc@859: names = calloc(nthreads, sizeof(int)); adamc@859: adamc@859: sin_size = sizeof their_addr; adamc@859: adamc@859: { adamc@859: pthread_t thread; adamc@859: int name; adamc@859: adamc@859: if (pthread_create(&thread, NULL, client_pruner, &ls)) { adamc@859: fprintf(stderr, "Error creating pruner thread\n"); adamc@859: return 1; adamc@859: } adamc@859: } adamc@859: adamc@859: for (i = 0; i < nthreads; ++i) { adamc@859: pthread_t thread; adamc@859: names[i] = i; adamc@859: if (pthread_create(&thread, NULL, worker, &names[i])) { adamc@859: fprintf(stderr, "Error creating worker thread #%d\n", i); adamc@859: return 1; adamc@859: } adamc@859: } adamc@859: adamc@859: while (1) { adamc@859: int new_fd = accept(FCGI_LISTENSOCK_FILENO, (struct sockaddr *)&their_addr, &sin_size); adamc@859: adamc@859: if (new_fd < 0) { adamc@859: fprintf(stderr, "Socket accept failed\n"); adamc@859: return 1; adamc@859: } adamc@859: adamc@859: if (fwsa) { adamc@859: char host[100], matched = 0; adamc@859: char *ips, *sep; adamc@859: adamc@859: if (getnameinfo((struct sockaddr *)&their_addr, sin_size, host, sizeof host, NULL, 0, NI_NUMERICHOST)) { adamc@859: fprintf(stderr, "Remote IP determination failed\n"); adamc@859: return 1; adamc@859: } adamc@859: adamc@859: for (ips = fwsa; sep = strchr(ips, ','); ips = sep+1) { adamc@859: if (!strncmp(ips, host, sep - ips)) { adamc@859: matched = 1; adamc@859: break; adamc@859: } adamc@859: } adamc@859: adamc@859: if (!matched && strcmp(ips, host)) { adamc@859: fprintf(stderr, "Remote address is not in FCGI_WEB_SERVER_ADDRS"); adamc@859: return 1; adamc@859: } adamc@859: } adamc@859: adamc@859: uw_enqueue(new_fd); adamc@859: } adamc@859: }