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 "ap_config.h" 18#include "ap_mmn.h" 19#include "httpd.h" 20#include "http_config.h" 21#include "http_connection.h" 22#include "http_core.h" 23#include "http_log.h" 24 25#include "apr_buckets.h" 26#include "apr_strings.h" 27#include "util_filter.h" 28#include "scoreboard.h" 29 30module AP_MODULE_DECLARE_DATA echo_module; 31 32typedef struct { 33 int bEnabled; 34} EchoConfig; 35 36static void *create_echo_server_config(apr_pool_t *p, server_rec *s) 37{ 38 EchoConfig *pConfig = apr_pcalloc(p, sizeof *pConfig); 39 40 pConfig->bEnabled = 0; 41 42 return pConfig; 43} 44 45static const char *echo_on(cmd_parms *cmd, void *dummy, int arg) 46{ 47 EchoConfig *pConfig = ap_get_module_config(cmd->server->module_config, 48 &echo_module); 49 pConfig->bEnabled = arg; 50 51 return NULL; 52} 53 54static apr_status_t brigade_peek(apr_bucket_brigade *bbIn, 55 char *buff, apr_size_t bufflen) 56{ 57 apr_bucket *b; 58 apr_size_t readbytes = 0; 59 60 if (bufflen--) 61 /* compensate for NULL */ 62 *buff = '\0'; 63 else 64 return APR_EGENERAL; 65 66 if (APR_BRIGADE_EMPTY(bbIn)) 67 return APR_EGENERAL; 68 69 b = APR_BRIGADE_FIRST(bbIn); 70 71 while ((b != APR_BRIGADE_SENTINEL(bbIn)) && (readbytes < bufflen)) { 72 const char *pos; 73 const char *str; 74 apr_size_t len; 75 apr_status_t rv; 76 77 if ((rv = apr_bucket_read(b, &str, &len, APR_NONBLOCK_READ)) 78 != APR_SUCCESS) 79 return rv; 80 81 if ((pos = memchr(str, APR_ASCII_LF, len)) != NULL) 82 len = pos - str; 83 if (len > bufflen - readbytes) 84 len = bufflen - readbytes; 85 memcpy (buff + readbytes, str, len); 86 readbytes += len; 87 buff[readbytes] = '\0'; 88 89 b = APR_BUCKET_NEXT(b); 90 } 91 return APR_SUCCESS; 92} 93 94 95static int update_echo_child_status(ap_sb_handle_t *sbh, 96 int status, conn_rec *c, 97 apr_bucket_brigade *last_echoed) 98{ 99 worker_score *ws = ap_get_scoreboard_worker(sbh); 100 int old_status = ws->status; 101 102 ws->status = status; 103 104 if (!ap_extended_status) 105 return old_status; 106 107 ws->last_used = apr_time_now(); 108 109 /* initial pass only, please - in the name of efficiency */ 110 if (c) { 111 apr_cpystrn(ws->client, 112 ap_get_remote_host(c, c->base_server->lookup_defaults, 113 REMOTE_NOLOOKUP, NULL), 114 sizeof(ws->client)); 115 apr_cpystrn(ws->vhost, c->base_server->server_hostname, 116 sizeof(ws->vhost)); 117 /* Deliberate trailing space - filling in string on WRITE passes */ 118 apr_cpystrn(ws->request, "ECHO ", sizeof(ws->request)); 119 } 120 121 /* each subsequent WRITE pass, let's update what we echoed */ 122 if (last_echoed) { 123 brigade_peek(last_echoed, ws->request + sizeof("ECHO ") - 1, 124 sizeof(ws->request) - sizeof("ECHO ") + 1); 125 } 126 127 return old_status; 128} 129 130static int process_echo_connection(conn_rec *c) 131{ 132 apr_bucket_brigade *bb; 133 apr_bucket *b; 134 apr_socket_t *csd = NULL; 135 EchoConfig *pConfig = ap_get_module_config(c->base_server->module_config, 136 &echo_module); 137 138 if (!pConfig->bEnabled) { 139 return DECLINED; 140 } 141 142 ap_time_process_request(c->sbh, START_PREQUEST); 143 update_echo_child_status(c->sbh, SERVER_BUSY_READ, c, NULL); 144 145 bb = apr_brigade_create(c->pool, c->bucket_alloc); 146 147 for ( ; ; ) { 148 apr_status_t rv; 149 150 /* Get a single line of input from the client */ 151 if (((rv = ap_get_brigade(c->input_filters, bb, AP_MODE_GETLINE, 152 APR_BLOCK_READ, 0)) != APR_SUCCESS)) { 153 apr_brigade_cleanup(bb); 154 if (!APR_STATUS_IS_EOF(rv) && ! APR_STATUS_IS_TIMEUP(rv)) 155 ap_log_error(APLOG_MARK, APLOG_INFO, rv, c->base_server, APLOGNO(01611) 156 "ProtocolEcho: Failure reading from %s", 157 c->client_ip); 158 break; 159 } 160 161 /* Something horribly wrong happened. Someone didn't block! */ 162 if (APR_BRIGADE_EMPTY(bb)) { 163 apr_brigade_cleanup(bb); 164 ap_log_error(APLOG_MARK, APLOG_INFO, rv, c->base_server, APLOGNO(01612) 165 "ProtocolEcho: Error - read empty brigade from %s!", 166 c->client_ip); 167 break; 168 } 169 170 if (!csd) { 171 csd = ap_get_conn_socket(c); 172 apr_socket_timeout_set(csd, c->base_server->keep_alive_timeout); 173 } 174 175 update_echo_child_status(c->sbh, SERVER_BUSY_WRITE, NULL, bb); 176 177 /* Make sure the data is flushed to the client */ 178 b = apr_bucket_flush_create(c->bucket_alloc); 179 APR_BRIGADE_INSERT_TAIL(bb, b); 180 rv = ap_pass_brigade(c->output_filters, bb); 181 if (rv != APR_SUCCESS) { 182 ap_log_error(APLOG_MARK, APLOG_INFO, rv, c->base_server, APLOGNO(01613) 183 "ProtocolEcho: Failure writing to %s", 184 c->client_ip); 185 break; 186 } 187 apr_brigade_cleanup(bb); 188 189 /* Announce our intent to loop */ 190 update_echo_child_status(c->sbh, SERVER_BUSY_KEEPALIVE, NULL, NULL); 191 } 192 apr_brigade_destroy(bb); 193 ap_time_process_request(c->sbh, STOP_PREQUEST); 194 update_echo_child_status(c->sbh, SERVER_CLOSING, c, NULL); 195 return OK; 196} 197 198static const command_rec echo_cmds[] = 199{ 200 AP_INIT_FLAG("ProtocolEcho", echo_on, NULL, RSRC_CONF, 201 "Run an echo server on this host"), 202 { NULL } 203}; 204 205static void register_hooks(apr_pool_t *p) 206{ 207 ap_hook_process_connection(process_echo_connection, NULL, NULL, 208 APR_HOOK_MIDDLE); 209} 210 211AP_DECLARE_MODULE(echo) = { 212 STANDARD20_MODULE_STUFF, 213 NULL, /* create per-directory config structure */ 214 NULL, /* merge per-directory config structures */ 215 create_echo_server_config, /* create per-server config structure */ 216 NULL, /* merge per-server config structures */ 217 echo_cmds, /* command apr_table_t */ 218 register_hooks /* register hooks */ 219}; 220