| /* 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 */ | |
| }; |