server_fcgi.c revision 1.4
1/* $OpenBSD: server_fcgi.c,v 1.4 2014/07/31 17:55:09 reyk Exp $ */ 2 3/* 4 * Copyright (c) 2014 Florian Obser <florian@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19#include <sys/types.h> 20#include <sys/queue.h> 21#include <sys/time.h> 22#include <sys/stat.h> 23#include <sys/socket.h> 24#include <sys/un.h> 25#include <sys/tree.h> 26#include <sys/hash.h> 27 28#include <net/if.h> 29#include <netinet/in_systm.h> 30#include <netinet/in.h> 31#include <netinet/ip.h> 32#include <netinet/tcp.h> 33#include <arpa/inet.h> 34 35#include <errno.h> 36#include <fcntl.h> 37#include <stdlib.h> 38#include <string.h> 39#include <unistd.h> 40#include <stdio.h> 41#include <err.h> 42#include <event.h> 43 44#include <openssl/ssl.h> 45 46#include "httpd.h" 47#include "http.h" 48 49#define FCGI_CONTENT_SIZE 65535 50#define FCGI_PADDING_SIZE 255 51#define FCGI_RECORD_SIZE \ 52 (sizeof(struct fcgi_record_header) + FCGI_CONTENT_SIZE + FCGI_PADDING_SIZE) 53 54#define FCGI_BEGIN_REQUEST 1 55#define FCGI_ABORT_REQUEST 2 56#define FCGI_END_REQUEST 3 57#define FCGI_PARAMS 4 58#define FCGI_STDIN 5 59#define FCGI_STDOUT 6 60#define FCGI_STDERR 7 61#define FCGI_DATA 8 62#define FCGI_GET_VALUES 9 63#define FCGI_GET_VALUES_RESULT 10 64#define FCGI_UNKNOWN_TYPE 11 65#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) 66 67#define FCGI_RESPONDER 1 68 69struct fcgi_record_header { 70 uint8_t version; 71 uint8_t type; 72 uint16_t id; 73 uint16_t content_len; 74 uint8_t padding_len; 75 uint8_t reserved; 76} __packed; 77 78struct fcgi_begin_request_body { 79 uint16_t role; 80 uint8_t flags; 81 uint8_t reserved[5]; 82} __packed; 83 84int server_fcgi_header(struct client *, u_int); 85void server_fcgi_read(struct bufferevent *, void *); 86int fcgi_add_param(uint8_t *, char *, char *, int); 87 88int 89server_fcgi(struct httpd *env, struct client *clt) 90{ 91 struct server_config *srv_conf = clt->clt_srv_conf; 92 struct http_descriptor *desc = clt->clt_desc; 93 struct sockaddr_un sun; 94 struct fcgi_record_header *h; 95 struct fcgi_begin_request_body *begin; 96 size_t len, total_len; 97 int fd; 98 const char *errstr = NULL; 99 uint8_t buf[FCGI_RECORD_SIZE]; 100 uint8_t *params; 101 102 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) 103 goto fail; 104 105 bzero(&sun, sizeof(sun)); 106 sun.sun_family = AF_UNIX; 107 len = strlcpy(sun.sun_path, srv_conf->path, sizeof(sun.sun_path)); 108 if (len >= sizeof(sun.sun_path)) { 109 errstr = "socket path to long"; 110 goto fail; 111 } 112 sun.sun_len = len; 113 114 if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) 115 goto fail; 116 117 if (clt->clt_srvbev != NULL) 118 bufferevent_free(clt->clt_srvbev); 119 120 clt->clt_srvbev = bufferevent_new(fd, server_fcgi_read, 121 NULL, server_file_error, clt); 122 if (clt->clt_srvbev == NULL) { 123 errstr = "failed to allocate fcgi buffer event"; 124 goto fail; 125 } 126 127 bzero(&buf, sizeof(buf)); 128 129 h = (struct fcgi_record_header *) &buf; 130 h->version = 1; 131 h->type = FCGI_BEGIN_REQUEST; 132 h->id = htons(1); 133 h->content_len = htons(sizeof(struct fcgi_begin_request_body)); 134 h->padding_len = 0; 135 136 begin = (struct fcgi_begin_request_body *) &buf[sizeof(struct 137 fcgi_record_header)]; 138 begin->role = htons(FCGI_RESPONDER); 139 140 bufferevent_write(clt->clt_srvbev, &buf, 141 sizeof(struct fcgi_record_header) + 142 sizeof(struct fcgi_begin_request_body)); 143 144 h->type = FCGI_PARAMS; 145 h->content_len = 0; 146 params = &buf[sizeof(struct fcgi_record_header)]; 147 148 total_len = 0; 149 150 len = fcgi_add_param(params, "SCRIPT_NAME", desc->http_path, 151 FCGI_CONTENT_SIZE); 152 params += len; 153 total_len += len; 154 155 if (desc->http_query) { 156 len = fcgi_add_param(params, "QUERY_STRING", desc->http_query, 157 FCGI_CONTENT_SIZE); 158 params += len; 159 total_len += len; 160 } 161 162 h->content_len = htons(total_len); 163 164 bufferevent_write(clt->clt_srvbev, &buf, 165 sizeof(struct fcgi_record_header) + 166 ntohs(h->content_len)); 167 168 h->content_len = 0; 169 170 bufferevent_write(clt->clt_srvbev, &buf, 171 sizeof(struct fcgi_record_header)); 172 173 h->type = FCGI_STDIN; 174 175 bufferevent_write(clt->clt_srvbev, &buf, 176 sizeof(struct fcgi_record_header)); 177 178 bufferevent_settimeout(clt->clt_srvbev, 179 srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); 180 bufferevent_enable(clt->clt_srvbev, EV_READ|EV_WRITE); 181 bufferevent_disable(clt->clt_bev, EV_READ); 182 183 /* 184 * persist is not supported yet because we don't get the 185 * Content-Length from slowcgi and don't support chunked encoding. 186 */ 187 clt->clt_persist = 0; 188 clt->clt_done = 0; 189 190 return (0); 191 fail: 192 if (errstr == NULL) 193 errstr = strerror(errno); 194 server_abort_http(clt, 500, errstr); 195 return (-1); 196} 197 198int 199fcgi_add_param(uint8_t *buf, char *key, char *val, int size) 200{ 201 int len = 0; 202 DPRINTF("%s: %s => %s", __func__, key, val); 203 buf[0] = strlen(key); 204 len++; 205 buf[1] = strlen(val); 206 len++; 207 len += strlcpy(buf + len, key, size - len); 208 len += strlcpy(buf + len, val, size - len); 209 210 return len; 211} 212 213void 214server_fcgi_read(struct bufferevent *bev, void *arg) 215{ 216 struct client *clt = (struct client *) arg; 217 struct fcgi_record_header *h; 218 uint8_t buf[FCGI_RECORD_SIZE]; 219 size_t len; 220 221 len = bufferevent_read(bev, &buf, FCGI_RECORD_SIZE); 222 DPRINTF("%s: %lu", __func__, len); 223 224 h = (struct fcgi_record_header *) &buf; 225 DPRINTF("%s: record header: version %d type %d id %d content len %d", 226 __func__, h->version, h->type, ntohs(h->id), 227 ntohs(h->content_len)); 228 229 if (h->type == FCGI_STDOUT && ntohs(h->content_len) > 0) { 230 DPRINTF("%s", (char *) &buf + 231 sizeof(struct fcgi_record_header)); 232 233 server_fcgi_header(clt, 200); 234 server_bufferevent_write(clt, (char *)&buf + 235 sizeof(struct fcgi_record_header), 236 len - sizeof(struct fcgi_record_header)); 237 } 238} 239 240int 241server_fcgi_header(struct client *clt, u_int code) 242{ 243 struct http_descriptor *desc = clt->clt_desc; 244 const char *error; 245 char tmbuf[32]; 246 247 if (desc == NULL || (error = server_httperror_byid(code)) == NULL) 248 return (-1); 249 250 kv_purge(&desc->http_headers); 251 252 /* Add error codes */ 253 if (kv_setkey(&desc->http_pathquery, "%lu", code) == -1 || 254 kv_set(&desc->http_pathquery, "%s", error) == -1) 255 return (-1); 256 257 /* Add headers */ 258 if (kv_add(&desc->http_headers, "Server", HTTPD_SERVERNAME) == NULL) 259 return (-1); 260 261 /* Is it a persistent connection? */ 262 if (clt->clt_persist) { 263 if (kv_add(&desc->http_headers, 264 "Connection", "keep-alive") == NULL) 265 return (-1); 266 } else if (kv_add(&desc->http_headers, "Connection", "close") == NULL) 267 return (-1); 268 269 /* Date header is mandatory and should be added last */ 270 server_http_date(tmbuf, sizeof(tmbuf)); 271 if (kv_add(&desc->http_headers, "Date", tmbuf) == NULL) 272 return (-1); 273 274 /* Write initial header (fcgi might append more) */ 275 if (server_writeresponse_http(clt) == -1 || 276 server_bufferevent_print(clt, "\r\n") == -1 || 277 server_writeheader_http(clt) == -1) 278 return (-1); 279 280 return (0); 281} 282