diff options
Diffstat (limited to 'Utilities/cmxmlrpc/xmlrpc_server_abyss.c')
-rw-r--r-- | Utilities/cmxmlrpc/xmlrpc_server_abyss.c | 799 |
1 files changed, 799 insertions, 0 deletions
diff --git a/Utilities/cmxmlrpc/xmlrpc_server_abyss.c b/Utilities/cmxmlrpc/xmlrpc_server_abyss.c new file mode 100644 index 0000000..5964c33 --- /dev/null +++ b/Utilities/cmxmlrpc/xmlrpc_server_abyss.c @@ -0,0 +1,799 @@ +/* Copyright (C) 2001 by First Peer, Inc. All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. 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. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 THE AUTHOR OR 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. +** +** There is more copyright information in the bottom half of this file. +** Please see it for more details. */ + +#include "xmlrpc_config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "abyss.h" + +#include "xmlrpc.h" +#include "xmlrpc_server.h" +#include "xmlrpc_int.h" +#include "xmlrpc_server_abyss.h" +#include "xmlrpc_server_abyss_int.h" + + +/*========================================================================= +** die_if_fault_occurred +**========================================================================= +** If certain kinds of out-of-memory errors occur during server setup, +** we want to quit and print an error. +*/ + +static void die_if_fault_occurred(xmlrpc_env *env) { + if (env->fault_occurred) { + fprintf(stderr, "Unexpected XML-RPC fault: %s (%d)\n", + env->fault_string, env->fault_code); + exit(1); + } +} + + + +/*========================================================================= +** send_xml_data +**========================================================================= +** Blast some XML data back to the client. +*/ + +static void +send_xml_data (TSession * const r, + char * const buffer, + uint64 const len) { + + const char * const http_cookie = NULL; + /* This used to set http_cookie to getenv("HTTP_COOKIE"), but + that doesn't make any sense -- environment variables are not + appropriate for this. So for now, cookie code is disabled. + - Bryan 2004.10.03. + */ + + /* fwrite(buffer, sizeof(char), len, stderr); */ + + /* XXX - Is it safe to chunk our response? */ + ResponseChunked(r); + + ResponseStatus(r, 200); + + if (http_cookie) { + /* There's an auth cookie, so pass it back in the response. */ + + char *cookie_response; + + cookie_response = malloc(10+strlen(http_cookie)); + sprintf(cookie_response, "auth=%s", http_cookie); + + /* Return abyss response. */ + ResponseAddField(r, "Set-Cookie", cookie_response); + + free(cookie_response); + } + + + ResponseContentType(r, "text/xml; charset=\"utf-8\""); + ResponseContentLength(r, len); + + ResponseWrite(r); + + HTTPWrite(r, buffer, len); + HTTPWriteEnd(r); +} + + + +/*========================================================================= +** send_error +**========================================================================= +** Send an error back to the client. +*/ + +static void +send_error(TSession * const abyssSessionP, + unsigned int const status) { + + ResponseStatus(abyssSessionP, (uint16) status); + ResponseError(abyssSessionP); +} + + + +/*========================================================================= +** get_buffer_data +**========================================================================= +** Extract some data from the TConn's underlying input buffer. Do not +** extract more than 'max'. +*/ + +static void +get_buffer_data(TSession * const r, + int const max, + char ** const out_start, + int * const out_len) { + + /* Point to the start of our data. */ + *out_start = &r->conn->buffer[r->conn->bufferpos]; + + /* Decide how much data to retrieve. */ + *out_len = r->conn->buffersize - r->conn->bufferpos; + if (*out_len > max) + *out_len = max; + + /* Update our buffer position. */ + r->conn->bufferpos += *out_len; +} + + + +/*========================================================================= +** get_body +**========================================================================= +** Slurp the body of the request into an xmlrpc_mem_block. +*/ + +static void +getBody(xmlrpc_env * const envP, + TSession * const abyssSessionP, + unsigned int const contentSize, + xmlrpc_mem_block ** const bodyP) { +/*---------------------------------------------------------------------------- + Get the entire body from the Abyss session and return it as the new + memblock *bodyP. + + The first chunk of the body may already be in Abyss's buffer. We + retrieve that before reading more. +-----------------------------------------------------------------------------*/ + xmlrpc_mem_block * body; + + body = xmlrpc_mem_block_new(envP, 0); + if (!envP->fault_occurred) { + unsigned int bytesRead; + char * chunkPtr; + int chunkLen; + + bytesRead = 0; + + while (!envP->fault_occurred && bytesRead < contentSize) { + get_buffer_data(abyssSessionP, contentSize - bytesRead, + &chunkPtr, &chunkLen); + bytesRead += chunkLen; + + XMLRPC_TYPED_MEM_BLOCK_APPEND(char, envP, body, + chunkPtr, chunkLen); + + if (bytesRead < contentSize) { + /* Get the next chunk of data from the connection into the + buffer + */ + abyss_bool succeeded; + + /* Reset our read buffer & flush data from previous reads. */ + ConnReadInit(abyssSessionP->conn); + + /* Read more network data into our buffer. If we encounter + a timeout, exit immediately. We're very forgiving about + the timeout here. We allow a full timeout per network + read, which would allow somebody to keep a connection + alive nearly indefinitely. But it's hard to do anything + intelligent here without very complicated code. + */ + succeeded = ConnRead(abyssSessionP->conn, + abyssSessionP->server->timeout); + if (!succeeded) + xmlrpc_env_set_fault_formatted( + envP, XMLRPC_TIMEOUT_ERROR, "Timed out waiting for " + "client to send its POST data"); + } + } + if (envP->fault_occurred) + xmlrpc_mem_block_free(body); + else + *bodyP = body; + } +} + + + +static void +storeCookies(TSession * const httpRequestP, + unsigned int * const httpErrorP) { +/*---------------------------------------------------------------------------- + Get the cookie settings from the HTTP headers and remember them for + use in responses. +-----------------------------------------------------------------------------*/ + const char * const cookie = RequestHeaderValue(httpRequestP, "cookie"); + if (cookie) { + /* + Setting the value in an environment variable doesn't make + any sense. So for now, cookie code is disabled. + -Bryan 04.10.03. + + setenv("HTTP_COOKIE", cookie, 1); + */ + } + /* TODO: parse HTTP_COOKIE to find auth pair, if there is one */ + + *httpErrorP = 0; +} + + + + +static void +validateContentType(TSession * const httpRequestP, + unsigned int * const httpErrorP) { +/*---------------------------------------------------------------------------- + If the client didn't specify a content-type of "text/xml", return + "400 Bad Request". We can't allow the client to default this header, + because some firewall software may rely on all XML-RPC requests + using the POST method and a content-type of "text/xml". +-----------------------------------------------------------------------------*/ + const char * const content_type = + RequestHeaderValue(httpRequestP, "content-type"); + if (content_type == NULL || strcmp(content_type, "text/xml") != 0) + *httpErrorP = 400; + else + *httpErrorP = 0; +} + + + +static void +processContentLength(TSession * const httpRequestP, + unsigned int * const inputLenP, + unsigned int * const httpErrorP) { +/*---------------------------------------------------------------------------- + Make sure the content length is present and non-zero. This is + technically required by XML-RPC, but we only enforce it because we + don't want to figure out how to safely handle HTTP < 1.1 requests + without it. If the length is missing, return "411 Length Required". +-----------------------------------------------------------------------------*/ + const char * const content_length = + RequestHeaderValue(httpRequestP, "content-length"); + if (content_length == NULL) + *httpErrorP = 411; + else { + int const contentLengthValue = atoi(content_length); + if (contentLengthValue <= 0) + *httpErrorP = 400; + else { + *httpErrorP = 0; + *inputLenP = (unsigned int)contentLengthValue; + } + } +} + + +/**************************************************************************** + Abyss handlers (to be registered with and called by Abyss) +****************************************************************************/ + +/* XXX - This variable is *not* currently threadsafe. Once the server has +** been started, it must be treated as read-only. */ +static xmlrpc_registry *global_registryP; + +static const char * trace_abyss; + +static void +processCall(TSession * const abyssSessionP, + int const inputLen) { +/*---------------------------------------------------------------------------- + Handle an RPC request. This is an HTTP request that has the proper form + to be one of our RPCs. +-----------------------------------------------------------------------------*/ + xmlrpc_env env; + + if (trace_abyss) + fprintf(stderr, "xmlrpc_server_abyss RPC2 handler processing RPC.\n"); + + xmlrpc_env_init(&env); + + /* SECURITY: Make sure our content length is legal. + XXX - We can cast 'inputLen' because we know it's >= 0, yes? + */ + if ((size_t) inputLen > xmlrpc_limit_get(XMLRPC_XML_SIZE_LIMIT_ID)) + xmlrpc_env_set_fault_formatted( + &env, XMLRPC_LIMIT_EXCEEDED_ERROR, + "XML-RPC request too large (%d bytes)", inputLen); + else { + xmlrpc_mem_block *body; + /* Read XML data off the wire. */ + getBody(&env, abyssSessionP, inputLen, &body); + if (!env.fault_occurred) { + xmlrpc_mem_block * output; + /* Process the RPC. */ + output = xmlrpc_registry_process_call( + &env, global_registryP, NULL, + XMLRPC_MEMBLOCK_CONTENTS(char, body), + XMLRPC_MEMBLOCK_SIZE(char, body)); + if (!env.fault_occurred) { + /* Send our the result. */ + send_xml_data(abyssSessionP, + XMLRPC_MEMBLOCK_CONTENTS(char, output), + XMLRPC_MEMBLOCK_SIZE(char, output)); + + XMLRPC_MEMBLOCK_FREE(char, output); + } + XMLRPC_MEMBLOCK_FREE(char, body); + } + } + if (env.fault_occurred) { + if (env.fault_code == XMLRPC_TIMEOUT_ERROR) + send_error(abyssSessionP, 408); /* 408 Request Timeout */ + else + send_error(abyssSessionP, 500); /* 500 Internal Server Error */ + } + + xmlrpc_env_clean(&env); +} + + + +/*========================================================================= +** xmlrpc_server_abyss_rpc2_handler +**========================================================================= +** This handler processes all requests to '/RPC2'. See the header for +** more documentation. +*/ + +xmlrpc_bool +xmlrpc_server_abyss_rpc2_handler (TSession * const r) { + + xmlrpc_bool retval; + + if (trace_abyss) + fprintf(stderr, "xmlrpc_server_abyss RPC2 handler called.\n"); + + /* We handle only requests to /RPC2, the default XML-RPC URL. + Everything else we pass through to other handlers. + */ + if (strcmp(r->uri, "/RPC2") != 0) + retval = FALSE; + else { + retval = TRUE; + + /* We understand only the POST HTTP method. For anything else, return + "405 Method Not Allowed". + */ + if (r->method != m_post) + send_error(r, 405); + else { + unsigned int httpError; + storeCookies(r, &httpError); + if (httpError) + send_error(r, httpError); + else { + unsigned int httpError; + validateContentType(r, &httpError); + if (httpError) + send_error(r, httpError); + else { + unsigned int httpError; + int inputLen; + + processContentLength(r, &inputLen, &httpError); + if (httpError) + send_error(r, httpError); + + processCall(r, inputLen); + } + } + } + } + if (trace_abyss) + fprintf(stderr, "xmlrpc_server_abyss RPC2 handler returning.\n"); + return retval; +} + + + +/*========================================================================= +** xmlrpc_server_abyss_default_handler +**========================================================================= +** This handler returns a 404 Not Found for all requests. See the header +** for more documentation. +*/ + +xmlrpc_bool +xmlrpc_server_abyss_default_handler (TSession * const r) { + send_error(r, 404); + + return TRUE; +} + + + +/************************************************************************** +** +** The code below was adapted from the main.c file of the Abyss webserver +** project. In addition to the other copyrights on this file, the following +** code is also under this copyright: +** +** Copyright (C) 2000 by Moez Mahfoudh <mmoez@bigfoot.com>. +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. 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. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 THE AUTHOR OR 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. +** +**************************************************************************/ + +#include <time.h> +#include <fcntl.h> + +#ifdef _WIN32 +#include <io.h> +#else +/* Must check this +#include <sys/io.h> +*/ +#endif /* _WIN32 */ + +#ifdef _UNIX +#include <sys/signal.h> +#include <sys/wait.h> +#include <grp.h> +#endif + + +#ifdef _UNIX +static void +sigterm(int const sig) { + TraceExit("Signal %d received. Exiting...\n",sig); +} +#endif + + +#ifdef _UNIX +static void +sigchld(int const sig ATTR_UNUSED) { +/*---------------------------------------------------------------------------- + This is a signal handler for a SIGCHLD signal (which informs us that + one of our child processes has terminated). + + We respond by reaping the zombie process. + + Implementation note: In some systems, just setting the signal handler + to SIG_IGN (ignore signal) does this. In others, it doesn't. +-----------------------------------------------------------------------------*/ + pid_t pid; + int status; + + /* Reap defunct children until there aren't any more. */ + for (;;) { + pid = waitpid( (pid_t) -1, &status, WNOHANG ); + + /* none left */ + if (pid==0) + break; + + if (pid<0) { + /* because of ptrace */ + if (errno==EINTR) + continue; + + break; + } + } +} +#endif /* _UNIX */ + +static TServer globalSrv; + /* When you use the old interface (xmlrpc_server_abyss_init(), etc.), + this is the Abyss server to which they refer. Obviously, there can be + only one Abyss server per program using this interface. + */ + + +void +xmlrpc_server_abyss_init(int const flags ATTR_UNUSED, + const char * const config_file) { + + DateInit(); + MIMETypeInit(); + + ServerCreate(&globalSrv, "XmlRpcServer", 8080, DEFAULT_DOCS, NULL); + + ConfReadServerFile(config_file, &globalSrv); + + xmlrpc_server_abyss_init_registry(); + /* Installs /RPC2 handler and default handler that use the + built-in registry. + */ + + ServerInit(&globalSrv); +} + + + +static void +setupSignalHandlers(void) { +#ifdef _UNIX + struct sigaction mysigaction; + + sigemptyset(&mysigaction.sa_mask); + mysigaction.sa_flags = 0; + + /* These signals abort the program, with tracing */ + mysigaction.sa_handler = sigterm; + sigaction(SIGTERM, &mysigaction, NULL); + sigaction(SIGINT, &mysigaction, NULL); + sigaction(SIGHUP, &mysigaction, NULL); + sigaction(SIGUSR1, &mysigaction, NULL); + + /* This signal indicates connection closed in the middle */ + mysigaction.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &mysigaction, NULL); + + /* This signal indicates a child process (request handler) has died */ + mysigaction.sa_handler = sigchld; + sigaction(SIGCHLD, &mysigaction, NULL); +#endif +} + + + +static void +runServer(TServer * const srvP, + runfirstFn const runfirst, + void * const runfirstArg) { + + setupSignalHandlers(); + +#ifdef _UNIX + /* Become a daemon */ + switch (fork()) { + case 0: + break; + case -1: + TraceExit("Unable to become a daemon"); + default: + exit(0); + }; + + setsid(); + + /* Change the current user if we are root */ + if (getuid()==0) { + if (srvP->uid == (uid_t)-1) + TraceExit("Can't run under root privileges. " + "Please add a User option in your " + "Abyss configuration file."); + +#ifdef HAVE_SETGROUPS + if (setgroups(0,NULL)==(-1)) + TraceExit("Failed to setup the group."); + if (srvP->gid != (gid_t)-1) + if (setgid(srvP->gid)==(-1)) + TraceExit("Failed to change the group."); +#endif + + if (setuid(srvP->uid) == -1) + TraceExit("Failed to change the user."); + }; + + if (srvP->pidfile!=(-1)) { + char z[16]; + + sprintf(z,"%d",getpid()); + FileWrite(&srvP->pidfile,z,strlen(z)); + FileClose(&srvP->pidfile); + }; +#endif + + /* We run the user supplied runfirst after forking, but before accepting + connections (helpful when running with threads) + */ + if (runfirst) + runfirst(runfirstArg); + + ServerRun(srvP); + + /* We can't exist here because ServerRun doesn't return */ + XMLRPC_ASSERT(FALSE); +} + + + +void +xmlrpc_server_abyss_run_first(runfirstFn const runfirst, + void * const runfirstArg) { + + runServer(&globalSrv, runfirst, runfirstArg); +} + + + +void +xmlrpc_server_abyss_run(void) { + runServer(&globalSrv, NULL, NULL); +} + + + +void +xmlrpc_server_abyss_set_handlers(TServer * const srvP, + xmlrpc_registry * const registryP) { + + /* Abyss ought to have a way to register with a handler an argument + that gets passed to the handler every time it is called. That's + where we should put the registry handle. But we don't find such + a thing in Abyss, so we use the global variable 'global_registryP'. + */ + global_registryP = registryP; + + trace_abyss = getenv("XMLRPC_TRACE_ABYSS"); + + ServerAddHandler(srvP, xmlrpc_server_abyss_rpc2_handler); + ServerDefaultHandler(srvP, xmlrpc_server_abyss_default_handler); +} + + + +void +xmlrpc_server_abyss(xmlrpc_env * const envP, + const xmlrpc_server_abyss_parms * const parmsP, + unsigned int const parm_size) { + + XMLRPC_ASSERT_ENV_OK(envP); + + if (parm_size < XMLRPC_APSIZE(registryP)) + xmlrpc_env_set_fault_formatted( + envP, XMLRPC_INTERNAL_ERROR, + "You must specify members at least up through " + "'registryP' in the server parameters argument. " + "That would mean the parameter size would be >= %u " + "but you specified a size of %u", + XMLRPC_APSIZE(registryP), parm_size); + else { + TServer srv; + runfirstFn runfirst; + void * runfirstArg; + + DateInit(); + MIMETypeInit(); + + ServerCreate(&srv, "XmlRpcServer", 8080, DEFAULT_DOCS, NULL); + + ConfReadServerFile(parmsP->config_file_name, &srv); + + xmlrpc_server_abyss_set_handlers(&srv, parmsP->registryP); + + ServerInit(&srv); + + if (parm_size >= XMLRPC_APSIZE(runfirst_arg)) { + runfirst = parmsP->runfirst; + runfirstArg = parmsP->runfirst_arg; + } else { + runfirst = NULL; + runfirstArg = NULL; + } + runServer(&srv, runfirst, runfirstArg); + } +} + + + +/*========================================================================= +** XML-RPC Server Method Registry +**========================================================================= +** A simple front-end to our method registry. +*/ + +/* XXX - This variable is *not* currently threadsafe. Once the server has +** been started, it must be treated as read-only. */ +static xmlrpc_registry *builtin_registryP; + +void +xmlrpc_server_abyss_init_registry(void) { + + /* This used to just create the registry and Caller would be + responsible for adding the handlers that use it. + + But that isn't very modular -- the handlers and registry go + together; there's no sense in using the built-in registry and + not the built-in handlers because if you're custom building + something, you can just make your own regular registry. So now + we tie them together, and we don't export our handlers. + */ + xmlrpc_env env; + + xmlrpc_env_init(&env); + builtin_registryP = xmlrpc_registry_new(&env); + die_if_fault_occurred(&env); + xmlrpc_env_clean(&env); + + xmlrpc_server_abyss_set_handlers(&globalSrv, builtin_registryP); +} + + + +xmlrpc_registry * +xmlrpc_server_abyss_registry(void) { + + /* This is highly deprecated. If you want to mess with a registry, + make your own with xmlrpc_registry_new() -- don't mess with the + internal one. + */ + return builtin_registryP; +} + + + +/* A quick & easy shorthand for adding a method. */ +void +xmlrpc_server_abyss_add_method (char * const method_name, + xmlrpc_method const method, + void * const user_data) { + xmlrpc_env env; + + xmlrpc_env_init(&env); + xmlrpc_registry_add_method(&env, builtin_registryP, NULL, method_name, + method, user_data); + die_if_fault_occurred(&env); + xmlrpc_env_clean(&env); +} + + + +void +xmlrpc_server_abyss_add_method_w_doc (char * const method_name, + xmlrpc_method const method, + void * const user_data, + char * const signature, + char * const help) { + + xmlrpc_env env; + xmlrpc_env_init(&env); + xmlrpc_registry_add_method_w_doc( + &env, builtin_registryP, NULL, method_name, + method, user_data, signature, help); + die_if_fault_occurred(&env); + xmlrpc_env_clean(&env); +} |