/* 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 #include #include #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 . ** 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 #include #ifdef _WIN32 #include #else /* Must check this #include */ #endif /* _WIN32 */ #ifdef _UNIX #include #include #include #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); }