1 /*
2 * $Id: fcgi_protocol.c,v 1.27 2008/09/23 14:48:13 robs Exp $
3 */
4
5 #include "fcgi.h"
6 #include "fcgi_protocol.h"
7
8 #ifdef APACHE2
9 #include "apr_lib.h"
10 #endif
11
12 #ifdef WIN32
13 #pragma warning( disable : 4706)
14 #endif
15
16 /*******************************************************************************
17 * Build and queue a FastCGI message header. It is the caller's
18 * responsibility to make sure that there's enough space in the buffer, and
19 * that the data bytes (specified by 'len') are queued immediately following
20 * this header.
21 */
queue_header(fcgi_request * fr,unsigned char type,unsigned int len)22 static void queue_header(fcgi_request *fr, unsigned char type, unsigned int len)
23 {
24 FCGI_Header header;
25
26 ASSERT(type > 0);
27 ASSERT(type <= FCGI_MAXTYPE);
28 ASSERT(len <= 0xffff);
29 ASSERT(BufferFree(fr->serverOutputBuffer) >= sizeof(FCGI_Header));
30
31 /* Assemble and queue the packet header. */
32 header.version = FCGI_VERSION;
33 header.type = type;
34 header.requestIdB1 = (unsigned char) (fr->requestId >> 8);
35 header.requestIdB0 = (unsigned char) fr->requestId;
36 header.contentLengthB1 = (unsigned char) (len / 256); /* MSB */
37 header.contentLengthB0 = (unsigned char) (len % 256); /* LSB */
38 header.paddingLength = 0;
39 header.reserved = 0;
40 fcgi_buf_add_block(fr->serverOutputBuffer, (char *) &header, sizeof(FCGI_Header));
41 }
42
43 /*******************************************************************************
44 * Build a FCGI_BeginRequest message body.
45 */
build_begin_request(unsigned int role,unsigned char keepConnection,FCGI_BeginRequestBody * body)46 static void build_begin_request(unsigned int role, unsigned char keepConnection,
47 FCGI_BeginRequestBody *body)
48 {
49 ASSERT((role >> 16) == 0);
50 body->roleB1 = (unsigned char) (role >> 8);
51 body->roleB0 = (unsigned char) role;
52 body->flags = (unsigned char) ((keepConnection) ? FCGI_KEEP_CONN : 0);
53 memset(body->reserved, 0, sizeof(body->reserved));
54 }
55
56 /*******************************************************************************
57 * Build and queue a FastCGI "Begin Request" message.
58 */
fcgi_protocol_queue_begin_request(fcgi_request * fr)59 void fcgi_protocol_queue_begin_request(fcgi_request *fr)
60 {
61 FCGI_BeginRequestBody body;
62 int bodySize = sizeof(FCGI_BeginRequestBody);
63
64 /* We should be the first ones to use this buffer */
65 ASSERT(BufferLength(fr->serverOutputBuffer) == 0);
66
67 build_begin_request(fr->role, FALSE, &body);
68 queue_header(fr, FCGI_BEGIN_REQUEST, bodySize);
69 fcgi_buf_add_block(fr->serverOutputBuffer, (char *) &body, bodySize);
70 }
71
72 /*******************************************************************************
73 * Build a FastCGI name-value pair (env) header.
74 */
build_env_header(int nameLen,int valueLen,unsigned char * headerBuffPtr,int * headerLenPtr)75 static void build_env_header(int nameLen, int valueLen,
76 unsigned char *headerBuffPtr, int *headerLenPtr)
77 {
78 unsigned char *startHeaderBuffPtr = headerBuffPtr;
79
80 ASSERT(nameLen >= 0);
81
82 if (nameLen < 0x80) {
83 *headerBuffPtr++ = (unsigned char) nameLen;
84 } else {
85 *headerBuffPtr++ = (unsigned char) ((nameLen >> 24) | 0x80);
86 *headerBuffPtr++ = (unsigned char) (nameLen >> 16);
87 *headerBuffPtr++ = (unsigned char) (nameLen >> 8);
88 *headerBuffPtr++ = (unsigned char) nameLen;
89 }
90
91 ASSERT(valueLen >= 0);
92
93 if (valueLen < 0x80) {
94 *headerBuffPtr++ = (unsigned char) valueLen;
95 } else {
96 *headerBuffPtr++ = (unsigned char) ((valueLen >> 24) | 0x80);
97 *headerBuffPtr++ = (unsigned char) (valueLen >> 16);
98 *headerBuffPtr++ = (unsigned char) (valueLen >> 8);
99 *headerBuffPtr++ = (unsigned char) valueLen;
100 }
101 *headerLenPtr = headerBuffPtr - startHeaderBuffPtr;
102 }
103
104 /* A static fn stolen from Apache's util_script.c...
105 * Obtain the Request-URI from the original request-line, returning
106 * a new string from the request pool containing the URI or "".
107 */
apache_original_uri(request_rec * r)108 static char *apache_original_uri(request_rec *r)
109 {
110 char *first, *last;
111
112 if (r->the_request == NULL)
113 return (char *) ap_pcalloc(r->pool, 1);
114
115 first = r->the_request; /* use the request-line */
116
117 while (*first && !ap_isspace(*first))
118 ++first; /* skip over the method */
119
120 while (ap_isspace(*first))
121 ++first; /* and the space(s) */
122
123 last = first;
124 while (*last && !ap_isspace(*last))
125 ++last; /* end at next whitespace */
126
127 return ap_pstrndup(r->pool, first, last - first);
128 }
129
130 /* Based on Apache's ap_add_cgi_vars() in util_script.c.
131 * Apache's spins in sub_req_lookup_uri() trying to setup PATH_TRANSLATED,
132 * so we just don't do that part.
133 */
add_auth_cgi_vars(request_rec * r,const int compat)134 static void add_auth_cgi_vars(request_rec *r, const int compat)
135 {
136 table *e = r->subprocess_env;
137
138 ap_table_setn(e, "GATEWAY_INTERFACE", "CGI/1.1");
139 ap_table_setn(e, "SERVER_PROTOCOL", r->protocol);
140 ap_table_setn(e, "REQUEST_METHOD", r->method);
141 ap_table_setn(e, "QUERY_STRING", r->args ? r->args : "");
142 ap_table_setn(e, "REQUEST_URI", apache_original_uri(r));
143
144 /* The FastCGI spec precludes sending of CONTENT_LENGTH, PATH_INFO,
145 * PATH_TRANSLATED, and SCRIPT_NAME (for some reason?). PATH_TRANSLATED we
146 * don't have, its the variable that causes Apache to break trying to set
147 * up (and thus the reason this fn exists vs. using ap_add_cgi_vars()). */
148 if (compat) {
149 ap_table_unset(e, "CONTENT_LENGTH");
150 return;
151 }
152
153 /* Note that the code below special-cases scripts run from includes,
154 * because it "knows" that the sub_request has been hacked to have the
155 * args and path_info of the original request, and not any that may have
156 * come with the script URI in the include command. Ugh. */
157 if (!strcmp(r->protocol, "INCLUDED")) {
158 ap_table_setn(e, "SCRIPT_NAME", r->uri);
159 if (r->path_info && *r->path_info)
160 ap_table_setn(e, "PATH_INFO", r->path_info);
161 }
162 else if (!r->path_info || !*r->path_info)
163 ap_table_setn(e, "SCRIPT_NAME", r->uri);
164 else {
165 int path_info_start = ap_find_path_info(r->uri, r->path_info);
166
167 ap_table_setn(e, "SCRIPT_NAME", ap_pstrndup(r->pool, r->uri, path_info_start));
168 ap_table_setn(e, "PATH_INFO", r->path_info);
169 }
170 }
171
172 /* copied from util_script.c */
http2env(pool * a,const char * w)173 static char *http2env(pool *a, const char *w)
174 {
175 char *res = (char *) ap_palloc(a, sizeof("HTTP_") + strlen(w));
176 char *cp = res;
177 char c;
178
179 *cp++ = 'H';
180 *cp++ = 'T';
181 *cp++ = 'T';
182 *cp++ = 'P';
183 *cp++ = '_';
184
185 while ((c = *w++) != 0) {
186 if (!ap_isalnum(c)) {
187 *cp++ = '_';
188 }
189 else {
190 *cp++ = (char) ap_toupper(c);
191 }
192 }
193 *cp = 0;
194
195 return res;
196 }
197
add_pass_header_vars(fcgi_request * fr)198 static void add_pass_header_vars(fcgi_request *fr)
199 {
200 const array_header *ph = fr->dynamic ? dynamic_pass_headers : fr->fs->pass_headers;
201
202 if (ph) {
203 const char **elt = (const char **)ph->elts;
204 int i = ph->nelts;
205
206 for ( ; i; --i, ++elt) {
207 const char *val = ap_table_get(fr->r->headers_in, *elt);
208 if (val) {
209 const char *key = *elt;
210 #ifndef USE_BROKEN_PASS_HEADER
211 key = http2env(fr->r->pool, key);
212 #endif
213 ap_table_setn(fr->r->subprocess_env, key, val);
214 }
215 }
216 }
217 }
218
219 /*******************************************************************************
220 * Build and queue the environment name-value pairs. Returns TRUE if the
221 * complete ENV was buffered, FALSE otherwise. Note: envp is updated to
222 * reflect the current position in the ENV.
223 */
fcgi_protocol_queue_env(request_rec * r,fcgi_request * fr,env_status * env)224 int fcgi_protocol_queue_env(request_rec *r, fcgi_request *fr, env_status *env)
225 {
226 int charCount;
227
228 if (env->envp == NULL) {
229 ap_add_common_vars(r);
230 add_pass_header_vars(fr);
231
232 if (fr->role == FCGI_RESPONDER)
233 ap_add_cgi_vars(r);
234 else
235 add_auth_cgi_vars(r, fr->auth_compat);
236
237 env->envp = ap_create_environment(r->pool, r->subprocess_env);
238 env->pass = PREP;
239 }
240
241 while (*env->envp) {
242 switch (env->pass)
243 {
244 case PREP:
245 env->equalPtr = strchr(*env->envp, '=');
246 ASSERT(env->equalPtr != NULL);
247 env->nameLen = env->equalPtr - *env->envp;
248 env->valueLen = strlen(++env->equalPtr);
249 build_env_header(env->nameLen, env->valueLen, env->headerBuff, &env->headerLen);
250 env->totalLen = env->headerLen + env->nameLen + env->valueLen;
251 env->pass = HEADER;
252 /* drop through */
253
254 case HEADER:
255 if (BufferFree(fr->serverOutputBuffer) < (int)(sizeof(FCGI_Header) + env->headerLen)) {
256 return (FALSE);
257 }
258 queue_header(fr, FCGI_PARAMS, env->totalLen);
259 fcgi_buf_add_block(fr->serverOutputBuffer, (char *)env->headerBuff, env->headerLen);
260 env->pass = NAME;
261 /* drop through */
262
263 case NAME:
264 charCount = fcgi_buf_add_block(fr->serverOutputBuffer, *env->envp, env->nameLen);
265 if (charCount != env->nameLen) {
266 *env->envp += charCount;
267 env->nameLen -= charCount;
268 return (FALSE);
269 }
270 env->pass = VALUE;
271 /* drop through */
272
273 case VALUE:
274 charCount = fcgi_buf_add_block(fr->serverOutputBuffer, env->equalPtr, env->valueLen);
275 if (charCount != env->valueLen) {
276 env->equalPtr += charCount;
277 env->valueLen -= charCount;
278 return (FALSE);
279 }
280 env->pass = PREP;
281 }
282 ++env->envp;
283 }
284
285 if (BufferFree(fr->serverOutputBuffer) < sizeof(FCGI_Header)) {
286 return(FALSE);
287 }
288 queue_header(fr, FCGI_PARAMS, 0);
289 return(TRUE);
290 }
291
292 /*******************************************************************************
293 * Queue data from the client input buffer to the FastCGI server output
294 * buffer (encapsulating the data in FastCGI protocol messages).
295 */
fcgi_protocol_queue_client_buffer(fcgi_request * fr)296 void fcgi_protocol_queue_client_buffer(fcgi_request *fr)
297 {
298 int movelen;
299 int in_len, out_free;
300
301 if (fr->eofSent)
302 return;
303
304 /*
305 * If there's some client data and room for at least one byte
306 * of data in the output buffer (after protocol overhead), then
307 * move some data to the output buffer.
308 */
309 in_len = BufferLength(fr->clientInputBuffer);
310 out_free = max(0, BufferFree(fr->serverOutputBuffer) - sizeof(FCGI_Header));
311 movelen = min(in_len, out_free);
312 if (movelen > 0) {
313 queue_header(fr, FCGI_STDIN, movelen);
314 fcgi_buf_get_to_buf(fr->serverOutputBuffer, fr->clientInputBuffer, movelen);
315 }
316
317 /*
318 * If all the client data has been sent, and there's room
319 * in the output buffer, indicate EOF.
320 */
321 if (movelen == in_len && fr->expectingClientContent <= 0
322 && BufferFree(fr->serverOutputBuffer) >= sizeof(FCGI_Header))
323 {
324 queue_header(fr, FCGI_STDIN, 0);
325 fr->eofSent = TRUE;
326 }
327 }
328
329 /*******************************************************************************
330 * Read FastCGI protocol messages from the FastCGI server input buffer into
331 * fr->header when parsing headers, to fr->fs_stderr when reading stderr data,
332 * or to the client output buffer otherwises.
333 */
fcgi_protocol_dequeue(pool * p,fcgi_request * fr)334 int fcgi_protocol_dequeue(pool *p, fcgi_request *fr)
335 {
336 FCGI_Header header;
337 int len;
338
339 while (BufferLength(fr->serverInputBuffer) > 0) {
340 /*
341 * State #1: looking for the next complete packet header.
342 */
343 if (fr->gotHeader == FALSE) {
344 if (BufferLength(fr->serverInputBuffer) < sizeof(FCGI_Header)) {
345 return OK;
346 }
347 fcgi_buf_get_to_block(fr->serverInputBuffer, (char *) &header,
348 sizeof(FCGI_Header));
349 /*
350 * XXX: Better handling of packets with other version numbers
351 * and other packet problems.
352 */
353 if (header.version != FCGI_VERSION) {
354 ap_log_rerror(FCGI_LOG_ERR_NOERRNO, fr->r,
355 "FastCGI: comm with server \"%s\" aborted: protocol error: invalid version: %d != FCGI_VERSION(%d)",
356 fr->fs_path, header.version, FCGI_VERSION);
357 return HTTP_INTERNAL_SERVER_ERROR;
358 }
359 if (header.type > FCGI_MAXTYPE) {
360 ap_log_rerror(FCGI_LOG_ERR_NOERRNO, fr->r,
361 "FastCGI: comm with server \"%s\" aborted: protocol error: invalid type: %d > FCGI_MAXTYPE(%d)",
362 fr->fs_path, header.type, FCGI_MAXTYPE);
363 return HTTP_INTERNAL_SERVER_ERROR;
364 }
365
366 fr->packetType = header.type;
367 fr->dataLen = (header.contentLengthB1 << 8)
368 + header.contentLengthB0;
369 fr->gotHeader = TRUE;
370 fr->paddingLen = header.paddingLength;
371 }
372
373 /*
374 * State #2: got a header, and processing packet bytes.
375 */
376 len = min(fr->dataLen, BufferLength(fr->serverInputBuffer));
377 ASSERT(len >= 0);
378 switch (fr->packetType) {
379 case FCGI_STDOUT:
380 if (len > 0) {
381 switch(fr->parseHeader) {
382 case SCAN_CGI_READING_HEADERS:
383 fcgi_buf_get_to_array(fr->serverInputBuffer, fr->header, len);
384 break;
385 case SCAN_CGI_FINISHED:
386 len = min(BufferFree(fr->clientOutputBuffer), len);
387 if (len > 0) {
388 fcgi_buf_get_to_buf(fr->clientOutputBuffer, fr->serverInputBuffer, len);
389 } else {
390 return OK;
391 }
392 break;
393 default:
394 /* Toss data on the floor */
395 fcgi_buf_removed(fr->serverInputBuffer, len);
396 break;
397 }
398 fr->dataLen -= len;
399 }
400 break;
401
402 case FCGI_STDERR:
403
404 if (fr->fs_stderr == NULL)
405 {
406 fr->fs_stderr = ap_palloc(p, FCGI_SERVER_MAX_STDERR_LINE_LEN + 1);
407 }
408
409 /* We're gonna consume all thats here */
410 fr->dataLen -= len;
411
412 while (len > 0)
413 {
414 char *null, *end, *start = fr->fs_stderr;
415
416 /* Get as much as will fit in the buffer */
417 int get_len = min(len, FCGI_SERVER_MAX_STDERR_LINE_LEN - fr->fs_stderr_len);
418 fcgi_buf_get_to_block(fr->serverInputBuffer, start + fr->fs_stderr_len, get_len);
419 len -= get_len;
420 fr->fs_stderr_len += get_len;
421 *(start + fr->fs_stderr_len) = '\0';
422
423 /* Disallow nulls, we could be nicer but this is the motivator */
424 while ((null = memchr(start, '\0', fr->fs_stderr_len)))
425 {
426 int discard = ++null - start;
427 ap_log_rerror(FCGI_LOG_ERR_NOERRNO, fr->r,
428 "FastCGI: server \"%s\" sent a null character in the stderr stream!?, "
429 "discarding %d characters of stderr", fr->fs_path, discard);
430 start = null;
431 fr->fs_stderr_len -= discard;
432 }
433
434 /* Print as much as possible */
435 while ((end = strpbrk(start, "\r\n")))
436 {
437 if (start != end)
438 {
439 *end = '\0';
440 ap_log_rerror(FCGI_LOG_ERR_NOERRNO, fr->r,
441 "FastCGI: server \"%s\" stderr: %s", fr->fs_path, start);
442 }
443 end++;
444 end += strspn(end, "\r\n");
445 fr->fs_stderr_len -= (end - start);
446 start = end;
447 }
448
449 if (fr->fs_stderr_len)
450 {
451 if (start != fr->fs_stderr)
452 {
453 /* Move leftovers down */
454 memmove(fr->fs_stderr, start, fr->fs_stderr_len);
455 }
456 else if (fr->fs_stderr_len == FCGI_SERVER_MAX_STDERR_LINE_LEN)
457 {
458 /* Full buffer, dump it and complain */
459 ap_log_rerror(FCGI_LOG_ERR_NOERRNO, fr->r,
460 "FastCGI: server \"%s\" stderr: %s", fr->fs_path, fr->fs_stderr);
461 ap_log_rerror(FCGI_LOG_WARN_NOERRNO, fr->r,
462 "FastCGI: too much stderr received from server \"%s\", "
463 "increase FCGI_SERVER_MAX_STDERR_LINE_LEN (%d) and rebuild "
464 "or use \"\\n\" to terminate lines",
465 fr->fs_path, FCGI_SERVER_MAX_STDERR_LINE_LEN);
466 fr->fs_stderr_len = 0;
467 }
468 }
469 }
470 break;
471
472 case FCGI_END_REQUEST:
473 if (!fr->readingEndRequestBody) {
474 if (fr->dataLen != sizeof(FCGI_EndRequestBody)) {
475 ap_log_rerror(FCGI_LOG_ERR_NOERRNO, fr->r,
476 "FastCGI: comm with server \"%s\" aborted: protocol error: "
477 "invalid FCGI_END_REQUEST size: "
478 "%d != sizeof(FCGI_EndRequestBody)(%zu)",
479 fr->fs_path, fr->dataLen, sizeof(FCGI_EndRequestBody));
480 return HTTP_INTERNAL_SERVER_ERROR;
481 }
482 fr->readingEndRequestBody = TRUE;
483 }
484 if (len>0) {
485 fcgi_buf_get_to_buf(fr->erBufPtr, fr->serverInputBuffer, len);
486 fr->dataLen -= len;
487 }
488 if (fr->dataLen == 0) {
489 FCGI_EndRequestBody *erBody = &fr->endRequestBody;
490 fcgi_buf_get_to_block(
491 fr->erBufPtr, (char *) &fr->endRequestBody,
492 sizeof(FCGI_EndRequestBody));
493 if (erBody->protocolStatus != FCGI_REQUEST_COMPLETE) {
494 /*
495 * XXX: What to do with FCGI_OVERLOADED?
496 */
497 ap_log_rerror(FCGI_LOG_ERR_NOERRNO, fr->r,
498 "FastCGI: comm with server \"%s\" aborted: protocol error: invalid FCGI_END_REQUEST status: "
499 "%d != FCGI_REQUEST_COMPLETE(%d)", fr->fs_path,
500 erBody->protocolStatus, FCGI_REQUEST_COMPLETE);
501 return HTTP_INTERNAL_SERVER_ERROR;
502 }
503 fr->exitStatus = (erBody->appStatusB3 << 24)
504 + (erBody->appStatusB2 << 16)
505 + (erBody->appStatusB1 << 8)
506 + (erBody->appStatusB0 );
507 fr->exitStatusSet = TRUE;
508 fr->readingEndRequestBody = FALSE;
509 }
510 break;
511 case FCGI_GET_VALUES_RESULT:
512 /* XXX coming soon */
513 case FCGI_UNKNOWN_TYPE:
514 /* XXX coming soon */
515
516 /*
517 * XXX Ignore unknown packet types from the FastCGI server.
518 */
519 default:
520 fcgi_buf_toss(fr->serverInputBuffer, len);
521 fr->dataLen -= len;
522 break;
523 } /* switch */
524
525 /*
526 * Discard padding, then start looking for
527 * the next header.
528 */
529 if (fr->dataLen == 0) {
530 if (fr->paddingLen > 0) {
531 len = min(fr->paddingLen,
532 BufferLength(fr->serverInputBuffer));
533 fcgi_buf_toss(fr->serverInputBuffer, len);
534 fr->paddingLen -= len;
535 }
536 if (fr->paddingLen == 0) {
537 fr->gotHeader = FALSE;
538 }
539 }
540 } /* while */
541 return OK;
542 }
543