| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "ap_config.h" |
| #include "ap_mmn.h" |
| #include "httpd.h" |
| #include "http_config.h" |
| #include "http_connection.h" |
| #include "http_core.h" |
| #include "http_log.h" |
| |
| #include "apr_buckets.h" |
| #include "apr_strings.h" |
| #include "util_filter.h" |
| #include "scoreboard.h" |
| |
| module AP_MODULE_DECLARE_DATA echo_module; |
| |
| typedef struct { |
| int bEnabled; |
| } EchoConfig; |
| |
| static void *create_echo_server_config(apr_pool_t *p, server_rec *s) |
| { |
| EchoConfig *pConfig = apr_pcalloc(p, sizeof *pConfig); |
| |
| pConfig->bEnabled = 0; |
| |
| return pConfig; |
| } |
| |
| static const char *echo_on(cmd_parms *cmd, void *dummy, int arg) |
| { |
| EchoConfig *pConfig = ap_get_module_config(cmd->server->module_config, |
| &echo_module); |
| pConfig->bEnabled = arg; |
| |
| return NULL; |
| } |
| |
| static apr_status_t brigade_peek(apr_bucket_brigade *bbIn, |
| char *buff, apr_size_t bufflen) |
| { |
| apr_bucket *b; |
| apr_size_t readbytes = 0; |
| |
| if (bufflen--) |
| /* compensate for NULL */ |
| *buff = '\0'; |
| else |
| return APR_EGENERAL; |
| |
| if (APR_BRIGADE_EMPTY(bbIn)) |
| return APR_EGENERAL; |
| |
| b = APR_BRIGADE_FIRST(bbIn); |
| |
| while ((b != APR_BRIGADE_SENTINEL(bbIn)) && (readbytes < bufflen)) { |
| const char *pos; |
| const char *str; |
| apr_size_t len; |
| apr_status_t rv; |
| |
| if ((rv = apr_bucket_read(b, &str, &len, APR_NONBLOCK_READ)) |
| != APR_SUCCESS) |
| return rv; |
| |
| if ((pos = memchr(str, APR_ASCII_LF, len)) != NULL) |
| len = pos - str; |
| if (len > bufflen - readbytes) |
| len = bufflen - readbytes; |
| memcpy (buff + readbytes, str, len); |
| readbytes += len; |
| buff[readbytes] = '\0'; |
| |
| b = APR_BUCKET_NEXT(b); |
| } |
| return APR_SUCCESS; |
| } |
| |
| |
| static int update_echo_child_status(ap_sb_handle_t *sbh, |
| int status, conn_rec *c, |
| apr_bucket_brigade *last_echoed) |
| { |
| worker_score *ws = ap_get_scoreboard_worker(sbh); |
| int old_status = ws->status; |
| |
| ws->status = status; |
| |
| if (!ap_extended_status) |
| return old_status; |
| |
| ws->last_used = apr_time_now(); |
| |
| /* initial pass only, please - in the name of efficiency */ |
| if (c) { |
| apr_cpystrn(ws->client64, |
| ap_get_remote_host(c, c->base_server->lookup_defaults, |
| REMOTE_NOLOOKUP, NULL), |
| sizeof(ws->client64)); |
| apr_cpystrn(ws->vhost, c->base_server->server_hostname, |
| sizeof(ws->vhost)); |
| /* Deliberate trailing space - filling in string on WRITE passes */ |
| apr_cpystrn(ws->request, "ECHO ", sizeof(ws->request)); |
| } |
| |
| /* each subsequent WRITE pass, let's update what we echoed */ |
| if (last_echoed) { |
| brigade_peek(last_echoed, ws->request + sizeof("ECHO ") - 1, |
| sizeof(ws->request) - sizeof("ECHO ") + 1); |
| } |
| |
| return old_status; |
| } |
| |
| static int process_echo_connection(conn_rec *c) |
| { |
| apr_bucket_brigade *bb; |
| apr_bucket *b; |
| apr_socket_t *csd = NULL; |
| EchoConfig *pConfig = ap_get_module_config(c->base_server->module_config, |
| &echo_module); |
| |
| if (!pConfig->bEnabled) { |
| return DECLINED; |
| } |
| |
| ap_time_process_request(c->sbh, START_PREQUEST); |
| update_echo_child_status(c->sbh, SERVER_BUSY_READ, c, NULL); |
| |
| bb = apr_brigade_create(c->pool, c->bucket_alloc); |
| |
| for ( ; ; ) { |
| apr_status_t rv; |
| |
| /* Get a single line of input from the client */ |
| if (((rv = ap_get_brigade(c->input_filters, bb, AP_MODE_GETLINE, |
| APR_BLOCK_READ, 0)) != APR_SUCCESS)) { |
| apr_brigade_cleanup(bb); |
| if (!APR_STATUS_IS_EOF(rv) && ! APR_STATUS_IS_TIMEUP(rv)) |
| ap_log_error(APLOG_MARK, APLOG_INFO, rv, c->base_server, APLOGNO(01611) |
| "ProtocolEcho: Failure reading from %s", |
| c->client_ip); |
| break; |
| } |
| |
| /* Something horribly wrong happened. Someone didn't block! */ |
| if (APR_BRIGADE_EMPTY(bb)) { |
| ap_log_error(APLOG_MARK, APLOG_INFO, rv, c->base_server, APLOGNO(01612) |
| "ProtocolEcho: Error - read empty brigade from %s!", |
| c->client_ip); |
| break; |
| } |
| |
| if (!csd) { |
| csd = ap_get_conn_socket(c); |
| apr_socket_timeout_set(csd, c->base_server->keep_alive_timeout); |
| } |
| |
| update_echo_child_status(c->sbh, SERVER_BUSY_WRITE, NULL, bb); |
| |
| /* Make sure the data is flushed to the client */ |
| b = apr_bucket_flush_create(c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, b); |
| rv = ap_pass_brigade(c->output_filters, bb); |
| if (rv != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_INFO, rv, c->base_server, APLOGNO(01613) |
| "ProtocolEcho: Failure writing to %s", |
| c->client_ip); |
| break; |
| } |
| apr_brigade_cleanup(bb); |
| |
| /* Announce our intent to loop */ |
| update_echo_child_status(c->sbh, SERVER_BUSY_KEEPALIVE, NULL, NULL); |
| } |
| apr_brigade_destroy(bb); |
| ap_time_process_request(c->sbh, STOP_PREQUEST); |
| update_echo_child_status(c->sbh, SERVER_CLOSING, c, NULL); |
| return OK; |
| } |
| |
| static const command_rec echo_cmds[] = |
| { |
| AP_INIT_FLAG("ProtocolEcho", echo_on, NULL, RSRC_CONF, |
| "Run an echo server on this host"), |
| { NULL } |
| }; |
| |
| static void register_hooks(apr_pool_t *p) |
| { |
| ap_hook_process_connection(process_echo_connection, NULL, NULL, |
| APR_HOOK_MIDDLE); |
| } |
| |
| AP_DECLARE_MODULE(echo) = { |
| STANDARD20_MODULE_STUFF, |
| NULL, /* create per-directory config structure */ |
| NULL, /* merge per-directory config structures */ |
| create_echo_server_config, /* create per-server config structure */ |
| NULL, /* merge per-server config structures */ |
| echo_cmds, /* command apr_table_t */ |
| register_hooks /* register hooks */ |
| }; |