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