1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "mod_proxy.h"
18#include "util_fcgi.h"
19#include "util_script.h"
20
21module AP_MODULE_DECLARE_DATA proxy_fcgi_module;
22
23/*
24 * Canonicalise http-like URLs.
25 * scheme is the scheme for the URL
26 * url is the URL starting with the first '/'
27 * def_port is the default port for this scheme.
28 */
29static int proxy_fcgi_canon(request_rec *r, char *url)
30{
31    char *host, sport[7];
32    const char *err, *path;
33    apr_port_t port, def_port;
34
35    if (strncasecmp(url, "fcgi:", 5) == 0) {
36        url += 5;
37    }
38    else {
39        return DECLINED;
40    }
41
42    port = def_port = ap_proxy_port_of_scheme("fcgi");
43
44    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
45                 "canonicalising URL %s", url);
46    err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
47    if (err) {
48        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01059)
49                      "error parsing URL %s: %s", url, err);
50        return HTTP_BAD_REQUEST;
51    }
52
53    if (port != def_port)
54        apr_snprintf(sport, sizeof(sport), ":%d", port);
55    else
56        sport[0] = '\0';
57
58    if (ap_strchr_c(host, ':')) {
59        /* if literal IPv6 address */
60        host = apr_pstrcat(r->pool, "[", host, "]", NULL);
61    }
62
63    if (apr_table_get(r->notes, "proxy-nocanon")) {
64        path = url;   /* this is the raw path */
65    }
66    else {
67        path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
68                             r->proxyreq);
69    }
70    if (path == NULL)
71        return HTTP_BAD_REQUEST;
72
73    r->filename = apr_pstrcat(r->pool, "proxy:fcgi://", host, sport, "/",
74                              path, NULL);
75
76    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01060)
77                  "set r->filename to %s", r->filename);
78
79    if (apr_table_get(r->subprocess_env, "proxy-fcgi-pathinfo")) {
80        r->path_info = apr_pstrcat(r->pool, "/", path, NULL);
81
82        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01061)
83                      "set r->path_info to %s", r->path_info);
84    }
85
86    return OK;
87}
88
89/* Wrapper for apr_socket_sendv that handles updating the worker stats. */
90static apr_status_t send_data(proxy_conn_rec *conn,
91                              struct iovec *vec,
92                              int nvec,
93                              apr_size_t *len,
94                              int blocking)
95{
96    apr_status_t rv = APR_SUCCESS, arv;
97    apr_size_t written = 0, to_write = 0;
98    int i, offset;
99    apr_interval_time_t old_timeout;
100    apr_socket_t *s = conn->sock;
101
102    if (!blocking) {
103        arv = apr_socket_timeout_get(s, &old_timeout);
104        if (arv != APR_SUCCESS) {
105            return arv;
106        }
107        arv = apr_socket_timeout_set(s, 0);
108        if (arv != APR_SUCCESS) {
109            return arv;
110        }
111    }
112
113    for (i = 0; i < nvec; i++) {
114        to_write += vec[i].iov_len;
115    }
116
117    offset = 0;
118    while (to_write) {
119        apr_size_t n = 0;
120        rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n);
121        if ((rv != APR_SUCCESS) && !APR_STATUS_IS_EAGAIN(rv)) {
122            break;
123        }
124        if (n > 0) {
125            written += n;
126            if (written >= to_write)
127                break;                 /* short circuit out */
128            for (i = offset; i < nvec; ) {
129                if (n >= vec[i].iov_len) {
130                    offset++;
131                    n -= vec[i++].iov_len;
132                } else {
133                    vec[i].iov_len -= n;
134                    vec[i].iov_base = (char *) vec[i].iov_base + n;
135                    break;
136                }
137            }
138        }
139    }
140
141    conn->worker->s->transferred += written;
142    *len = written;
143
144    if (!blocking) {
145        arv = apr_socket_timeout_set(s, old_timeout);
146        if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) {
147            return arv;
148        }
149    }
150    return rv;
151}
152
153/* Wrapper for apr_socket_recv that handles updating the worker stats. */
154static apr_status_t get_data(proxy_conn_rec *conn,
155                             char *buffer,
156                             apr_size_t *buflen)
157{
158    apr_status_t rv = apr_socket_recv(conn->sock, buffer, buflen);
159
160    if (rv == APR_SUCCESS) {
161        conn->worker->s->read += *buflen;
162    }
163
164    return rv;
165}
166
167static apr_status_t get_data_full(proxy_conn_rec *conn,
168                                  char *buffer,
169                                  apr_size_t buflen)
170{
171    apr_size_t readlen;
172    apr_size_t cumulative_len = 0;
173    apr_status_t rv;
174
175    do {
176        readlen = buflen - cumulative_len;
177        rv = get_data(conn, buffer + cumulative_len, &readlen);
178        if (rv != APR_SUCCESS) {
179            return rv;
180        }
181        cumulative_len += readlen;
182    } while (cumulative_len < buflen);
183
184    return APR_SUCCESS;
185}
186
187static apr_status_t send_begin_request(proxy_conn_rec *conn,
188                                       apr_uint16_t request_id)
189{
190    struct iovec vec[2];
191    ap_fcgi_header header;
192    unsigned char farray[AP_FCGI_HEADER_LEN];
193    ap_fcgi_begin_request_body brb;
194    unsigned char abrb[AP_FCGI_HEADER_LEN];
195    apr_size_t len;
196
197    ap_fcgi_fill_in_header(&header, AP_FCGI_BEGIN_REQUEST, request_id,
198                           sizeof(abrb), 0);
199
200    ap_fcgi_fill_in_request_body(&brb, AP_FCGI_RESPONDER, AP_FCGI_KEEP_CONN);
201
202    ap_fcgi_header_to_array(&header, farray);
203    ap_fcgi_begin_request_body_to_array(&brb, abrb);
204
205    vec[0].iov_base = (void *)farray;
206    vec[0].iov_len = sizeof(farray);
207    vec[1].iov_base = (void *)abrb;
208    vec[1].iov_len = sizeof(abrb);
209
210    return send_data(conn, vec, 2, &len, 1);
211}
212
213static apr_status_t send_environment(proxy_conn_rec *conn, request_rec *r,
214                                     apr_pool_t *temp_pool,
215                                     apr_uint16_t request_id)
216{
217    const apr_array_header_t *envarr;
218    const apr_table_entry_t *elts;
219    struct iovec vec[2];
220    ap_fcgi_header header;
221    unsigned char farray[AP_FCGI_HEADER_LEN];
222    char *body;
223    apr_status_t rv;
224    apr_size_t avail_len, len, required_len;
225    int next_elem, starting_elem;
226
227    ap_add_common_vars(r);
228    ap_add_cgi_vars(r);
229
230    /* XXX are there any FastCGI specific env vars we need to send? */
231
232    /* XXX mod_cgi/mod_cgid use ap_create_environment here, which fills in
233     *     the TZ value specially.  We could use that, but it would mean
234     *     parsing the key/value pairs back OUT of the allocated env array,
235     *     not to mention allocating a totally useless array in the first
236     *     place, which would suck. */
237
238    envarr = apr_table_elts(r->subprocess_env);
239    elts = (const apr_table_entry_t *) envarr->elts;
240
241#ifdef FCGI_DUMP_ENV_VARS
242    {
243        int i;
244
245        for (i = 0; i < envarr->nelts; ++i) {
246            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01062)
247                          "sending env var '%s' value '%s'",
248                          elts[i].key, elts[i].val);
249        }
250    }
251#endif
252
253    /* Send envvars over in as many FastCGI records as it takes, */
254    next_elem = 0; /* starting with the first one */
255
256    avail_len = 16 * 1024; /* our limit per record, which could have been up
257                            * to AP_FCGI_MAX_CONTENT_LEN
258                            */
259
260    while (next_elem < envarr->nelts) {
261        starting_elem = next_elem;
262        required_len = ap_fcgi_encoded_env_len(r->subprocess_env,
263                                               avail_len,
264                                               &next_elem);
265
266        if (!required_len) {
267            if (next_elem < envarr->nelts) {
268                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
269                              APLOGNO(02536) "couldn't encode envvar '%s' in %"
270                              APR_SIZE_T_FMT " bytes",
271                              elts[next_elem].key, avail_len);
272                /* skip this envvar and continue */
273                ++next_elem;
274                continue;
275            }
276            /* only an unused element at the end of the array */
277            break;
278        }
279
280        body = apr_palloc(temp_pool, required_len);
281        rv = ap_fcgi_encode_env(r, r->subprocess_env, body, required_len,
282                                &starting_elem);
283        /* we pre-compute, so we can't run out of space */
284        ap_assert(rv == APR_SUCCESS);
285        /* compute and encode must be in sync */
286        ap_assert(starting_elem == next_elem);
287
288        ap_fcgi_fill_in_header(&header, AP_FCGI_PARAMS, request_id,
289                               (apr_uint16_t)required_len, 0);
290        ap_fcgi_header_to_array(&header, farray);
291
292        vec[0].iov_base = (void *)farray;
293        vec[0].iov_len = sizeof(farray);
294        vec[1].iov_base = body;
295        vec[1].iov_len = required_len;
296
297        rv = send_data(conn, vec, 2, &len, 1);
298        apr_pool_clear(temp_pool);
299
300        if (rv) {
301            return rv;
302        }
303    }
304
305    /* Envvars sent, so say we're done */
306    ap_fcgi_fill_in_header(&header, AP_FCGI_PARAMS, request_id, 0, 0);
307    ap_fcgi_header_to_array(&header, farray);
308
309    vec[0].iov_base = (void *)farray;
310    vec[0].iov_len = sizeof(farray);
311
312    return send_data(conn, vec, 1, &len, 1);
313}
314
315enum {
316  HDR_STATE_READING_HEADERS,
317  HDR_STATE_GOT_CR,
318  HDR_STATE_GOT_CRLF,
319  HDR_STATE_GOT_CRLFCR,
320  HDR_STATE_GOT_LF,
321  HDR_STATE_DONE_WITH_HEADERS
322};
323
324/* Try to find the end of the script headers in the response from the back
325 * end fastcgi server. STATE holds the current header parsing state for this
326 * request.
327 *
328 * Returns 0 if it can't find the end of the headers, and 1 if it found the
329 * end of the headers. */
330static int handle_headers(request_rec *r,
331                          int *state,
332                          char *readbuf)
333{
334    const char *itr = readbuf;
335
336    while (*itr) {
337        if (*itr == '\r') {
338            switch (*state) {
339                case HDR_STATE_GOT_CRLF:
340                    *state = HDR_STATE_GOT_CRLFCR;
341                    break;
342
343                default:
344                    *state = HDR_STATE_GOT_CR;
345                    break;
346            }
347        }
348        else if (*itr == '\n') {
349            switch (*state) {
350                 case HDR_STATE_GOT_LF:
351                     *state = HDR_STATE_DONE_WITH_HEADERS;
352                     break;
353
354                 case HDR_STATE_GOT_CR:
355                     *state = HDR_STATE_GOT_CRLF;
356                     break;
357
358                 case HDR_STATE_GOT_CRLFCR:
359                     *state = HDR_STATE_DONE_WITH_HEADERS;
360                     break;
361
362                 default:
363                     *state = HDR_STATE_GOT_LF;
364                     break;
365            }
366        }
367        else {
368            *state = HDR_STATE_READING_HEADERS;
369        }
370
371        if (*state == HDR_STATE_DONE_WITH_HEADERS)
372            break;
373
374        ++itr;
375    }
376
377    if (*state == HDR_STATE_DONE_WITH_HEADERS) {
378        return 1;
379    }
380
381    return 0;
382}
383
384static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf,
385                             request_rec *r, apr_pool_t *setaside_pool,
386                             apr_uint16_t request_id)
387{
388    apr_bucket_brigade *ib, *ob;
389    int seen_end_of_headers = 0, done = 0;
390    apr_status_t rv = APR_SUCCESS;
391    int script_error_status = HTTP_OK;
392    conn_rec *c = r->connection;
393    struct iovec vec[2];
394    ap_fcgi_header header;
395    unsigned char farray[AP_FCGI_HEADER_LEN];
396    apr_pollfd_t pfd;
397    int header_state = HDR_STATE_READING_HEADERS;
398
399    pfd.desc_type = APR_POLL_SOCKET;
400    pfd.desc.s = conn->sock;
401    pfd.p = r->pool;
402    pfd.reqevents = APR_POLLIN | APR_POLLOUT;
403
404    ib = apr_brigade_create(r->pool, c->bucket_alloc);
405    ob = apr_brigade_create(r->pool, c->bucket_alloc);
406
407    while (! done) {
408        apr_interval_time_t timeout;
409        apr_size_t len;
410        int n;
411
412        /* We need SOME kind of timeout here, or virtually anything will
413         * cause timeout errors. */
414        apr_socket_timeout_get(conn->sock, &timeout);
415
416        rv = apr_poll(&pfd, 1, &n, timeout);
417        if (rv != APR_SUCCESS) {
418            if (APR_STATUS_IS_EINTR(rv)) {
419                continue;
420            }
421            break;
422        }
423
424        if (pfd.rtnevents & APR_POLLOUT) {
425            char writebuf[AP_IOBUFSIZE];
426            apr_size_t writebuflen;
427            int last_stdin = 0;
428            int nvec = 0;
429
430            rv = ap_get_brigade(r->input_filters, ib,
431                                AP_MODE_READBYTES, APR_BLOCK_READ,
432                                sizeof(writebuf));
433            if (rv != APR_SUCCESS) {
434                break;
435            }
436
437            if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(ib))) {
438                last_stdin = 1;
439            }
440
441            writebuflen = sizeof(writebuf);
442
443            rv = apr_brigade_flatten(ib, writebuf, &writebuflen);
444
445            apr_brigade_cleanup(ib);
446
447            if (rv != APR_SUCCESS) {
448                break;
449            }
450
451            ap_fcgi_fill_in_header(&header, AP_FCGI_STDIN, request_id,
452                                   (apr_uint16_t) writebuflen, 0);
453            ap_fcgi_header_to_array(&header, farray);
454
455            vec[nvec].iov_base = (void *)farray;
456            vec[nvec].iov_len = sizeof(farray);
457            ++nvec;
458            if (writebuflen) {
459                vec[nvec].iov_base = writebuf;
460                vec[nvec].iov_len = writebuflen;
461                ++nvec;
462            }
463
464            rv = send_data(conn, vec, nvec, &len, 0);
465            if (rv != APR_SUCCESS) {
466                break;
467            }
468
469            if (last_stdin) {
470                pfd.reqevents = APR_POLLIN; /* Done with input data */
471
472                if (writebuflen) { /* empty AP_FCGI_STDIN not already sent? */
473                    ap_fcgi_fill_in_header(&header, AP_FCGI_STDIN, request_id,
474                                           0, 0);
475                    ap_fcgi_header_to_array(&header, farray);
476
477                    vec[0].iov_base = (void *)farray;
478                    vec[0].iov_len = sizeof(farray);
479
480                    rv = send_data(conn, vec, 1, &len, 1);
481                }
482            }
483        }
484
485        if (pfd.rtnevents & APR_POLLIN) {
486            /* readbuf has one byte on the end that is always 0, so it's
487             * able to work with a strstr when we search for the end of
488             * the headers, even if we fill the entire length in the recv. */
489            char readbuf[AP_IOBUFSIZE + 1];
490            apr_size_t readbuflen;
491            apr_uint16_t clen, rid;
492            apr_bucket *b;
493            unsigned char plen;
494            unsigned char type, version;
495
496            memset(readbuf, 0, sizeof(readbuf));
497            memset(farray, 0, sizeof(farray));
498
499            /* First, we grab the header... */
500            rv = get_data_full(conn, (char *) farray, AP_FCGI_HEADER_LEN);
501            if (rv != APR_SUCCESS) {
502                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01067)
503                              "Failed to read FastCGI header");
504                break;
505            }
506
507#ifdef FCGI_DUMP_HEADERS
508            ap_log_rdata(APLOG_MARK, APLOG_DEBUG, r, "FastCGI header",
509                         farray, AP_FCGI_HEADER_LEN, 0);
510#endif
511
512            ap_fcgi_header_fields_from_array(&version, &type, &rid,
513                                             &clen, &plen, farray);
514
515            if (version != AP_FCGI_VERSION_1) {
516                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01068)
517                              "Got bogus version %d", (int)version);
518                rv = APR_EINVAL;
519                break;
520            }
521
522            if (rid != request_id) {
523                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01069)
524                              "Got bogus rid %d, expected %d",
525                              rid, request_id);
526                rv = APR_EINVAL;
527                break;
528            }
529
530recv_again:
531            if (clen > sizeof(readbuf) - 1) {
532                readbuflen = sizeof(readbuf) - 1;
533            } else {
534                readbuflen = clen;
535            }
536
537            /* Now get the actual data.  Yes it sucks to do this in a second
538             * recv call, this will eventually change when we move to real
539             * nonblocking recv calls. */
540            if (readbuflen != 0) {
541                rv = get_data(conn, readbuf, &readbuflen);
542                if (rv != APR_SUCCESS) {
543                    break;
544                }
545                readbuf[readbuflen] = 0;
546            }
547
548            switch (type) {
549            case AP_FCGI_STDOUT:
550                if (clen != 0) {
551                    b = apr_bucket_transient_create(readbuf,
552                                                    readbuflen,
553                                                    c->bucket_alloc);
554
555                    APR_BRIGADE_INSERT_TAIL(ob, b);
556
557                    if (! seen_end_of_headers) {
558                        int st = handle_headers(r, &header_state, readbuf);
559
560                        if (st == 1) {
561                            int status;
562                            seen_end_of_headers = 1;
563
564                            status = ap_scan_script_header_err_brigade_ex(r, ob,
565                                NULL, APLOG_MODULE_INDEX);
566                            /* suck in all the rest */
567                            if (status != OK) {
568                                apr_bucket *tmp_b;
569                                apr_brigade_cleanup(ob);
570                                tmp_b = apr_bucket_eos_create(c->bucket_alloc);
571                                APR_BRIGADE_INSERT_TAIL(ob, tmp_b);
572                                r->status = status;
573                                ap_pass_brigade(r->output_filters, ob);
574                                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01070)
575                                              "Error parsing script headers");
576                                rv = APR_EINVAL;
577                                break;
578                            }
579
580                            if (conf->error_override &&
581                                ap_is_HTTP_ERROR(r->status)) {
582                                /*
583                                 * set script_error_status to discard
584                                 * everything after the headers
585                                 */
586                                script_error_status = r->status;
587                                /*
588                                 * prevent ap_die() from treating this as a
589                                 * recursive error, initially:
590                                 */
591                                r->status = HTTP_OK;
592                            }
593
594                            if (script_error_status == HTTP_OK) {
595                                rv = ap_pass_brigade(r->output_filters, ob);
596                                if (rv != APR_SUCCESS) {
597                                    break;
598                                }
599                            }
600                            apr_brigade_cleanup(ob);
601
602                            apr_pool_clear(setaside_pool);
603                        }
604                        else {
605                            /* We're still looking for the end of the
606                             * headers, so this part of the data will need
607                             * to persist. */
608                            apr_bucket_setaside(b, setaside_pool);
609                        }
610                    } else {
611                        /* we've already passed along the headers, so now pass
612                         * through the content.  we could simply continue to
613                         * setaside the content and not pass until we see the
614                         * 0 content-length (below, where we append the EOS),
615                         * but that could be a huge amount of data; so we pass
616                         * along smaller chunks
617                         */
618                        if (script_error_status == HTTP_OK) {
619                            rv = ap_pass_brigade(r->output_filters, ob);
620                            if (rv != APR_SUCCESS) {
621                                break;
622                            }
623                        }
624                        apr_brigade_cleanup(ob);
625                    }
626
627                    /* If we didn't read all the data go back and get the
628                     * rest of it. */
629                    if (clen > readbuflen) {
630                        clen -= readbuflen;
631                        goto recv_again;
632                    }
633                } else {
634                    /* XXX what if we haven't seen end of the headers yet? */
635
636                    if (script_error_status == HTTP_OK) {
637                        b = apr_bucket_eos_create(c->bucket_alloc);
638                        APR_BRIGADE_INSERT_TAIL(ob, b);
639                        rv = ap_pass_brigade(r->output_filters, ob);
640                        if (rv != APR_SUCCESS) {
641                            break;
642                        }
643                    }
644
645                    /* XXX Why don't we cleanup here?  (logic from AJP) */
646                }
647                break;
648
649            case AP_FCGI_STDERR:
650                /* TODO: Should probably clean up this logging a bit... */
651                if (clen) {
652                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01071)
653                                  "Got error '%s'", readbuf);
654                }
655
656                if (clen > readbuflen) {
657                    clen -= readbuflen;
658                    goto recv_again;
659                }
660                break;
661
662            case AP_FCGI_END_REQUEST:
663                done = 1;
664                break;
665
666            default:
667                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01072)
668                              "Got bogus record %d", type);
669                break;
670            }
671
672            if (plen) {
673                rv = get_data_full(conn, readbuf, plen);
674                if (rv != APR_SUCCESS) {
675                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
676                                  APLOGNO(02537) "Error occurred reading padding");
677                    break;
678                }
679            }
680        }
681    }
682
683    apr_brigade_destroy(ib);
684    apr_brigade_destroy(ob);
685
686    if (script_error_status != HTTP_OK) {
687        ap_die(script_error_status, r); /* send ErrorDocument */
688    }
689
690    return rv;
691}
692
693/*
694 * process the request and write the response.
695 */
696static int fcgi_do_request(apr_pool_t *p, request_rec *r,
697                           proxy_conn_rec *conn,
698                           conn_rec *origin,
699                           proxy_dir_conf *conf,
700                           apr_uri_t *uri,
701                           char *url, char *server_portstr)
702{
703    /* Request IDs are arbitrary numbers that we assign to a
704     * single request. This would allow multiplex/pipelinig of
705     * multiple requests to the same FastCGI connection, but
706     * we don't support that, and always use a value of '1' to
707     * keep things simple. */
708    apr_uint16_t request_id = 1;
709    apr_status_t rv;
710    apr_pool_t *temp_pool;
711
712    /* Step 1: Send AP_FCGI_BEGIN_REQUEST */
713    rv = send_begin_request(conn, request_id);
714    if (rv != APR_SUCCESS) {
715        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01073)
716                      "Failed Writing Request to %s:", server_portstr);
717        conn->close = 1;
718        return HTTP_SERVICE_UNAVAILABLE;
719    }
720
721    apr_pool_create(&temp_pool, r->pool);
722
723    /* Step 2: Send Environment via FCGI_PARAMS */
724    rv = send_environment(conn, r, temp_pool, request_id);
725    if (rv != APR_SUCCESS) {
726        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01074)
727                      "Failed writing Environment to %s:", server_portstr);
728        conn->close = 1;
729        return HTTP_SERVICE_UNAVAILABLE;
730    }
731
732    /* Step 3: Read records from the back end server and handle them. */
733    rv = dispatch(conn, conf, r, temp_pool, request_id);
734    if (rv != APR_SUCCESS) {
735        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01075)
736                      "Error dispatching request to %s:", server_portstr);
737        conn->close = 1;
738        return HTTP_SERVICE_UNAVAILABLE;
739    }
740
741    return OK;
742}
743
744#define FCGI_SCHEME "FCGI"
745
746/*
747 * This handles fcgi:(dest) URLs
748 */
749static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker,
750                              proxy_server_conf *conf,
751                              char *url, const char *proxyname,
752                              apr_port_t proxyport)
753{
754    int status;
755    char server_portstr[32];
756    conn_rec *origin = NULL;
757    proxy_conn_rec *backend;
758
759    proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
760                                                 &proxy_module);
761
762    apr_pool_t *p = r->pool;
763
764    apr_uri_t *uri = apr_palloc(r->pool, sizeof(*uri));
765
766    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01076)
767                  "url: %s proxyname: %s proxyport: %d",
768                 url, proxyname, proxyport);
769
770    if (strncasecmp(url, "fcgi:", 5) != 0) {
771        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01077) "declining URL %s", url);
772        return DECLINED;
773    }
774
775    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01078) "serving URL %s", url);
776
777    /* Create space for state information */
778    status = ap_proxy_acquire_connection(FCGI_SCHEME, &backend, worker,
779                                         r->server);
780    if (status != OK) {
781        if (backend) {
782            backend->close = 1;
783            ap_proxy_release_connection(FCGI_SCHEME, backend, r->server);
784        }
785        return status;
786    }
787
788    backend->is_ssl = 0;
789
790    /* Step One: Determine Who To Connect To */
791    status = ap_proxy_determine_connection(p, r, conf, worker, backend,
792                                           uri, &url, proxyname, proxyport,
793                                           server_portstr,
794                                           sizeof(server_portstr));
795    if (status != OK) {
796        goto cleanup;
797    }
798
799    /* XXX Setting close to 0 is a great way to end up with
800     *     timeouts at this point, since we lack good ways to manage the
801     *     back end fastcgi processes.  This should be revisited when we
802     *     have a better story on that part of things. */
803    backend->close = 1;
804
805    /* Step Two: Make the Connection */
806    if (ap_proxy_connect_backend(FCGI_SCHEME, backend, worker, r->server)) {
807        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01079)
808                      "failed to make connection to backend: %s",
809                      backend->hostname);
810        status = HTTP_SERVICE_UNAVAILABLE;
811        goto cleanup;
812    }
813
814    /* Step Three: Process the Request */
815    status = fcgi_do_request(p, r, backend, origin, dconf, uri, url,
816                             server_portstr);
817
818cleanup:
819    ap_proxy_release_connection(FCGI_SCHEME, backend, r->server);
820    return status;
821}
822
823static void register_hooks(apr_pool_t *p)
824{
825    proxy_hook_scheme_handler(proxy_fcgi_handler, NULL, NULL, APR_HOOK_FIRST);
826    proxy_hook_canon_handler(proxy_fcgi_canon, NULL, NULL, APR_HOOK_FIRST);
827}
828
829AP_DECLARE_MODULE(proxy_fcgi) = {
830    STANDARD20_MODULE_STUFF,
831    NULL,                       /* create per-directory config structure */
832    NULL,                       /* merge per-directory config structures */
833    NULL,                       /* create per-server config structure */
834    NULL,                       /* merge per-server config structures */
835    NULL,                       /* command apr_table_t */
836    register_hooks              /* register hooks */
837};
838