#include #include #include #include #include #include "inet_ntop_cache.h" #include "mod_proxy_core.h" #include "mod_proxy_core_protocol.h" #include "buffer.h" #include "log.h" #include "array.h" #include "keyvalue.h" #include "ajp13.h" #define CORE_PLUGIN "mod_proxy_core" typedef struct { http_method_t http; ajp13_method_t ajp13; } http_method_map; static http_method_map http_methods[] = { { HTTP_METHOD_GET, AJP13_METHOD_GET }, { HTTP_METHOD_POST, AJP13_METHOD_POST }, { HTTP_METHOD_HEAD, AJP13_METHOD_HEAD }, { HTTP_METHOD_OPTIONS, AJP13_METHOD_OPTIONS }, { HTTP_METHOD_PROPFIND, AJP13_METHOD_PROPFIND }, { HTTP_METHOD_MKCOL, AJP13_METHOD_MKCOL }, { HTTP_METHOD_PUT, AJP13_METHOD_PUT }, { HTTP_METHOD_DELETE, AJP13_METHOD_DELETE }, { HTTP_METHOD_COPY, AJP13_METHOD_COPY }, { HTTP_METHOD_MOVE, AJP13_METHOD_MOVE }, { HTTP_METHOD_PROPPATCH, AJP13_METHOD_PROPPATCH }, { HTTP_METHOD_REPORT, AJP13_METHOD_REPORT }, { HTTP_METHOD_CHECKOUT, AJP13_METHOD_CHECKOUT }, { HTTP_METHOD_CHECKIN, AJP13_METHOD_CHECKIN }, { HTTP_METHOD_VERSION_CONTROL, AJP13_METHOD_VERSION_CONTROL }, { HTTP_METHOD_UNCHECKOUT, AJP13_METHOD_UNCHECKOUT }, { HTTP_METHOD_MKACTIVITY, AJP13_METHOD_MKACTIVITY }, { HTTP_METHOD_MERGE, AJP13_METHOD_MERGE }, { HTTP_METHOD_LOCK, AJP13_METHOD_LOCK }, { HTTP_METHOD_UNLOCK, AJP13_METHOD_UNLOCK }, { HTTP_METHOD_LABEL, AJP13_METHOD_LABEL }, { HTTP_METHOD_CONNECT, AJP13_METHOD_UNKNOWN }, { HTTP_METHOD_UNSET, AJP13_METHOD_UNKNOWN } }; static ajp13_method_t ajp13_convert_http_method(http_method_t http_method) { int i; /* try fast lookup. if http_methods[] is sorted, we will find it this way. */ if(http_method != HTTP_METHOD_UNSET) { if(http_methods[http_method].http == http_method) { return http_methods[http_method].ajp13; } } /* walk the list to find ajp13 method. */ for(i = 0; http_methods[i].http != HTTP_METHOD_UNSET; i++) { if(http_methods[i].http == http_method) return http_methods[i].http; } return AJP13_METHOD_UNKNOWN; } /* * request header keyvalue map. * * Note: The strings have to be uppercase */ static keyvalue request_headers[] = { { AJP13_REQ_ACCEPT, "ACCEPT" }, { AJP13_REQ_ACCEPT_CHARSET, "ACCEPT-CHARSET" }, { AJP13_REQ_ACCEPT_ENCODING, "ACCEPT-ENCODING" }, { AJP13_REQ_ACCEPT_LANGUAGE, "ACCEPT-LANGUAGE" }, { AJP13_REQ_AUTHORIZATION, "AUTHORIZATION" }, { AJP13_REQ_CONNECTION, "CONNECTION" }, { AJP13_REQ_CONTENT_TYPE, "CONTENT-TYPE" }, { AJP13_REQ_CONTENT_LENGTH, "CONTENT-LENGTH" }, { AJP13_REQ_COOKIE, "COOKIE" }, { AJP13_REQ_COOKIE2, "COOKIE2" }, { AJP13_REQ_HOST, "HOST" }, { AJP13_REQ_PRAGMA, "PRAGMA" }, { AJP13_REQ_REFERER, "REFERER" }, { AJP13_REQ_USER_AGENT, "USER-AGENT" }, { -1, NULL } }; /* * response header keyvalue map * * Note: The strings don't have to be all uppercase here */ static keyvalue response_headers[] = { { AJP13_RESP_CONTENT_TYPE, "Content-Type" }, { AJP13_RESP_CONTENT_LANGUAGE, "Content-Language" }, { AJP13_RESP_CONTENT_LENGTH, "Content-Length" }, { AJP13_RESP_DATE, "Date" }, { AJP13_RESP_LAST_MODIFIED, "Last-Modified" }, { AJP13_RESP_LOCATION, "Location" }, { AJP13_RESP_SET_COOKIE, "Set-Cookie" }, { AJP13_RESP_SET_COOKIE2, "Set-Cookie2" }, { AJP13_RESP_SERVLET_ENGINE, "Servlet-Engine" }, { AJP13_RESP_STATUS, "Status" }, { AJP13_RESP_WWW_AUTHENTICATE, "WWW-Authenticate" }, { -1, NULL } }; typedef struct { PLUGIN_DATA; proxy_protocol *protocol; } protocol_plugin_data; typedef struct { size_t len; off_t offset; int type; } ajp13_packet; typedef struct { unsigned char magicB0; unsigned char magicB1; unsigned char lengthB0; unsigned char lengthB1; unsigned char type; } AJP13_Header; /** * init the AJP13_header */ static int ajp13_header(char *ptr, int length) { AJP13_Header * header = (AJP13_Header *)ptr; header->magicB0 = (AJP13_SERVER_MAGIC >> 8) & 0xff; header->magicB1 = (AJP13_SERVER_MAGIC ) & 0xff; header->lengthB0 = (length >> 8) & 0xff; header->lengthB1 = (length ) & 0xff; return AJP13_HEADER_LEN; } /** * The ajp13 protocol decoder will use this struct for storing state variables * used in decoding the stream */ typedef struct { buffer *buf; /* holds raw header bytes or used to buffer STDERR */ off_t offset; /* parse offset into buffer. */ ajp13_packet packet; /* parsed info about current packet. */ size_t chunk_len; /* chunk length */ size_t requested_bytes; /* backend requested we send this many bytes of the request content. */ } ajp13_state_data; static ajp13_state_data *ajp13_state_data_init(void) { ajp13_state_data *data; data = calloc(1, sizeof(*data)); data->buf = buffer_init(); return data; } static void ajp13_state_data_free(ajp13_state_data *data) { buffer_free(data->buf); free(data); } static void ajp13_state_data_reset(ajp13_state_data *data) { buffer_reset(data->buf); data->packet.len = 0; data->packet.offset = 0; data->packet.type = 0; data->offset = 0; data->chunk_len = 0; } /** * encode ajp13 byte/boolean */ static int ajp13_encode_byte(buffer *buf, int val) { /* format : 2 bytes integer */ buffer_prepare_append(buf, 1); buf->ptr[buf->used++] = val; return 1; } /** * encode ajp13 integer (2 bytes) */ static int ajp13_encode_int(buffer *buf, int val) { /* format : 2 bytes integer */ buffer_prepare_append(buf, 2); buf->ptr[buf->used++] = (val >> 8) & 0xff; buf->ptr[buf->used++] = (val >> 0) & 0xff; return 2; } /** * encode ajp13 string. */ static int ajp13_encode_string(buffer *buf, const char *str, size_t str_len) { size_t len = 0; if (!str || str_len == 0) { return ajp13_encode_int(buf,0xFFFF); } /* format : 2 byte string length : string : null */ len = 2 + str_len + 1; buffer_prepare_append(buf, len); ajp13_encode_int(buf,str_len); /* include the NUL */ buffer_append_memory(buf, str, str_len + 1); return len; } /** * add a key-value pair to the ajp13-buffer */ #define MAX_KEY_LEN 16 static int ajp13_env_add(buffer *buf, const char *key, size_t key_len, const char *val, size_t val_len) { char uppercase_key[MAX_KEY_LEN]; size_t len = 0; int code = -1; size_t i; if (!key || !val) return -1; if(key_len < MAX_KEY_LEN) { /* convert key to uppercase */ for(i=0;i = 0) { len += ajp13_encode_int(buf, AJP13_COMMON_HEADER_CODE + code); } else { len += ajp13_encode_string(buf, key, key_len); } len += ajp13_encode_string(buf, val, val_len); return len; } /** * decode ajp13 integer (2 bytes) */ static int ajp13_decode_int(ajp13_state_data *data) { int val = 0; if ((data->buf->used - data->offset) <= 2) return -1; val = (unsigned char)data->buf->ptr[data->offset++]; val <<= 8; val |= (unsigned char)data->buf->ptr[data->offset++]; return val; } /** * decode ajp13 string. */ static int ajp13_decode_string(buffer *str, ajp13_state_data *data, int is_header) { size_t len = 0; const char *p = NULL; if (!str) { return len; } /* string length */ len = ajp13_decode_int(data); if ((ssize_t)len == -1) { ERROR("ajp13_decode_int() returned invalid len: %zu", len); return len; } #ifdef AJP13_DEBUG TRACE("ajp13_decode_string() string-len: %zu (is_header: %d, common-header: %zd)", len, is_header, (len & AJP13_COMMON_HEADER_CODE)); #endif /* if string is header, check for common header code. */ if (is_header && (len & AJP13_COMMON_HEADER_CODE)) { p = keyvalue_get_value(response_headers, len & ~AJP13_COMMON_HEADER_CODE); if (p) { len = strlen(p); } else { ERROR("ajp13_decode_string() can't resolve common-header: %zd", len & ~AJP13_COMMON_HEADER_CODE); return -1; } } /* copy string from buffer. */ if (p == NULL) { if ((data->buf->used - data->offset) <= (len + 1)) { ERROR("we have %jd bytes, but a partial-string wants %zu. no way", (intmax_t) (data->buf->used - data->offset), len); return -1; } p = data->buf->ptr + data->offset; data->offset += len + 1; } buffer_copy_string_len(str, p, len); return len; } /** * decode ajp13 response headers */ static int ajp13_decode_response_headers(http_resp *resp, ajp13_state_data *data) { buffer *key, *value; int key_len, value_len; int i,num; resp->protocol = HTTP_VERSION_UNSET; resp->status = ajp13_decode_int(data); if (resp->status == -1) { ERROR("parsing AJP13 response-status failed, got %d", resp->status); return -1; } if (ajp13_decode_string(resp->reason, data, 0) == -1) { ERROR("parsing AJP13 response-reason failed: %s", "..."); return -1; } #ifdef AJP13_DEBUG TRACE("ajp13: header-status: %d", resp->status); TRACE("ajp13: header-reason: %s", resp->reason->ptr); #endif /* leave if we have no headers to decode */ num = ajp13_decode_int(data); #ifdef AJP13_DEBUG TRACE("ajp13: header-count: %d", num); #endif if (0 == num) return 0; key = buffer_init(); value = buffer_init(); for(i = 0; i < num; i++) { key_len = ajp13_decode_string(key, data, 1); value_len = ajp13_decode_string(value, data, 1); #ifdef AJP13_DEBUG TRACE("ajp13: header[%d]: key-len: %d, value-len: %d", i, key_len, value_len); #endif if (key_len > 0 && value_len >= 0) { data_string *HDR; if (NULL == (HDR = (data_string *)array_get_unused_element(resp->headers, TYPE_STRING))) { HDR = data_response_init(); } buffer_copy_string_len(HDR->key, key->ptr, key_len); buffer_copy_string_len(HDR->value, value->ptr, value_len); array_insert_unique(resp->headers, (data_unset *)HDR); #ifdef AJP13_DEBUG TRACE("ajp13: header[%d]: %s = %s", i, key->ptr, value->ptr); #endif } else { ERROR("ajp13: response-headers skipped: key-len = %d, val-len = %d", key_len, value_len); } } buffer_free(key); buffer_free(value); return 0; } PROXY_CONNECTION_FUNC(proxy_ajp13_init) { UNUSED(srv); if(!proxy_con->protocol_data) { proxy_con->protocol_data = ajp13_state_data_init(); } return 1; } PROXY_CONNECTION_FUNC(proxy_ajp13_cleanup) { UNUSED(srv); if(proxy_con->protocol_data) { ajp13_state_data_free((ajp13_state_data *)proxy_con->protocol_data); proxy_con->protocol_data = NULL; } return 1; } static int proxy_ajp13_forward_request(server *srv, connection *con, proxy_session *sess, buffer *packet) { char buf[32]; const char *str; server_socket *srv_sock = con->srv_socket; #ifdef HAVE_IPV6 char b2[INET6_ADDRSTRLEN + 1]; #endif int len = 0,port = 0; size_t i; /* prefix_code */ len += ajp13_encode_byte(packet, AJP13_TYPE_FORWARD_REQUEST); /* http method */ len += ajp13_encode_byte(packet, ajp13_convert_http_method(con->request.http_method)); /* protocol */ str = get_http_version_name(con->request.http_version); len += ajp13_encode_string(packet, str, strlen(str)); /* request uri * * the docs where unspecific here, but it looks like tomcat wants to get * the uri without the query-string here */ len += ajp13_encode_string(packet, CONST_BUF_LEN(con->uri.path)); /* remote address */ str = inet_ntop_cache_get_ip(srv, &(con->dst_addr)); len += ajp13_encode_string(packet, str, strlen(str)); /* remote host */ len += ajp13_encode_string(packet, CONST_STR_LEN("")); /* server name */ if (con->server_name->used) { len += ajp13_encode_string(packet, CONST_BUF_LEN(con->server_name)); } else { #ifdef HAVE_IPV6 str = inet_ntop(srv_sock->addr.plain.sa_family, srv_sock->addr.plain.sa_family == AF_INET6 ? (const void *) &(srv_sock->addr.ipv6.sin6_addr) : (const void *) &(srv_sock->addr.ipv4.sin_addr), b2, sizeof(b2)-1); #else str = inet_ntoa(srv_sock->addr.ipv4.sin_addr); #endif len += ajp13_encode_string(packet, str, strlen(str)); } /* server port */ #ifdef HAVE_IPV6 port = ntohs(srv_sock->addr.plain.sa_family ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port); #else port = ntohs(srv_sock->addr.ipv4.sin_port); #endif len += ajp13_encode_int(packet, port); /* is_ssl */ #ifdef USE_OPENSSL len += ajp13_encode_byte(packet, srv_sock->is_ssl); #else len += ajp13_encode_byte(packet, 0); #endif /* make sure we have content-length header */ if(con->request.content_length > 0) { LI_ltostr(buf, con->request.content_length); array_set_key_value(sess->request_headers, CONST_STR_LEN("Content-Length"), buf, strlen(buf)); } else { array_set_key_value(sess->request_headers, CONST_STR_LEN("Content-Length"), CONST_STR_LEN("0")); } /* request headers count */ len += ajp13_encode_int(packet, sess->request_headers->used); /* request headers */ for (i = 0; i < sess->request_headers->used; i++) { data_string *ds; ds = (data_string *)sess->request_headers->data[i]; len += ajp13_env_add(packet, CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value)); } /* remote user */ if (!buffer_is_empty(con->authed_user)) { len += ajp13_encode_byte(packet, AJP13_ATTRIBUTE_REMOTE_USER); len += ajp13_encode_string(packet, CONST_BUF_LEN(con->authed_user)); } /* query string */ if (!buffer_is_empty(con->uri.query)) { len += ajp13_encode_byte(packet, AJP13_ATTRIBUTE_QUERY_STRING); len += ajp13_encode_string(packet, CONST_BUF_LEN(con->uri.query)); } /* jvm_route */ if (!buffer_is_empty(sess->proxy_con->address->name)) { len += ajp13_encode_byte(packet, AJP13_ATTRIBUTE_JVM_ROUTE); len += ajp13_encode_string(packet, CONST_BUF_LEN(sess->proxy_con->address->name)); } /* request terminator */ len += ajp13_encode_byte(packet, AJP13_ATTRIBUTE_ARE_DONE); return len; } PROXY_STREAM_ENCODER_FUNC(proxy_ajp13_encode_request_headers) { proxy_connection *proxy_con = sess->proxy_con; ajp13_state_data *data = (ajp13_state_data *)proxy_con->protocol_data; chunkqueue *out = proxy_con->send; connection *con = sess->remote_con; buffer *packet; size_t len; UNUSED(srv); UNUSED(in); packet = chunkqueue_get_append_buffer(out); buffer_prepare_copy(packet, 1024); /* reserve bytes for header. Will over right header when we know packet length. */ packet->used += AJP13_HEADER_LEN; /* send AJP13_TYPE_FORWARD_REQUEST */ len = proxy_ajp13_forward_request(srv, con, sess, packet); packet->used++; /* this is needed because the network will only write "used - 1" bytes */ out->bytes_in += packet->used - 1; /* rewrite packet header with correct length. */ ajp13_header(packet->ptr, len); if(con->request.content_length > AJP13_MAX_BODY_PACKET_SIZE) { data->requested_bytes = AJP13_MAX_BODY_PACKET_SIZE; } else if(con->request.content_length > 0) { data->requested_bytes = con->request.content_length; } else { data->requested_bytes = 0; } return HANDLER_FINISHED; } /* * copy len bytes from chunk-chain into buffer */ static int proxy_ajp13_fill_buffer(ajp13_state_data *data, chunkqueue *in, size_t len) { off_t we_have = 0, we_need = len; chunk *c; buffer_prepare_append(data->buf, we_need); for (c = in->first; c && we_need > 0; c = c->next) { if(c->mem->used == 0) continue; we_have = c->mem->used - c->offset - 1; if (we_have == 0) continue; if (we_have > we_need) we_have = we_need; buffer_append_string_len(data->buf, c->mem->ptr + c->offset, we_have); data->packet.offset += we_have; c->offset += we_have; in->bytes_out += we_have; we_need -= we_have; } return we_need; } PROXY_STREAM_DECODER_FUNC(proxy_ajp13_stream_decoder_internal) { proxy_connection *proxy_con = sess->proxy_con; ajp13_state_data *data = (ajp13_state_data *)proxy_con->protocol_data; chunkqueue *in = proxy_con->recv; AJP13_Header *header; size_t we_parsed = 0, we_need = 0; handler_t rc = HANDLER_GO_ON; int magic = 0; int reuse = 0; UNUSED(srv); /* no data ? */ if (!in->first) return HANDLER_WAIT_FOR_EVENT; /* parse the packet header. */ if(data->packet.offset < AJP13_FULL_HEADER_LEN) { we_need = (AJP13_FULL_HEADER_LEN - data->packet.offset); /* copy ajp13 header to buffer */ we_need = proxy_ajp13_fill_buffer(data, in, we_need); /* make sure we have the full ajp13 header. */ if(we_need > 0) { /* we need more data to parse the header. */ return HANDLER_WAIT_FOR_EVENT; } /* parse raw header. */ header = (AJP13_Header *)(data->buf->ptr); data->packet.len = ((header->lengthB0 << 8) | header->lengthB1); data->packet.len--; /* packet type byte already parsed. */ data->packet.type = header->type; magic = ((header->magicB0 << 8) | header->magicB1); if (magic != AJP13_CONTAINER_MAGIC) { ERROR("%s", "bad ajp13 magic code, invalid protocl stream"); return HANDLER_ERROR; } /* Finished parsing raw header bytes. */ buffer_reset(data->buf); } /* for most packet types copy the content into the data buffer */ if (data->packet.type != AJP13_TYPE_SEND_BODY_CHUNK) { we_need = data->packet.len - (data->packet.offset - AJP13_FULL_HEADER_LEN); if(we_need > 0) { /* copy ajp13 packet contents to buffer */ we_need = proxy_ajp13_fill_buffer(data, in, we_need); /* make sure we have the full ajp13 packet content. */ if(we_need > 0) { /* we need more data to parse the content. */ return HANDLER_WAIT_FOR_EVENT; } } } switch (data->packet.type) { case AJP13_TYPE_GET_BODY_CHUNK: data->requested_bytes = ajp13_decode_int(data); break; case AJP13_TYPE_SEND_HEADERS: if (ajp13_decode_response_headers(sess->resp, data) == -1) { ERROR("%s", "Error parsing response_headers"); rc = HANDLER_ERROR; } sess->have_response_headers = 1; break; case AJP13_TYPE_SEND_BODY_CHUNK: /* parse chunk length */ we_parsed = (data->packet.offset - AJP13_FULL_HEADER_LEN); if (we_parsed < 2) { we_need = 2 - we_parsed; /* copy chunk length bytes to buffer */ we_need = proxy_ajp13_fill_buffer(data, in, we_need); if(we_need > 0) { /* we need more data to parse the chunk length. */ return HANDLER_WAIT_FOR_EVENT; } /* parse chunk length */ data->chunk_len = ajp13_decode_int(data); } /* parse chunk data. */ we_parsed = (data->packet.offset - 2 - AJP13_FULL_HEADER_LEN); if(we_parsed < data->chunk_len) { we_need = data->chunk_len - we_parsed; /* copy chunk data */ we_parsed = chunkqueue_steal_chunks_len(out, in->first, we_need); data->packet.offset += we_parsed; we_need -= we_parsed; in->bytes_out += we_parsed; out->bytes_in += we_parsed; } we_need = data->packet.len - (data->packet.offset - AJP13_FULL_HEADER_LEN); /* ignore padding */ if(we_need > 0) { we_parsed = chunkqueue_skip(in, we_need); data->packet.offset += we_parsed; we_need -= we_parsed; in->bytes_out += we_parsed; } rc = HANDLER_GO_ON; break; case AJP13_TYPE_END_RESPONSE: if(data->buf->used >= 1) { reuse = data->buf->ptr[0]; } if(reuse != 1) { sess->is_closing = 1; } sess->is_request_finished = 1; /* close the queues. no more data to decode. */ in->is_closed = 1; out->is_closed = 1; /* make sure the send queue is closed, since we can't send any more request content. */ proxy_con->send->is_closed = 1; rc = HANDLER_FINISHED; break; default: TRACE("unknown packet.type: %d", data->packet.type); rc = HANDLER_ERROR; break; } if(we_need == 0) { /* packet finished, reset state for next packet */ ajp13_state_data_reset(data); } chunkqueue_remove_finished_chunks(in); return rc; } PROXY_STREAM_DECODER_FUNC(proxy_ajp13_stream_decoder) { proxy_connection *proxy_con = sess->proxy_con; chunkqueue *in = proxy_con->recv; int res; if(out->is_closed) return 1; /* decode the whole packet stream */ do { /* decode the packet */ res = proxy_ajp13_stream_decoder_internal(srv, sess, out); } while (in->first && res == HANDLER_GO_ON); if (res == HANDLER_WAIT_FOR_EVENT) { if (in->is_closed) return HANDLER_ERROR; return HANDLER_GO_ON; } return res; } /** * transform the content-stream into a valid FastCGI STDIN content-stream * * as we don't apply chunked-encoding here, pass it on AS IS */ PROXY_STREAM_ENCODER_FUNC(proxy_ajp13_stream_encoder) { proxy_connection *proxy_con = sess->proxy_con; ajp13_state_data *data = (ajp13_state_data *)proxy_con->protocol_data; chunkqueue *out = proxy_con->send; size_t we_need = 0, we_have = 0; buffer *b; UNUSED(srv); /* output queue closed, can't encode any more data. */ if(out->is_closed) return HANDLER_FINISHED; if (data->requested_bytes == 0) { /* backend needs to send a request for more content before we can encode more. */ return HANDLER_GO_ON; } /* calculate how many bytes we can encode. */ if (in->bytes_in > in->bytes_out) { we_need = in->bytes_in - in->bytes_out; if (we_need > AJP13_MAX_BODY_PACKET_SIZE) we_need = AJP13_MAX_BODY_PACKET_SIZE; if (we_need > data->requested_bytes) we_need = data->requested_bytes; data->requested_bytes = 0; } /* * write ajp13 header */ b = chunkqueue_get_append_buffer(out); buffer_prepare_copy(b, AJP13_HEADER_LEN); b->used += AJP13_HEADER_LEN; if (we_need > 0) { ajp13_header(b->ptr, we_need + 2); ajp13_encode_int(b, we_need); } else { /* empty body packet to mark EOF, if the backend requested data beyond * the end of the request content. */ ajp13_header(b->ptr, we_need); } out->bytes_in += b->used; b->used++; we_have = chunkqueue_steal_chunks_len(out, in->first, we_need); in->bytes_out += we_have; out->bytes_in += we_have; if (in->bytes_in == in->bytes_out && in->is_closed) { /* We are finished encoding the request content, * but we can't close the send queue here since the backend might * try to request more request content then is available and * we will have to response with a 0 length body packet. */ return HANDLER_FINISHED; } return HANDLER_GO_ON; } INIT_FUNC(mod_proxy_backend_ajp13_init) { mod_proxy_core_plugin_data *core_data; protocol_plugin_data *p; /* get the plugin_data of the core-plugin */ core_data = plugin_get_config(srv, CORE_PLUGIN); if(!core_data) return NULL; p = calloc(1, sizeof(*p)); /* define protocol handler callbacks */ p->protocol = core_data->proxy_register_protocol("ajp13"); p->protocol->proxy_stream_init = proxy_ajp13_init; p->protocol->proxy_stream_cleanup = proxy_ajp13_cleanup; p->protocol->proxy_stream_decoder = proxy_ajp13_stream_decoder; p->protocol->proxy_stream_encoder = proxy_ajp13_stream_encoder; p->protocol->proxy_encode_request_headers = proxy_ajp13_encode_request_headers; return p; } FREE_FUNC(mod_proxy_backend_ajp13_free) { protocol_plugin_data *p = p_d; UNUSED(srv); if (!p) return HANDLER_GO_ON; free(p); return HANDLER_GO_ON; } LI_EXPORT int mod_proxy_backend_ajp13_plugin_init(plugin *p); LI_EXPORT int mod_proxy_backend_ajp13_plugin_init(plugin *p) { data_string *ds; p->version = LIGHTTPD_VERSION_ID; p->name = buffer_init_string("mod_proxy_backend_ajp13"); p->init = mod_proxy_backend_ajp13_init; p->cleanup = mod_proxy_backend_ajp13_free; p->data = NULL; ds = data_string_init(); buffer_copy_string_len(ds->value, CONST_STR_LEN(CORE_PLUGIN)); array_insert_unique(p->required_plugins, (data_unset *)ds); return 0; }