ENH: make this work for older versions of OSX
[cmake.git] / Utilities / cmxmlrpc / xmlrpc_server_abyss.c
blob5964c33c0a1d70cd204baae98aa510be585849f7
1 /* Copyright (C) 2001 by First Peer, Inc. All rights reserved.
2 **
3 ** Redistribution and use in source and binary forms, with or without
4 ** modification, are permitted provided that the following conditions
5 ** are met:
6 ** 1. Redistributions of source code must retain the above copyright
7 ** notice, this list of conditions and the following disclaimer.
8 ** 2. Redistributions in binary form must reproduce the above copyright
9 ** notice, this list of conditions and the following disclaimer in the
10 ** documentation and/or other materials provided with the distribution.
11 ** 3. The name of the author may not be used to endorse or promote products
12 ** derived from this software without specific prior written permission.
13 **
14 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 ** SUCH DAMAGE.
26 ** There is more copyright information in the bottom half of this file.
27 ** Please see it for more details. */
29 #include "xmlrpc_config.h"
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
35 #include "abyss.h"
37 #include "xmlrpc.h"
38 #include "xmlrpc_server.h"
39 #include "xmlrpc_int.h"
40 #include "xmlrpc_server_abyss.h"
41 #include "xmlrpc_server_abyss_int.h"
44 /*=========================================================================
45 ** die_if_fault_occurred
46 **=========================================================================
47 ** If certain kinds of out-of-memory errors occur during server setup,
48 ** we want to quit and print an error.
51 static void die_if_fault_occurred(xmlrpc_env *env) {
52 if (env->fault_occurred) {
53 fprintf(stderr, "Unexpected XML-RPC fault: %s (%d)\n",
54 env->fault_string, env->fault_code);
55 exit(1);
61 /*=========================================================================
62 ** send_xml_data
63 **=========================================================================
64 ** Blast some XML data back to the client.
67 static void
68 send_xml_data (TSession * const r,
69 char * const buffer,
70 uint64 const len) {
72 const char * const http_cookie = NULL;
73 /* This used to set http_cookie to getenv("HTTP_COOKIE"), but
74 that doesn't make any sense -- environment variables are not
75 appropriate for this. So for now, cookie code is disabled.
76 - Bryan 2004.10.03.
79 /* fwrite(buffer, sizeof(char), len, stderr); */
81 /* XXX - Is it safe to chunk our response? */
82 ResponseChunked(r);
84 ResponseStatus(r, 200);
86 if (http_cookie) {
87 /* There's an auth cookie, so pass it back in the response. */
89 char *cookie_response;
91 cookie_response = malloc(10+strlen(http_cookie));
92 sprintf(cookie_response, "auth=%s", http_cookie);
94 /* Return abyss response. */
95 ResponseAddField(r, "Set-Cookie", cookie_response);
97 free(cookie_response);
101 ResponseContentType(r, "text/xml; charset=\"utf-8\"");
102 ResponseContentLength(r, len);
104 ResponseWrite(r);
106 HTTPWrite(r, buffer, len);
107 HTTPWriteEnd(r);
112 /*=========================================================================
113 ** send_error
114 **=========================================================================
115 ** Send an error back to the client.
118 static void
119 send_error(TSession * const abyssSessionP,
120 unsigned int const status) {
122 ResponseStatus(abyssSessionP, (uint16) status);
123 ResponseError(abyssSessionP);
128 /*=========================================================================
129 ** get_buffer_data
130 **=========================================================================
131 ** Extract some data from the TConn's underlying input buffer. Do not
132 ** extract more than 'max'.
135 static void
136 get_buffer_data(TSession * const r,
137 int const max,
138 char ** const out_start,
139 int * const out_len) {
141 /* Point to the start of our data. */
142 *out_start = &r->conn->buffer[r->conn->bufferpos];
144 /* Decide how much data to retrieve. */
145 *out_len = r->conn->buffersize - r->conn->bufferpos;
146 if (*out_len > max)
147 *out_len = max;
149 /* Update our buffer position. */
150 r->conn->bufferpos += *out_len;
155 /*=========================================================================
156 ** get_body
157 **=========================================================================
158 ** Slurp the body of the request into an xmlrpc_mem_block.
161 static void
162 getBody(xmlrpc_env * const envP,
163 TSession * const abyssSessionP,
164 unsigned int const contentSize,
165 xmlrpc_mem_block ** const bodyP) {
166 /*----------------------------------------------------------------------------
167 Get the entire body from the Abyss session and return it as the new
168 memblock *bodyP.
170 The first chunk of the body may already be in Abyss's buffer. We
171 retrieve that before reading more.
172 -----------------------------------------------------------------------------*/
173 xmlrpc_mem_block * body;
175 body = xmlrpc_mem_block_new(envP, 0);
176 if (!envP->fault_occurred) {
177 unsigned int bytesRead;
178 char * chunkPtr;
179 int chunkLen;
181 bytesRead = 0;
183 while (!envP->fault_occurred && bytesRead < contentSize) {
184 get_buffer_data(abyssSessionP, contentSize - bytesRead,
185 &chunkPtr, &chunkLen);
186 bytesRead += chunkLen;
188 XMLRPC_TYPED_MEM_BLOCK_APPEND(char, envP, body,
189 chunkPtr, chunkLen);
191 if (bytesRead < contentSize) {
192 /* Get the next chunk of data from the connection into the
193 buffer
195 abyss_bool succeeded;
197 /* Reset our read buffer & flush data from previous reads. */
198 ConnReadInit(abyssSessionP->conn);
200 /* Read more network data into our buffer. If we encounter
201 a timeout, exit immediately. We're very forgiving about
202 the timeout here. We allow a full timeout per network
203 read, which would allow somebody to keep a connection
204 alive nearly indefinitely. But it's hard to do anything
205 intelligent here without very complicated code.
207 succeeded = ConnRead(abyssSessionP->conn,
208 abyssSessionP->server->timeout);
209 if (!succeeded)
210 xmlrpc_env_set_fault_formatted(
211 envP, XMLRPC_TIMEOUT_ERROR, "Timed out waiting for "
212 "client to send its POST data");
215 if (envP->fault_occurred)
216 xmlrpc_mem_block_free(body);
217 else
218 *bodyP = body;
224 static void
225 storeCookies(TSession * const httpRequestP,
226 unsigned int * const httpErrorP) {
227 /*----------------------------------------------------------------------------
228 Get the cookie settings from the HTTP headers and remember them for
229 use in responses.
230 -----------------------------------------------------------------------------*/
231 const char * const cookie = RequestHeaderValue(httpRequestP, "cookie");
232 if (cookie) {
234 Setting the value in an environment variable doesn't make
235 any sense. So for now, cookie code is disabled.
236 -Bryan 04.10.03.
238 setenv("HTTP_COOKIE", cookie, 1);
241 /* TODO: parse HTTP_COOKIE to find auth pair, if there is one */
243 *httpErrorP = 0;
249 static void
250 validateContentType(TSession * const httpRequestP,
251 unsigned int * const httpErrorP) {
252 /*----------------------------------------------------------------------------
253 If the client didn't specify a content-type of "text/xml", return
254 "400 Bad Request". We can't allow the client to default this header,
255 because some firewall software may rely on all XML-RPC requests
256 using the POST method and a content-type of "text/xml".
257 -----------------------------------------------------------------------------*/
258 const char * const content_type =
259 RequestHeaderValue(httpRequestP, "content-type");
260 if (content_type == NULL || strcmp(content_type, "text/xml") != 0)
261 *httpErrorP = 400;
262 else
263 *httpErrorP = 0;
268 static void
269 processContentLength(TSession * const httpRequestP,
270 unsigned int * const inputLenP,
271 unsigned int * const httpErrorP) {
272 /*----------------------------------------------------------------------------
273 Make sure the content length is present and non-zero. This is
274 technically required by XML-RPC, but we only enforce it because we
275 don't want to figure out how to safely handle HTTP < 1.1 requests
276 without it. If the length is missing, return "411 Length Required".
277 -----------------------------------------------------------------------------*/
278 const char * const content_length =
279 RequestHeaderValue(httpRequestP, "content-length");
280 if (content_length == NULL)
281 *httpErrorP = 411;
282 else {
283 int const contentLengthValue = atoi(content_length);
284 if (contentLengthValue <= 0)
285 *httpErrorP = 400;
286 else {
287 *httpErrorP = 0;
288 *inputLenP = (unsigned int)contentLengthValue;
294 /****************************************************************************
295 Abyss handlers (to be registered with and called by Abyss)
296 ****************************************************************************/
298 /* XXX - This variable is *not* currently threadsafe. Once the server has
299 ** been started, it must be treated as read-only. */
300 static xmlrpc_registry *global_registryP;
302 static const char * trace_abyss;
304 static void
305 processCall(TSession * const abyssSessionP,
306 int const inputLen) {
307 /*----------------------------------------------------------------------------
308 Handle an RPC request. This is an HTTP request that has the proper form
309 to be one of our RPCs.
310 -----------------------------------------------------------------------------*/
311 xmlrpc_env env;
313 if (trace_abyss)
314 fprintf(stderr, "xmlrpc_server_abyss RPC2 handler processing RPC.\n");
316 xmlrpc_env_init(&env);
318 /* SECURITY: Make sure our content length is legal.
319 XXX - We can cast 'inputLen' because we know it's >= 0, yes?
321 if ((size_t) inputLen > xmlrpc_limit_get(XMLRPC_XML_SIZE_LIMIT_ID))
322 xmlrpc_env_set_fault_formatted(
323 &env, XMLRPC_LIMIT_EXCEEDED_ERROR,
324 "XML-RPC request too large (%d bytes)", inputLen);
325 else {
326 xmlrpc_mem_block *body;
327 /* Read XML data off the wire. */
328 getBody(&env, abyssSessionP, inputLen, &body);
329 if (!env.fault_occurred) {
330 xmlrpc_mem_block * output;
331 /* Process the RPC. */
332 output = xmlrpc_registry_process_call(
333 &env, global_registryP, NULL,
334 XMLRPC_MEMBLOCK_CONTENTS(char, body),
335 XMLRPC_MEMBLOCK_SIZE(char, body));
336 if (!env.fault_occurred) {
337 /* Send our the result. */
338 send_xml_data(abyssSessionP,
339 XMLRPC_MEMBLOCK_CONTENTS(char, output),
340 XMLRPC_MEMBLOCK_SIZE(char, output));
342 XMLRPC_MEMBLOCK_FREE(char, output);
344 XMLRPC_MEMBLOCK_FREE(char, body);
347 if (env.fault_occurred) {
348 if (env.fault_code == XMLRPC_TIMEOUT_ERROR)
349 send_error(abyssSessionP, 408); /* 408 Request Timeout */
350 else
351 send_error(abyssSessionP, 500); /* 500 Internal Server Error */
354 xmlrpc_env_clean(&env);
359 /*=========================================================================
360 ** xmlrpc_server_abyss_rpc2_handler
361 **=========================================================================
362 ** This handler processes all requests to '/RPC2'. See the header for
363 ** more documentation.
366 xmlrpc_bool
367 xmlrpc_server_abyss_rpc2_handler (TSession * const r) {
369 xmlrpc_bool retval;
371 if (trace_abyss)
372 fprintf(stderr, "xmlrpc_server_abyss RPC2 handler called.\n");
374 /* We handle only requests to /RPC2, the default XML-RPC URL.
375 Everything else we pass through to other handlers.
377 if (strcmp(r->uri, "/RPC2") != 0)
378 retval = FALSE;
379 else {
380 retval = TRUE;
382 /* We understand only the POST HTTP method. For anything else, return
383 "405 Method Not Allowed".
385 if (r->method != m_post)
386 send_error(r, 405);
387 else {
388 unsigned int httpError;
389 storeCookies(r, &httpError);
390 if (httpError)
391 send_error(r, httpError);
392 else {
393 unsigned int httpError;
394 validateContentType(r, &httpError);
395 if (httpError)
396 send_error(r, httpError);
397 else {
398 unsigned int httpError;
399 int inputLen;
401 processContentLength(r, &inputLen, &httpError);
402 if (httpError)
403 send_error(r, httpError);
405 processCall(r, inputLen);
410 if (trace_abyss)
411 fprintf(stderr, "xmlrpc_server_abyss RPC2 handler returning.\n");
412 return retval;
417 /*=========================================================================
418 ** xmlrpc_server_abyss_default_handler
419 **=========================================================================
420 ** This handler returns a 404 Not Found for all requests. See the header
421 ** for more documentation.
424 xmlrpc_bool
425 xmlrpc_server_abyss_default_handler (TSession * const r) {
426 send_error(r, 404);
428 return TRUE;
433 /**************************************************************************
435 ** The code below was adapted from the main.c file of the Abyss webserver
436 ** project. In addition to the other copyrights on this file, the following
437 ** code is also under this copyright:
439 ** Copyright (C) 2000 by Moez Mahfoudh <mmoez@bigfoot.com>.
440 ** All rights reserved.
442 ** Redistribution and use in source and binary forms, with or without
443 ** modification, are permitted provided that the following conditions
444 ** are met:
445 ** 1. Redistributions of source code must retain the above copyright
446 ** notice, this list of conditions and the following disclaimer.
447 ** 2. Redistributions in binary form must reproduce the above copyright
448 ** notice, this list of conditions and the following disclaimer in the
449 ** documentation and/or other materials provided with the distribution.
450 ** 3. The name of the author may not be used to endorse or promote products
451 ** derived from this software without specific prior written permission.
453 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
454 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
455 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
456 ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
457 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
458 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
459 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
460 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
461 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
462 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
463 ** SUCH DAMAGE.
465 **************************************************************************/
467 #include <time.h>
468 #include <fcntl.h>
470 #ifdef _WIN32
471 #include <io.h>
472 #else
473 /* Must check this
474 #include <sys/io.h>
476 #endif /* _WIN32 */
478 #ifdef _UNIX
479 #include <sys/signal.h>
480 #include <sys/wait.h>
481 #include <grp.h>
482 #endif
485 #ifdef _UNIX
486 static void
487 sigterm(int const sig) {
488 TraceExit("Signal %d received. Exiting...\n",sig);
490 #endif
493 #ifdef _UNIX
494 static void
495 sigchld(int const sig ATTR_UNUSED) {
496 /*----------------------------------------------------------------------------
497 This is a signal handler for a SIGCHLD signal (which informs us that
498 one of our child processes has terminated).
500 We respond by reaping the zombie process.
502 Implementation note: In some systems, just setting the signal handler
503 to SIG_IGN (ignore signal) does this. In others, it doesn't.
504 -----------------------------------------------------------------------------*/
505 pid_t pid;
506 int status;
508 /* Reap defunct children until there aren't any more. */
509 for (;;) {
510 pid = waitpid( (pid_t) -1, &status, WNOHANG );
512 /* none left */
513 if (pid==0)
514 break;
516 if (pid<0) {
517 /* because of ptrace */
518 if (errno==EINTR)
519 continue;
521 break;
525 #endif /* _UNIX */
527 static TServer globalSrv;
528 /* When you use the old interface (xmlrpc_server_abyss_init(), etc.),
529 this is the Abyss server to which they refer. Obviously, there can be
530 only one Abyss server per program using this interface.
534 void
535 xmlrpc_server_abyss_init(int const flags ATTR_UNUSED,
536 const char * const config_file) {
538 DateInit();
539 MIMETypeInit();
541 ServerCreate(&globalSrv, "XmlRpcServer", 8080, DEFAULT_DOCS, NULL);
543 ConfReadServerFile(config_file, &globalSrv);
545 xmlrpc_server_abyss_init_registry();
546 /* Installs /RPC2 handler and default handler that use the
547 built-in registry.
550 ServerInit(&globalSrv);
555 static void
556 setupSignalHandlers(void) {
557 #ifdef _UNIX
558 struct sigaction mysigaction;
560 sigemptyset(&mysigaction.sa_mask);
561 mysigaction.sa_flags = 0;
563 /* These signals abort the program, with tracing */
564 mysigaction.sa_handler = sigterm;
565 sigaction(SIGTERM, &mysigaction, NULL);
566 sigaction(SIGINT, &mysigaction, NULL);
567 sigaction(SIGHUP, &mysigaction, NULL);
568 sigaction(SIGUSR1, &mysigaction, NULL);
570 /* This signal indicates connection closed in the middle */
571 mysigaction.sa_handler = SIG_IGN;
572 sigaction(SIGPIPE, &mysigaction, NULL);
574 /* This signal indicates a child process (request handler) has died */
575 mysigaction.sa_handler = sigchld;
576 sigaction(SIGCHLD, &mysigaction, NULL);
577 #endif
582 static void
583 runServer(TServer * const srvP,
584 runfirstFn const runfirst,
585 void * const runfirstArg) {
587 setupSignalHandlers();
589 #ifdef _UNIX
590 /* Become a daemon */
591 switch (fork()) {
592 case 0:
593 break;
594 case -1:
595 TraceExit("Unable to become a daemon");
596 default:
597 exit(0);
600 setsid();
602 /* Change the current user if we are root */
603 if (getuid()==0) {
604 if (srvP->uid == (uid_t)-1)
605 TraceExit("Can't run under root privileges. "
606 "Please add a User option in your "
607 "Abyss configuration file.");
609 #ifdef HAVE_SETGROUPS
610 if (setgroups(0,NULL)==(-1))
611 TraceExit("Failed to setup the group.");
612 if (srvP->gid != (gid_t)-1)
613 if (setgid(srvP->gid)==(-1))
614 TraceExit("Failed to change the group.");
615 #endif
617 if (setuid(srvP->uid) == -1)
618 TraceExit("Failed to change the user.");
621 if (srvP->pidfile!=(-1)) {
622 char z[16];
624 sprintf(z,"%d",getpid());
625 FileWrite(&srvP->pidfile,z,strlen(z));
626 FileClose(&srvP->pidfile);
628 #endif
630 /* We run the user supplied runfirst after forking, but before accepting
631 connections (helpful when running with threads)
633 if (runfirst)
634 runfirst(runfirstArg);
636 ServerRun(srvP);
638 /* We can't exist here because ServerRun doesn't return */
639 XMLRPC_ASSERT(FALSE);
644 void
645 xmlrpc_server_abyss_run_first(runfirstFn const runfirst,
646 void * const runfirstArg) {
648 runServer(&globalSrv, runfirst, runfirstArg);
653 void
654 xmlrpc_server_abyss_run(void) {
655 runServer(&globalSrv, NULL, NULL);
660 void
661 xmlrpc_server_abyss_set_handlers(TServer * const srvP,
662 xmlrpc_registry * const registryP) {
664 /* Abyss ought to have a way to register with a handler an argument
665 that gets passed to the handler every time it is called. That's
666 where we should put the registry handle. But we don't find such
667 a thing in Abyss, so we use the global variable 'global_registryP'.
669 global_registryP = registryP;
671 trace_abyss = getenv("XMLRPC_TRACE_ABYSS");
673 ServerAddHandler(srvP, xmlrpc_server_abyss_rpc2_handler);
674 ServerDefaultHandler(srvP, xmlrpc_server_abyss_default_handler);
679 void
680 xmlrpc_server_abyss(xmlrpc_env * const envP,
681 const xmlrpc_server_abyss_parms * const parmsP,
682 unsigned int const parm_size) {
684 XMLRPC_ASSERT_ENV_OK(envP);
686 if (parm_size < XMLRPC_APSIZE(registryP))
687 xmlrpc_env_set_fault_formatted(
688 envP, XMLRPC_INTERNAL_ERROR,
689 "You must specify members at least up through "
690 "'registryP' in the server parameters argument. "
691 "That would mean the parameter size would be >= %u "
692 "but you specified a size of %u",
693 XMLRPC_APSIZE(registryP), parm_size);
694 else {
695 TServer srv;
696 runfirstFn runfirst;
697 void * runfirstArg;
699 DateInit();
700 MIMETypeInit();
702 ServerCreate(&srv, "XmlRpcServer", 8080, DEFAULT_DOCS, NULL);
704 ConfReadServerFile(parmsP->config_file_name, &srv);
706 xmlrpc_server_abyss_set_handlers(&srv, parmsP->registryP);
708 ServerInit(&srv);
710 if (parm_size >= XMLRPC_APSIZE(runfirst_arg)) {
711 runfirst = parmsP->runfirst;
712 runfirstArg = parmsP->runfirst_arg;
713 } else {
714 runfirst = NULL;
715 runfirstArg = NULL;
717 runServer(&srv, runfirst, runfirstArg);
723 /*=========================================================================
724 ** XML-RPC Server Method Registry
725 **=========================================================================
726 ** A simple front-end to our method registry.
729 /* XXX - This variable is *not* currently threadsafe. Once the server has
730 ** been started, it must be treated as read-only. */
731 static xmlrpc_registry *builtin_registryP;
733 void
734 xmlrpc_server_abyss_init_registry(void) {
736 /* This used to just create the registry and Caller would be
737 responsible for adding the handlers that use it.
739 But that isn't very modular -- the handlers and registry go
740 together; there's no sense in using the built-in registry and
741 not the built-in handlers because if you're custom building
742 something, you can just make your own regular registry. So now
743 we tie them together, and we don't export our handlers.
745 xmlrpc_env env;
747 xmlrpc_env_init(&env);
748 builtin_registryP = xmlrpc_registry_new(&env);
749 die_if_fault_occurred(&env);
750 xmlrpc_env_clean(&env);
752 xmlrpc_server_abyss_set_handlers(&globalSrv, builtin_registryP);
757 xmlrpc_registry *
758 xmlrpc_server_abyss_registry(void) {
760 /* This is highly deprecated. If you want to mess with a registry,
761 make your own with xmlrpc_registry_new() -- don't mess with the
762 internal one.
764 return builtin_registryP;
769 /* A quick & easy shorthand for adding a method. */
770 void
771 xmlrpc_server_abyss_add_method (char * const method_name,
772 xmlrpc_method const method,
773 void * const user_data) {
774 xmlrpc_env env;
776 xmlrpc_env_init(&env);
777 xmlrpc_registry_add_method(&env, builtin_registryP, NULL, method_name,
778 method, user_data);
779 die_if_fault_occurred(&env);
780 xmlrpc_env_clean(&env);
785 void
786 xmlrpc_server_abyss_add_method_w_doc (char * const method_name,
787 xmlrpc_method const method,
788 void * const user_data,
789 char * const signature,
790 char * const help) {
792 xmlrpc_env env;
793 xmlrpc_env_init(&env);
794 xmlrpc_registry_add_method_w_doc(
795 &env, builtin_registryP, NULL, method_name,
796 method, user_data, signature, help);
797 die_if_fault_occurred(&env);
798 xmlrpc_env_clean(&env);