blob: e73444cbbd15c1b92205a592013f19e926057a94 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2004-2006 Intel Corp. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Intel Corp. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL Intel Corp. OR THE CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/
/**
* @author Anas Nashif
* @author Vadim Revyakin
* @author Liang Hou
*/
#define _GNU_SOURCE
#ifdef HAVE_CONFIG_H
#include "wsman_config.h"
#endif
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <sys/stat.h>
#include <assert.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifndef WIN32
#include <dlfcn.h>
#endif
#include "u/libu.h"
#include "wsman-xml-api.h"
#include "wsman-soap.h"
#include "wsman-soap-envelope.h"
#include "wsman-xml.h"
#include "wsman-xml-serializer.h"
#include "wsman-dispatcher.h"
#include "shttpd.h"
#include "adapter.h"
#include "wsman-plugins.h"
#include "wsmand-listener.h"
#include "wsmand-daemon.h"
#include "wsman-server.h"
#include "wsman-server-api.h"
#include "wsman-plugins.h"
#ifdef ENABLE_EVENTING_SUPPORT
#include "wsman-cimindication-processor.h"
#endif
#ifdef HAVE_PTHREAD_H
#include <pthread.h>
#endif
#include <sys/socket.h>
static pthread_mutex_t shttpd_mutex;
static pthread_cond_t shttpd_cond;
int continue_working = 1;
static int (*basic_callback) (char *, char *) = NULL;
struct thread {
struct thread *next;
struct shttpd_ctx *ctx;
};
static struct thread *threads; /* List of worker threads */
typedef struct {
char *response;
int length;
int ind;
} ShttpMessage;
#ifdef SHTTPD_GSS
char * gss_decrypt(struct shttpd_arg *arg, char *data, int len);
int gss_encrypt(struct shttpd_arg *arg, char *input, int inlen, char **output, int *outlen);
#endif
/* Check HTTP headers */
static
int check_request_content_type(struct shttpd_arg *arg) {
const char *content_type;
int status = WSMAN_STATUS_OK;
content_type = shttpd_get_header(arg, "Content-Type");
if (content_type && strncmp(content_type,
SOAP_CONTENT_TYPE,
strlen(SOAP_CONTENT_TYPE)) != 0) {
status = WSMAN_STATUS_UNSUPPORTED_MEDIA_TYPE;
}
return status;
}
static
char *get_request_encoding(struct shttpd_arg *arg) {
const char *content_type;
char *p;
char *encoding = "UTF-8";
content_type = shttpd_get_header(arg, "Content-Type");
if(content_type ) {
if(( p = strstr(content_type, "charset")) != NULL ) {
p += strlen("charset");
p++;
encoding = p;
}
}
return encoding;
}
static
void server_callback(struct shttpd_arg *arg)
{
char *encoding = "UTF-8";
const char *s;
SoapH soap;
int k;
int status = WSMAN_STATUS_OK;
char *request_uri;
char *fault_reason = NULL;
struct state {
size_t cl; /* Content-Length */
size_t nread; /* Number of bytes read */
u_buf_t *request;
char *response;
size_t len;
int index;
int type;
} *state;
/* If the connection was broken prematurely, cleanup */
if ( (arg->flags & SHTTPD_CONNECTION_ERROR ) && arg->state) {
free(arg->state);
return;
} else if ((s = shttpd_get_header(arg, "Content-Length")) == NULL) {
shttpd_printf(arg, "HTTP/1.0 411 Length Required\n\n");
arg->flags |= SHTTPD_END_OF_OUTPUT;
return;
} else if (arg->state == NULL) {
/* New request. Allocate a state structure */
arg->state = state = calloc(1, sizeof(*state));
state->cl = strtoul(s, NULL, 10);
u_buf_create(&(state->request));
}
state = arg->state;
if ( state->response ) {
goto CONTINUE;
}
if (state->nread>0 )
u_buf_append(state->request, arg->in.buf, arg->in.len);
else
u_buf_set(state->request, arg->in.buf, arg->in.len);
state->nread += arg->in.len;
arg->in.num_bytes = arg->in.len;
if (state->nread >= state->cl) {
debug("Done reading request");
} else {
return;
}
#ifdef SHTTPD_GSS
const char *ct = shttpd_get_header(arg, "Content-Type");
char *payload = 0; // used for gss encrypt
if (ct && !memcmp(ct, "multipart/encrypted", 19)) {
// we have a encrypted payload. decrypt it
payload = gss_decrypt(arg, u_buf_ptr(state->request), u_buf_len(state->request));
}
#endif
request_uri = (char *)shttpd_get_env(arg, "REQUEST_URI");
if (strcmp(request_uri, "/wsman") == 0 ) {
/* Here we must handle the initial request */
WsmanMessage *wsman_msg = wsman_soap_message_new();
#ifdef SHTTPD_GSS
if(payload == 0) {
#endif
if ( (status = check_request_content_type(arg) ) != WSMAN_STATUS_OK ) {
wsman_soap_message_destroy(wsman_msg);
goto DONE;
}
encoding = get_request_encoding(arg);
u_buf_set(wsman_msg->request, u_buf_ptr(state->request), u_buf_len(state->request));
#ifdef SHTTPD_GSS
}
else {
u_buf_set(wsman_msg->request, payload, strlen(payload));
}
#endif
wsman_msg->charset = u_strdup(encoding);
soap = (SoapH) arg->user_data;
wsman_msg->status.fault_code = WSMAN_RC_OK;
/*
* some plugins can use credentials for their own authentication
* works only with basic authentication
*/
shttpd_get_credentials(arg, &wsman_msg->auth_data.username,
&wsman_msg->auth_data.password);
/* Call dispatcher. Real request handling */
if (status == WSMAN_STATUS_OK) {
/* dispatch if we didn't find out any error */
char *idfile = wsmand_options_get_identify_file();
if (idfile && wsman_check_identify(wsman_msg) == 1) {
if (u_buf_load(wsman_msg->response, idfile)) {
dispatch_inbound_call(soap, wsman_msg, NULL);
status = wsman_msg->http_code;
}
} else {
dispatch_inbound_call(soap, wsman_msg, NULL);
status = wsman_msg->http_code;
}
}
if (wsman_msg->request) {
#ifdef SHTTPD_GSS
if (payload) {
free(payload);
/* note that payload is stiil set - this is used as a flag later */
}
else {
#endif
u_buf_free(wsman_msg->request);
#ifdef SHTTPD_GSS
}
#endif
wsman_msg->request = NULL;
}
state->len = u_buf_len(wsman_msg->response);;
state->response = u_buf_steal(wsman_msg->response);
state->index = 0;
state->type = 0;
wsman_soap_message_destroy(wsman_msg);
#ifdef ENABLE_EVENTING_SUPPORT
} else if (strncmp(request_uri, DEFAULT_CIMINDICATION_PATH, strlen(DEFAULT_CIMINDICATION_PATH)) == 0 ) {
status = CIMXML_STATUS_OK;
int cim_error_code = 0;
char *cim_error = NULL;
char *fault_reason = NULL;
char *uuid = NULL, *tmp, *end;
cimxml_context *cntx = NULL;
SoapH soap = NULL;
CimxmlMessage *cimxml_msg = cimxml_message_new();
tmp = (char *)shttpd_get_env(arg, "REQUEST_URI");
if (tmp && ( end = strrchr(tmp, '/')) != NULL ) {
uuid = &end[1];
}
encoding = get_request_encoding(arg);
cimxml_msg->charset = u_strdup(encoding);
const char *cimexport = shttpd_get_header(arg, "CIMExport");
const char *cimexportmethod = shttpd_get_header(arg, "CIMExportMethod");
if ( cimexportmethod && cimexport ) {
if(strncmp(cimexport, "MethodRequest", strlen("MethodRequest")) ||
strncmp(cimexportmethod, "ExportIndication", strlen("ExportIndication"))) {
}
} else {
status = WSMAN_STATUS_FORBIDDEN;
cim_error_code = CIMXML_STATUS_UNSUPPORTED_OPERATION;
cim_error = "unsupported-operation";
goto DONE;
}
soap = (SoapH) arg->user_data;
u_buf_set(cimxml_msg->request, u_buf_ptr(state->request), u_buf_len(state->request));
cntx = u_malloc(sizeof(cimxml_context));
cntx->soap = soap;
cntx->uuid = uuid;
CIM_Indication_call(cntx, cimxml_msg, NULL);
status = cimxml_msg->http_code;
cim_error_code = cimxml_msg->status.code;
cim_error = cimxml_msg->status.fault_msg;
if (cim_error) {
shttpd_printf(arg, "HTTP/1.1 %d %s\r\n", status, fault_reason);
shttpd_printf(arg, "CIMError: %s\r\n", cim_error);
cimxml_message_destroy(cimxml_msg);
goto CONTINUE;
}
state->len = u_buf_len(cimxml_msg->response);;
state->response = u_buf_steal(cimxml_msg->response);
state->index = 0;
state->type = 1;
cimxml_message_destroy(cimxml_msg);
#endif
} else if (strcmp(request_uri, ANON_IDENTIFY_PATH) == 0 ) {
char *idfile = wsmand_options_get_anon_identify_file();
u_buf_t *id;
u_buf_create(&id);
if (idfile && u_buf_load(id, idfile) == 0 ) {
state->len = u_buf_len(id);;
state->response = u_buf_steal(id);
state->index = 0;
u_buf_free(id);
} else {
shttpd_printf(arg, "HTTP/1.0 404 Not foundn\n");
arg->flags |= SHTTPD_END_OF_OUTPUT;
u_buf_free(id);
return;
}
} else {
shttpd_printf(arg, "HTTP/1.0 404 Not foundn\n");
arg->flags |= SHTTPD_END_OF_OUTPUT;
return;
}
DONE:
if (fault_reason == NULL) {
fault_reason = shttpd_reason_phrase(status);
}
debug("Response status=%d (%s)", status, fault_reason);
/*
* Here we begin to create the http response.
* Create the headers at first.
*/
shttpd_printf(arg, "HTTP/1.1 %d %s\r\n", status, fault_reason);
shttpd_printf(arg, "Server: %s/%s\r\n", PACKAGE_NAME, PACKAGE_VERSION);
#ifdef SHTTPD_GSS
if(payload) {
// we had an encrypted message so now we have to encypt the reply
char *enc;
int enclen;
gss_encrypt(arg, state->response, state->len, &enc, &enclen);
u_free(state->response);
state->response = enc;
state->len = enclen;
payload = 0; // and reset the indicator so that if we send in packates we dont do this again
shttpd_printf(arg, "Content-Type: multipart/encrypted;protocol=\"application/HTTP-Kerberos-session-encrypted\";boundary=\"Encrypted Boundary\"\r\n");
shttpd_printf(arg, "Content-Length: %d\r\n", state->len);
}
else {
#endif
if (state->type == 1) { /* eventing */
shttpd_printf(arg, "Content-Type: application/xml; charset=\"utf-8\"\r\n");
shttpd_printf(arg, "CIMExport: MethodResponse\r\n");
} else {
shttpd_printf(arg, "Content-Type: application/soap+xml;charset=%s\r\n", encoding);
}
shttpd_printf(arg, "Content-Length: %d\r\n", state->len);
#ifdef SHTTPD_GSS
}
#endif
shttpd_printf(arg,"Connection: Close\r\n");
/* separate header from message-body */
shttpd_printf(arg, "\r\n");
/* add response body to output buffer */
CONTINUE:
k = arg->out.len - arg->out.num_bytes;
if (k <= state->len - state->index) {
memcpy(arg->out.buf + arg->out.num_bytes, state->response + state->index, k );
state->index += k ;
arg->out.num_bytes += k;
return;
}
else {
int l = state->len - state->index;
memcpy(arg->out.buf + arg->out.num_bytes, state->response + state->index, l);
state->index += l ;
arg->out.num_bytes += l;
}
u_buf_free(state->request);
u_free(state->response);
u_free(state);
arg->flags |= SHTTPD_END_OF_OUTPUT;
return;
}
static void listener_shutdown_handler(void *p)
{
int *a = (int *) p;
debug("listener_shutdown_handler started");
*a = 0;
}
static void protect_uri(struct shttpd_ctx *ctx, char *uri)
{
if (wsmand_options_get_digest_password_file()) {
shttpd_protect_uri(ctx, uri,
wsmand_options_get_digest_password_file(),NULL, 1);
debug("Using Digest Authorization for %s:", uri);
}
if (basic_callback) {
shttpd_protect_uri(ctx, uri, wsmand_options_get_basic_password_file(),
basic_callback, 0);
debug("Using Basic Authorization %s for %s",
wsmand_option_get_basic_authenticator()?
wsmand_option_get_basic_authenticator() :
wsmand_default_basic_authenticator(), uri);
}
}
static struct shttpd_ctx *create_shttpd_context(SoapH soap)
{
struct shttpd_ctx *ctx;
if (wsmand_options_get_use_ssl()) {
message("ssl certificate: %s", wsmand_options_get_ssl_cert_file());
message("Using SSL");
ctx = shttpd_init(NULL,
"ssl_certificate",
wsmand_options_get_ssl_cert_file(),
"auth_realm",
AUTHENTICATION_REALM,
NULL);
} else {
ctx = shttpd_init(NULL,
"auth_realm", AUTHENTICATION_REALM,
NULL);
}
if (ctx == NULL) {
return NULL;
}
shttpd_register_uri(ctx, wsmand_options_get_service_path(),
server_callback, (void *) soap);
shttpd_register_uri(ctx, ANON_IDENTIFY_PATH,
server_callback, (void *) soap);
#ifdef ENABLE_EVENTING_SUPPORT
message("Registered CIM Indication Listener: %s", DEFAULT_CIMINDICATION_PATH "/*");
shttpd_register_uri(ctx, DEFAULT_CIMINDICATION_PATH "/*", server_callback,(void *)soap);
protect_uri( ctx, DEFAULT_CIMINDICATION_PATH );
#endif
protect_uri( ctx, wsmand_options_get_service_path());
return ctx;
}
static int initialize_basic_authenticator(void)
{
char *auth;
char *arg;
void *hnd;
int (*init) (char *);
char *name;
int should_return = 0;
int res = 0;
if (wsmand_options_get_basic_password_file() != NULL) {
if ((wsmand_option_get_basic_authenticator() &&
(strcmp(wsmand_default_basic_authenticator(),
wsmand_option_get_basic_authenticator()))) ||
wsmand_option_get_basic_authenticator_arg()) {
fprintf(stderr,
"basic authentication is ambigious in config file\n");
return 1;
}
auth = wsmand_default_basic_authenticator();
arg = wsmand_options_get_basic_password_file();
} else {
auth = wsmand_option_get_basic_authenticator();
arg = wsmand_option_get_basic_authenticator_arg();
}
if (auth == NULL) {
/* No basic authenticationame */
return 0;
}
if (auth[0] == '/') {
name = auth;
} else {
name = u_strdup_printf("%s/%s", PACKAGE_AUTH_DIR, auth);
should_return = 1;
}
hnd = dlopen(name, RTLD_LAZY | RTLD_GLOBAL);
if (hnd == NULL) {
fprintf(stderr, "Could not dlopen %s\n", name);
res = 1;
goto DONE;
}
basic_callback = dlsym(hnd, "authorize");
if (basic_callback == NULL) {
fprintf(stderr, "Could not resolve authorize() in %s\n",
name);
res = 1;
goto DONE;
}
init = dlsym(hnd, "initialize");
if (init != NULL) {
res = init(arg);
}
DONE:
if (should_return) {
u_free(name);
}
return res;
}
static int get_server_auth(void) {
if (initialize_basic_authenticator()) {
return 0;
}
if (wsmand_options_get_digest_password_file()) {
message("Using Digest Authorization");
}
if (basic_callback) {
message("Using Basic Authorization %s",
wsmand_option_get_basic_authenticator()?
wsmand_option_get_basic_authenticator() :
wsmand_default_basic_authenticator());
}
if ((wsmand_options_get_digest_password_file() == NULL) &&
(basic_callback == NULL)) {
error("Server does not work without authentication");
return 0;
}
return 1;
}
static int get_server_port(void) {
int port = 0;
int use_ssl = wsmand_options_get_use_ssl();
if (use_ssl) {
message("Using SSL");
if (wsmand_options_get_ssl_cert_file() &&
wsmand_options_get_ssl_key_file() &&
(wsmand_options_get_server_ssl_port() > 0)) {
port = wsmand_options_get_server_ssl_port();
} else {
error("Not enough data to use SSL port");
return 0;
}
} else {
port = wsmand_options_get_server_port();
}
return port;
}
static int wsman_setup_thread(pthread_attr_t *pattrs) {
int r;
int ret = 0;
if ((r = pthread_cond_init(&shttpd_cond, NULL)) != 0) {
debug("pthread_cond_init failed = %d", r);
return ret;
}
if ((r = pthread_mutex_init(&shttpd_mutex, NULL)) != 0) {
debug("pthread_mutex_init failed = %d", r);
return ret;
}
if ((r = pthread_attr_init(pattrs)) != 0) {
debug("pthread_attr_init failed = %d", r);
return ret;
}
if ((r = pthread_attr_setdetachstate(pattrs, PTHREAD_CREATE_DETACHED)) != 0) {
debug("pthread_attr_setdetachstate = %d", r);
return ret;
}
return 1;
size_t thread_stack_size = wsmand_options_get_thread_stack_size();
if(thread_stack_size){
if(( r = pthread_attr_setstacksize(pattrs, thread_stack_size)) !=0) {
debug("pthread_attr_setstacksize failed = %d", r);
return ret;
}
}
}
static void *thread_function(void *param)
{
struct thread *thread = param;
for (;;)
shttpd_poll(thread->ctx, 1000);
return NULL;
}
static struct thread *
spawn_new_thread(pthread_attr_t pattrs, SoapH soap)
{
struct shttpd_ctx *ctx;
struct thread *thread;
pthread_t tid;
debug("spawning new thread");
thread = malloc(sizeof(*thread));
ctx = create_shttpd_context(soap);
assert(ctx != NULL);
assert(thread != NULL);
thread->ctx = ctx;
thread->next = threads;
threads = thread;
pthread_create(&tid, &pattrs, thread_function, thread);
return (thread);
}
static struct thread *
find_not_busy_thread(int *num_threads, int max_connections_per_thread)
{
struct thread *thread;
for (thread = threads, *num_threads=0; thread != NULL; thread = thread->next) {
debug("Active sockets: %d, Thread Number: %d", shttpd_active(thread->ctx), *num_threads );
(*num_threads)++;
if (shttpd_active(thread->ctx) < max_connections_per_thread)
return (thread);
}
return (NULL);
}
WsManListenerH *wsmand_start_server(dictionary * ini)
{
int lsn, port, sock;
struct thread *thread;
pthread_t tid;
#ifdef ENABLE_EVENTING_SUPPORT
pthread_t notificationManager_id;
#endif
pthread_attr_t pattrs;
int use_ssl = wsmand_options_get_use_ssl();
struct shttpd_ctx *httpd_ctx;
WsManListenerH *listener = wsman_dispatch_list_new();
listener->config = ini;
WsContextH cntx = wsman_init_plugins(listener);
int num_threads=0;
int max_threads=wsmand_options_get_max_threads();
int max_connections_per_thread = wsmand_options_get_max_connections_per_thread();
if(max_threads && !max_connections_per_thread){
error("max_threads: %d and max_connections_per_thread : %d", max_threads, max_connections_per_thread);
return listener;
}
#ifdef ENABLE_EVENTING_SUPPORT
wsman_event_init(cntx->soap);
#endif
if (cntx == NULL) {
return listener;
}
#ifndef HAVE_SSL
if (use_ssl) {
error("Server configured without SSL support");
return listener;
}
#endif
SoapH soap = ws_context_get_runtime(cntx);
ws_set_context_enumIdleTimeout(cntx,wsmand_options_get_enumIdleTimeout());
if ((port = get_server_port()) == 0 )
return listener;
#ifdef ENABLE_IPV6
if (wsmand_options_get_use_ipv6()) {
message(" Working on IPv6 port %d", port);
}
else {
#endif
message(" Working on IPv4 port %d", port);
#ifdef ENABLE_IPV6
}
#endif
if (!get_server_auth())
return listener;
wsmand_shutdown_add_handler(listener_shutdown_handler,
&continue_working);
httpd_ctx = create_shttpd_context(soap);
lsn = shttpd_listen(httpd_ctx, port, use_ssl);
if (wsman_setup_thread(&pattrs) == 0 )
return listener;
pthread_create(&tid, &pattrs, wsman_server_auxiliary_loop_thread, cntx);
#ifdef ENABLE_EVENTING_SUPPORT
pthread_create(&notificationManager_id, &pattrs, wsman_notification_manager, cntx);
#endif
while (continue_working) {
if ((sock = shttpd_accept(lsn, 1000)) == -1) {
continue;
}
debug("Sock %d accepted", sock);
if ((thread = find_not_busy_thread(&num_threads, max_connections_per_thread)) == NULL){
if(max_threads){
if(num_threads < max_threads){
thread = spawn_new_thread(pattrs, soap);
}
else{
continue;
}
}
else{
thread = spawn_new_thread(pattrs, soap);
}
}
shttpd_add_socket(thread->ctx, sock, use_ssl);
}
return listener;
}