| /* |
| * 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. |
| */ |
| |
| /*************************************************************************** |
| * Description: NSAPI plugin for Netscape servers * |
| * Author: Gal Shachor <shachor@il.ibm.com> * |
| * Version: $Revision$ * |
| ***************************************************************************/ |
| |
| #if defined(WIN32) |
| #ifndef _WIN32_WINNT |
| #define _WIN32_WINNT 0x0500 |
| #endif |
| #include <winsock2.h> |
| #endif |
| |
| #include "nsapi.h" |
| #include "jk_global.h" |
| #include "jk_url.h" |
| #include "jk_util.h" |
| #include "jk_map.h" |
| #include "jk_pool.h" |
| #include "jk_service.h" |
| #include "jk_worker.h" |
| #include "jk_shm.h" |
| #include "jk_ajp13.h" |
| |
| #define URI_PATTERN "path" |
| #define DEFAULT_WORKER_NAME ("ajp13") |
| #define REJECT_UNSAFE_TAG "reject_unsafe" |
| |
| #define STRNULL_FOR_NULL(x) ((x) ? (x) : "(null)") |
| |
| struct nsapi_private_data |
| { |
| jk_pool_t p; |
| |
| pblock *pb; |
| Session *sn; |
| Request *rq; |
| }; |
| typedef struct nsapi_private_data nsapi_private_data_t; |
| |
| static int init_on_other_thread_is_done = JK_FALSE; |
| static int init_on_other_thread_is_ok = JK_FALSE; |
| |
| static const char ssl_cert_start[] = "-----BEGIN CERTIFICATE-----\r\n"; |
| static const char ssl_cert_end[] = "\r\n-----END CERTIFICATE-----\r\n"; |
| |
| static jk_logger_t *logger = NULL; |
| static jk_worker_env_t worker_env; |
| static jk_map_t *init_map = NULL; |
| static jk_uri_worker_map_t *uw_map = NULL; |
| static int jk_shm_size = 0; |
| |
| static int JK_METHOD start_response(jk_ws_service_t *s, |
| int status, |
| const char *reason, |
| const char *const *header_names, |
| const char *const *header_values, |
| unsigned num_of_headers); |
| |
| static int JK_METHOD ws_read(jk_ws_service_t *s, |
| void *b, unsigned l, unsigned *a); |
| |
| static int JK_METHOD ws_write(jk_ws_service_t *s, const void *b, unsigned l); |
| |
| NSAPI_PUBLIC int jk_init(pblock * pb, Session * sn, Request * rq); |
| |
| NSAPI_PUBLIC void jk_term(void *p); |
| |
| NSAPI_PUBLIC int jk_service(pblock * pb, Session * sn, Request * rq); |
| |
| static int init_ws_service(nsapi_private_data_t * private_data, |
| jk_ws_service_t *s); |
| |
| static int setup_http_headers(nsapi_private_data_t * private_data, |
| jk_ws_service_t *s); |
| |
| static void init_workers_on_other_threads(void *init_d) |
| { |
| init_map = (jk_map_t *)init_d; |
| /* we add the URI->WORKER MAP since workers using AJP14 will feed it */ |
| /* but where are they here in Netscape ? */ |
| if (uri_worker_map_alloc(&uw_map, NULL, logger)) { |
| uw_map->fname = ""; |
| uw_map->reload = JK_URIMAP_DEF_RELOAD; |
| uw_map->reject_unsafe = jk_map_get_bool(init_map, "worker." REJECT_UNSAFE_TAG, JK_FALSE); |
| worker_env.uri_to_worker = uw_map; |
| worker_env.pool = NULL; |
| |
| if (wc_open(init_map, &worker_env, logger)) { |
| init_on_other_thread_is_ok = JK_TRUE; |
| uri_worker_map_ext(uw_map, logger); |
| uri_worker_map_switch(uw_map, logger); |
| } |
| else { |
| jk_log(logger, JK_LOG_EMERG, |
| "In init_workers_on_other_threads, failed"); |
| } |
| } |
| else { |
| jk_log(logger, JK_LOG_EMERG, |
| "In init_workers_on_other_threads, failed"); |
| } |
| |
| init_on_other_thread_is_done = JK_TRUE; |
| } |
| |
| /* |
| * Convert string to lower case. |
| * If string is longer than the provided buffer, |
| * just return the original string. |
| */ |
| static const char *to_lower(const char *str, char *buf, int bufsz) |
| { |
| const char *from = str; |
| char *to = buf; |
| char *end = buf + (bufsz - 1); |
| while (to != end && *from) { |
| *to = (char)tolower(*from); |
| to++; |
| from++; |
| } |
| if (to != end) { |
| *to = '\0'; |
| return buf; |
| } |
| return str; |
| } |
| |
| static int JK_METHOD start_response(jk_ws_service_t *s, |
| int status, |
| const char *reason, |
| const char *const *header_names, |
| const char *const *header_values, |
| unsigned num_of_headers) |
| { |
| if (s && s->ws_private) { |
| nsapi_private_data_t *p = s->ws_private; |
| if (!s->response_started) { |
| unsigned i; |
| |
| s->response_started = JK_TRUE; |
| |
| /* Remove "old" content type */ |
| param_free(pblock_remove("content-type", p->rq->srvhdrs)); |
| |
| if (num_of_headers) { |
| /* |
| * NSAPI expects http header names to be lower case. |
| * This is only relevant for the server itself or other |
| * plugins searching for headers in the pblock. |
| * We use a buffer of limited length, because conforming |
| * with this rule should only matter for well-known headers. |
| */ |
| char name_buf[64]; |
| for (i = 0; i < (int)num_of_headers; i++) { |
| pblock_nvinsert(to_lower(header_names[i], name_buf, 64), |
| header_values[i], p->rq->srvhdrs); |
| } |
| } |
| else { |
| pblock_nvinsert("content-type", "text/plain", p->rq->srvhdrs); |
| } |
| |
| protocol_status(p->sn, p->rq, status, (char *)reason); |
| |
| protocol_start_response(p->sn, p->rq); |
| } |
| return JK_TRUE; |
| |
| } |
| return JK_FALSE; |
| } |
| |
| static int JK_METHOD ws_read(jk_ws_service_t *s, |
| void *b, unsigned l, unsigned *a) |
| { |
| if (s && s->ws_private && b && a) { |
| nsapi_private_data_t *p = s->ws_private; |
| |
| *a = 0; |
| if (l) { |
| unsigned i; |
| netbuf *inbuf = p->sn->inbuf; |
| |
| i = netbuf_getbytes(inbuf, b, l); |
| if (NETBUF_EOF == i || NETBUF_ERROR == i) { |
| return JK_FALSE; |
| } |
| |
| *a = i; |
| |
| } |
| return JK_TRUE; |
| } |
| return JK_FALSE; |
| } |
| |
| static int JK_METHOD ws_write(jk_ws_service_t *s, const void *b, unsigned l) |
| { |
| if (s && s->ws_private && b) { |
| nsapi_private_data_t *p = s->ws_private; |
| |
| if (l) { |
| if (!s->response_started) { |
| start_response(s, 200, NULL, NULL, NULL, 0); |
| } |
| |
| if (net_write(p->sn->csd, (char *)b, (int)l) == IO_ERROR) { |
| return JK_FALSE; |
| } |
| } |
| |
| return JK_TRUE; |
| |
| } |
| return JK_FALSE; |
| } |
| |
| NSAPI_PUBLIC int jk_init(pblock * pb, Session * sn, Request * rq) |
| { |
| char *worker_prp_file = pblock_findval(JK_WORKER_FILE_TAG, pb); |
| char *log_level_str = pblock_findval(JK_LOG_LEVEL_TAG, pb); |
| char *log_file = pblock_findval(JK_LOG_FILE_TAG, pb); |
| char *shm_file = pblock_findval(JK_SHM_FILE_TAG, pb); |
| char *shm_file_safe = ""; |
| char *reject_unsafe = pblock_findval(REJECT_UNSAFE_TAG, pb); |
| |
| int rc = REQ_ABORTED; |
| |
| if (!worker_prp_file) { |
| worker_prp_file = JK_WORKER_FILE_DEF; |
| } |
| |
| if (!log_level_str) { |
| log_level_str = JK_LOG_DEF_VERB; |
| } |
| |
| if (!log_file) { |
| fprintf(stderr, |
| "Missing attribute %s in magnus.conf (jk_init) - aborting!\n", JK_LOG_FILE_TAG); |
| return rc; |
| } |
| |
| if (shm_file) { |
| shm_file_safe = shm_file; |
| } |
| #if !defined(WIN32) |
| else { |
| fprintf(stderr, |
| "Missing attribute %s in magnus.conf (jk_init) - aborting!\n", JK_SHM_FILE_TAG); |
| return rc; |
| } |
| #endif |
| |
| fprintf(stderr, |
| "In jk_init.\n Worker file = %s.\n Log level = %s.\n Log File = %s\n SHM File = %s\n", |
| worker_prp_file, log_level_str, log_file, shm_file); |
| |
| if (!jk_open_file_logger(&logger, log_file, |
| jk_parse_log_level(log_level_str))) { |
| logger = NULL; |
| } |
| |
| if (jk_map_alloc(&init_map)) { |
| if (jk_map_read_properties(init_map, NULL, worker_prp_file, NULL, |
| JK_MAP_HANDLE_DUPLICATES, logger)) { |
| int rv; |
| int sleep_cnt; |
| SYS_THREAD s; |
| |
| if (jk_map_resolve_references(init_map, "worker.", 1, 1, logger) == JK_FALSE) { |
| jk_log(logger, JK_LOG_ERROR, "Error in resolving configuration references"); |
| } |
| |
| if (reject_unsafe) { |
| jk_map_add(init_map, "worker." REJECT_UNSAFE_TAG, reject_unsafe); |
| } |
| |
| jk_shm_size = jk_shm_calculate_size(init_map, logger); |
| if ((rv = jk_shm_open(shm_file, jk_shm_size, logger)) != 0) |
| jk_log(logger, JK_LOG_ERROR, |
| "Initializing shm:%s errno=%d. Load balancing workers will not function properly.", |
| jk_shm_name(), rv); |
| |
| s = systhread_start(SYSTHREAD_DEFAULT_PRIORITY, |
| 0, init_workers_on_other_threads, init_map); |
| for (sleep_cnt = 0; sleep_cnt < 60; sleep_cnt++) { |
| systhread_sleep(1000); |
| jk_log(logger, JK_LOG_DEBUG, "jk_init, a second passed"); |
| if (init_on_other_thread_is_done) { |
| break; |
| } |
| } |
| |
| if (init_on_other_thread_is_done && init_on_other_thread_is_ok) { |
| magnus_atrestart(jk_term, NULL); |
| rc = REQ_PROCEED; |
| jk_log(logger, JK_LOG_INFO, "%s initialized", JK_FULL_EXPOSED_VERSION); |
| } |
| |
| /* if(wc_open(init_map, NULL, logger)) { |
| magnus_atrestart(jk_term, NULL); |
| rc = REQ_PROCEED; |
| } |
| */ |
| } |
| } |
| |
| return rc; |
| } |
| |
| NSAPI_PUBLIC void jk_term(void *p) |
| { |
| if (uw_map) { |
| uri_worker_map_free(&uw_map, logger); |
| } |
| |
| if (init_map) { |
| jk_map_free(&init_map); |
| } |
| |
| wc_close(logger); |
| jk_shm_close(logger); |
| if (logger) { |
| jk_close_file_logger(&logger); |
| } |
| } |
| |
| NSAPI_PUBLIC int jk_service(pblock * pb, Session * sn, Request * rq) |
| { |
| char *worker_name = pblock_findval(JK_WORKER_NAME_TAG, pb); |
| char *uri_pattern = pblock_findval(URI_PATTERN, pb); |
| jk_worker_t *worker; |
| int rc = REQ_ABORTED; |
| |
| if (uri_pattern) { |
| char *uri = pblock_findval("uri", rq->reqpb); |
| |
| if (0 != shexp_match(uri, uri_pattern)) { |
| return REQ_NOACTION; |
| } |
| } |
| |
| if (!worker_name) { |
| worker_name = DEFAULT_WORKER_NAME; |
| } |
| |
| worker = wc_get_worker_for_name(worker_name, logger); |
| if (worker) { |
| nsapi_private_data_t private_data; |
| jk_ws_service_t s; |
| jk_pool_atom_t buf[SMALL_POOL_SIZE]; |
| |
| jk_open_pool(&private_data.p, buf, sizeof(buf)); |
| |
| private_data.pb = pb; |
| private_data.sn = sn; |
| private_data.rq = rq; |
| |
| jk_init_ws_service(&s); |
| |
| s.ws_private = &private_data; |
| s.pool = &private_data.p; |
| |
| wc_maintain(logger); |
| if (init_ws_service(&private_data, &s)) { |
| jk_endpoint_t *e = NULL; |
| if (worker->get_endpoint(worker, &e, logger)) { |
| int is_error = JK_HTTP_SERVER_ERROR; |
| int result; |
| if ((result = e->service(e, &s, logger, &is_error)) > 0) { |
| rc = REQ_PROCEED; |
| if (JK_IS_DEBUG_LEVEL(logger)) |
| jk_log(logger, JK_LOG_DEBUG, |
| "service() returned OK"); |
| } |
| else { |
| protocol_status(sn, rq, is_error, NULL); |
| if ((result == JK_CLIENT_ERROR) && (is_error == JK_HTTP_OK)) { |
| rc = REQ_EXIT; |
| jk_log(logger, JK_LOG_INFO, |
| "service() failed because client aborted connection"); |
| } |
| else { |
| rc = REQ_ABORTED; |
| jk_log(logger, JK_LOG_ERROR, |
| "service() failed with http error %d", is_error); |
| } |
| } |
| |
| e->done(&e, logger); |
| } |
| } |
| jk_close_pool(&private_data.p); |
| } |
| |
| return rc; |
| } |
| |
| static int init_ws_service(nsapi_private_data_t * private_data, |
| jk_ws_service_t *s) |
| { |
| char *tmp; |
| int size; |
| int rc; |
| |
| s->start_response = start_response; |
| s->read = ws_read; |
| s->write = ws_write; |
| |
| s->auth_type = pblock_findval("auth-type", private_data->rq->vars); |
| s->remote_user = pblock_findval("auth-user", private_data->rq->vars); |
| |
| tmp = NULL; |
| rc = request_header("content-length", |
| &tmp, private_data->sn, private_data->rq); |
| |
| if ((rc != REQ_ABORTED) && tmp) { |
| sscanf(tmp, "%" JK_UINT64_T_FMT, &(s->content_length)); |
| } |
| |
| s->method = pblock_findval("method", private_data->rq->reqpb); |
| s->protocol = pblock_findval("protocol", private_data->rq->reqpb); |
| |
| s->remote_host = session_dns(private_data->sn); |
| s->remote_addr = pblock_findval("ip", private_data->sn->client); |
| /* Remote port is not available from NSAPI. */ |
| s->remote_port = "0"; |
| |
| tmp = pblock_findval("uri", private_data->rq->reqpb); |
| size = 3 * strlen(tmp) + 1; |
| s->req_uri = jk_pool_alloc(s->pool, size); |
| jk_canonenc(tmp, s->req_uri, size); |
| |
| s->query_string = pblock_findval("query", private_data->rq->reqpb); |
| |
| /* Local address is not available from NSAPI. */ |
| s->local_addr = server_hostname; |
| s->server_name = server_hostname; |
| |
| s->server_port = server_portnum; |
| s->server_software = system_version(); |
| |
| s->uw_map = uw_map; |
| |
| s->is_ssl = security_active; |
| |
| if (s->is_ssl) { |
| char *ssl_cert = pblock_findval("auth-cert", private_data->rq->vars); |
| if (ssl_cert != NULL) { |
| s->ssl_cert = jk_pool_alloc(s->pool, sizeof(ssl_cert_start)+ |
| strlen(ssl_cert)+ |
| sizeof(ssl_cert_end)); |
| strcpy(s->ssl_cert, ssl_cert_start); |
| strcat(s->ssl_cert, ssl_cert); |
| strcat(s->ssl_cert, ssl_cert_end); |
| s->ssl_cert_len = strlen(s->ssl_cert); |
| } |
| s->ssl_cipher = pblock_findval("cipher", private_data->sn->client); |
| s->ssl_session = pblock_findval("ssl-id", private_data->sn->client); |
| /* XXX: We need to investigate how to set s->ssl_key_size */ |
| } |
| |
| rc = setup_http_headers(private_data, s); |
| |
| /* Dump all connection param so we can trace what's going to |
| * the remote tomcat |
| */ |
| if (JK_IS_DEBUG_LEVEL(logger)) { |
| jk_log(logger, JK_LOG_DEBUG, |
| "Service protocol=%s method=%s host=%s addr=%s name=%s port=%d auth=%s user=%s uri=%s", |
| STRNULL_FOR_NULL(s->protocol), |
| STRNULL_FOR_NULL(s->method), |
| STRNULL_FOR_NULL(s->remote_host), |
| STRNULL_FOR_NULL(s->remote_addr), |
| STRNULL_FOR_NULL(s->server_name), |
| s->server_port, |
| STRNULL_FOR_NULL(s->auth_type), |
| STRNULL_FOR_NULL(s->remote_user), |
| STRNULL_FOR_NULL(s->req_uri)); |
| } |
| |
| return rc; |
| |
| } |
| |
| static int setup_http_headers(nsapi_private_data_t * private_data, |
| jk_ws_service_t *s) |
| { |
| int need_content_length_header = |
| (s->content_length == 0) ? JK_TRUE : JK_FALSE; |
| |
| pblock *headers_jar = private_data->rq->headers; |
| int cnt; |
| int i; |
| |
| for (i = 0, cnt = 0; i < headers_jar->hsize; i++) { |
| struct pb_entry *h = headers_jar->ht[i]; |
| while (h && h->param) { |
| cnt++; |
| h = h->next; |
| } |
| } |
| |
| s->headers_names = NULL; |
| s->headers_values = NULL; |
| s->num_headers = cnt; |
| if (cnt) { |
| /* allocate an extra header slot in case we need to add a content-length header */ |
| s->headers_names = |
| jk_pool_alloc(&private_data->p, (cnt + 1) * sizeof(char *)); |
| s->headers_values = |
| jk_pool_alloc(&private_data->p, (cnt + 1) * sizeof(char *)); |
| |
| if (s->headers_names && s->headers_values) { |
| for (i = 0, cnt = 0; i < headers_jar->hsize; i++) { |
| struct pb_entry *h = headers_jar->ht[i]; |
| while (h && h->param) { |
| s->headers_names[cnt] = h->param->name; |
| s->headers_values[cnt] = h->param->value; |
| if (need_content_length_header && |
| !strncmp(h->param->name, "content-length", 14)) { |
| need_content_length_header = JK_FALSE; |
| } |
| cnt++; |
| h = h->next; |
| } |
| } |
| /* Add a content-length = 0 header if needed. |
| * Ajp13 assumes an absent content-length header means an unknown, |
| * but non-zero length body. |
| */ |
| if (need_content_length_header) { |
| s->headers_names[cnt] = "content-length"; |
| s->headers_values[cnt] = "0"; |
| cnt++; |
| } |
| s->num_headers = cnt; |
| return JK_TRUE; |
| } |
| } |
| else { |
| if (need_content_length_header) { |
| s->headers_names = |
| jk_pool_alloc(&private_data->p, sizeof(char *)); |
| s->headers_values = |
| jk_pool_alloc(&private_data->p, sizeof(char *)); |
| if (s->headers_names && s->headers_values) { |
| s->headers_names[0] = "content-length"; |
| s->headers_values[0] = "0"; |
| s->num_headers++; |
| return JK_TRUE; |
| } |
| } |
| else |
| return JK_TRUE; |
| } |
| |
| return JK_FALSE; |
| } |