1/*
2mod_ecs - Embedded ClearSilver CGI Apache Module
3
4mod_ecs is a heavily modified version of mod_ecgi from:
5http://www.webthing.com/software/mod_ecgi.html
6
7This version is designed to run with the ClearSilver CGIKit, specifically
8with the cgi_wrap calls from that kit.  Those calls wrap the standard CGI
9access methods, namely environment variables and stdin/stdout, allowing
10those calls to be replaced easily.  mod_ecs provides replacement calls which
11interface directly with the Apache internals.
12
13Additionally, mod_ecs is designed to dlopen() the shared library CGI once,
14and keep it in memory, making the CGI almost identical in performance to a
15regular Apache module.  The fact that your CGI will be called multiple times
16is the biggest difference you can expect from a standard ClearSilver based CGI.
17This means your code must be clean!
18
19ECS - Embedded ClearSilver
20
21Platform: UNIX only.  Anyone who wants to is welcome to port it elsewhere.
22
23=======================================================
24To COMPILE Apache with embedded CGI support, use
25	-ldl in EXTRA_LIBS
26	possibly -rdynamic in EXTRA_LFLAGS
27 I took this out of the config because its not there on freebsd4
28 = ConfigStart
29	LIBS="$LIBS -ldl"
30 = ConfigEnd
31(or as required by your platform)
32
33OK, here's for APACI:
34 * MODULE-DEFINITION-START
35 * Name: ecs_module
36 * MODULE-DEFINITION-END
37
38=======================================================
39
40=======================================================
41BUGS
42Lots - here are some obvious ones
43	- won't work with NPH
44	- No mechanism is provided for running from an SSI
45	- Can't take part in content-negotiation
46	- No graceful cleanup if a CGI program crashes (though it's OK
47	  if the CGI fails but returns).
48	- Suspected memory leak inherited from Apache (which ignores it
49	  because it happens just before exit there).
50
51*/
52
53#include <dlfcn.h>
54#include "mod_ecs.h"
55
56#include "httpd.h"
57#include "http_config.h"
58#include "http_request.h"
59#include "http_core.h"
60#include "http_protocol.h"
61#include "http_main.h"
62#include "http_log.h"
63#include "util_script.h"
64#include "http_conf_globals.h"
65
66module ecs_module;
67
68/* Configuration stuff */
69
70#define log_reason(reason,name,r) ap_log_error(APLOG_MARK,APLOG_ERR,(r)->server,(reason),(name))
71#define log_scripterror(r,conf,ret,error) (log_reason((error),(r)->filename,(r)),ret)
72
73char** ecs_create_argv(pool*,char*,char*,char*,char*,const char*);
74
75/****************************************************************
76 *
77 * Actual CGI handling...
78 */
79const int ERROR = 500;
80const int INTERNAL_REDIRECT = 3020;
81
82#undef ECS_DEBUG
83
84/******************************************************************
85 * cgiwrap routines
86 *   We've replaced all the normal CGI api calls with calls to the
87 *   appropriate cgiwrap routines instead.  Then, we provide versions of
88 *   the cgiwrap callback here that interface directly with apache.  We
89 *   need to mimic a bunch of the stuff that apache does in mod_cgi in
90 *   order to implement the output portion of the CGI spec.
91 */
92typedef struct header_buf {
93  char *buf;
94  int len;
95  int max;
96  int loc;
97  int nonl;
98} HEADER_BUF;
99
100typedef struct wrap_data {
101  HEADER_BUF hbuf;
102  int end_of_header;
103  int returns;
104  request_rec *r;
105} WRAPPER_DATA;
106
107static int buf_getline (const char *idata, int ilen, char *odata, int olen, int *nonl)
108{
109  char *eol;
110  int len;
111
112  *nonl = 1;
113  eol = strchr (idata, '\n');
114  if (eol == NULL)
115  {
116    len = ilen;
117  }
118  else
119  {
120    *nonl = 0;
121    len = eol - idata + 1;
122  }
123  if (len > olen) len = olen;
124  memcpy (odata, idata, len);
125  odata[len] = '\0';
126  return len;
127}
128
129static int h_getline (char *buf, int len, void *h)
130{
131  HEADER_BUF *hbuf = (HEADER_BUF *)h;
132  int ret;
133
134  buf[0] = '\0';
135  if (hbuf->loc > hbuf->len)
136    return 0;
137
138  ret = buf_getline (hbuf->buf + hbuf->loc, hbuf->len - hbuf->loc, buf, len, &(hbuf->nonl));
139  hbuf->loc += ret;
140#if ECS_DEBUG>1
141  fprintf (stderr, "h_getline: [%d] %s\n", ret, buf);
142#endif
143  return ret;
144}
145
146static int header_write (HEADER_BUF *hbuf, const char *data, int dlen)
147{
148  char buf[1024];
149  int done, len;
150  int nonl = hbuf->nonl;
151
152  done = 0;
153  while (done < dlen)
154  {
155    nonl = hbuf->nonl;
156    len = buf_getline (data + done, dlen - done, buf, sizeof(buf), &(hbuf->nonl));
157    if (len == 0)
158      break;
159    done += len;
160    if (hbuf->len + len > hbuf->max)
161    {
162      hbuf->max *= 2;
163      if (hbuf->len + len > hbuf->max)
164      {
165	hbuf->max += len + 1;
166      }
167      hbuf->buf = (char *) realloc ((void *)(hbuf->buf), hbuf->max);
168    }
169    memcpy (hbuf->buf + hbuf->len, buf, len);
170    hbuf->len += len;
171    if (!nonl && (buf[0] == '\n' || buf[0] == '\r'))
172    {
173      /* end of headers */
174      return done;
175    }
176  }
177
178  return 0;
179}
180
181/* The normal CGI module passes the returned data through
182 * ap_scan_script_header().  We can't do that directly, since we don't
183 * have a constant stream of data, so we buffer the header into our own
184 * structure, and call ap_scan_script_header_err_core() with our own
185 * getline() function to walk the header buffer we have.  We could
186 * probably get some speed improvement by keeping the header buffer
187 * between runs, instead of growing it every time... for later.  Also,
188 * we currently don't use the pool allocation routines here, so we have
189 * to be very careful not to leak.  We could probably at least use the
190 * ap_register_cleanup() function to make sure we clean up our mess...
191 */
192static int wrap_write (void *data, const char *buf, size_t len)
193{
194  WRAPPER_DATA *wrap = (WRAPPER_DATA *)data;
195  int wl;
196  int ret;
197
198#if ECS_DEBUG>1
199  fprintf (stderr, "wrap_write (%s, %d)\n", buf, len);
200#endif
201  if (!wrap->end_of_header)
202  {
203    wl = header_write (&(wrap->hbuf), buf, len);
204    if (wl == 0)
205    {
206      return len;
207    }
208    wrap->end_of_header = 1;
209    wrap->hbuf.loc = 0;
210#if ECS_DEBUG>1
211    fprintf (stderr, "ap_scan_script_header_err_core\n%s\n", wrap->hbuf.buf);
212#endif
213    wrap->returns = ap_scan_script_header_err_core(wrap->r, NULL, h_getline,
214	(void *)&(wrap->hbuf));
215#if ECS_DEBUG>1
216    fprintf (stderr, "ap_scan_script_header_err_core.. done\n");
217#endif
218    if (len >= wl)
219    {
220      len = len - wl;
221      buf = buf + wl;
222    }
223
224    if (wrap->returns == OK)
225    {
226      const char* location = ap_table_get (wrap->r->headers_out, "Location");
227
228      if (location && location[0] == '/' && wrap->r->status == 200)
229      {
230	wrap->returns = INTERNAL_REDIRECT;
231      }
232      else if (location && wrap->r->status == 200)
233      {
234	/* XX Note that if a script wants to produce its own Redirect
235	 * body, it now has to explicitly *say* "Status: 302"
236	 */
237	wrap->returns = REDIRECT;
238      }
239      else
240      {
241#ifdef ECS_DEBUG
242	fprintf (stderr, "ap_send_http_header\n");
243#endif
244	ap_send_http_header(wrap->r);
245#ifdef ECS_DEBUG
246	fprintf (stderr, "ap_send_http_header.. done\n");
247#endif
248      }
249    }
250  }
251  /* if header didn't return OK, ignore the rest */
252  if ((wrap->returns != OK) || wrap->r->header_only)
253  {
254    return len;
255  }
256#if ECS_DEBUG>1
257  fprintf (stderr, "ap_rwrite(%s,%d)\n", buf, len);
258#endif
259  ret = ap_rwrite (buf, len, wrap->r);
260#if ECS_DEBUG>1
261  fprintf (stderr, "ap_rwrite.. done\n");
262#endif
263  return ret;
264}
265
266int wrap_vprintf (void *data, const char *fmt, va_list ap)
267{
268  char buf[4096];
269  int len;
270
271  len = ap_vsnprintf (buf, sizeof(buf), fmt, ap);
272  return wrap_write (data, buf, len);
273}
274
275static int wrap_read (void *data, char *buf, size_t len)
276{
277  WRAPPER_DATA *wrap = (WRAPPER_DATA *)data;
278  int ret;
279  int x = 0;
280
281#if ECS_DEBUG>1
282  fprintf (stderr, "wrap_read (%s, %d)\n", buf, len);
283#endif
284  do
285  {
286    ret = ap_get_client_block(wrap->r, buf + x, len - x);
287    if (ret <= 0) break;
288    x += ret;
289  } while (x < len);
290#if ECS_DEBUG>1
291  fprintf (stderr, "done ap_get_client_block\n");
292#endif
293  if (ret < 0) return ret;
294  return x;
295}
296
297static char *wrap_getenv (void *data, const char *s)
298{
299  WRAPPER_DATA *wrap = (WRAPPER_DATA *)data;
300  char *v;
301
302  v = (char *) ap_table_get (wrap->r->subprocess_env, s);
303  if (v) return strdup(v);
304  return NULL;
305}
306
307static int wrap_putenv (void *data, const char *k, const char *v)
308{
309  WRAPPER_DATA *wrap = (WRAPPER_DATA *)data;
310
311  ap_table_set (wrap->r->subprocess_env, k, v);
312
313  return 0;
314}
315
316static char *wrap_iterenv (void *data, int x, char **k, char **v)
317{
318  WRAPPER_DATA *wrap = (WRAPPER_DATA *)data;
319  array_header *env = ap_table_elts(wrap->r->subprocess_env);
320  table_entry *entry = (table_entry*)env->elts;
321
322  if (x >= env->nelts) return 0;
323
324  if (entry[x].key == NULL || entry[x].val == NULL)
325    return 0;
326
327  *k = strdup(entry[x].key);
328  *v = strdup(entry[x].val);
329
330  return 0;
331}
332
333/*************************************************************************
334 * Actual mod_ecs data structures for configuration
335 */
336
337typedef void (*InitFunc)();
338typedef void (*CleanupFunc)();
339typedef int (*CGIMainFunc)(int,char**,char**);
340typedef int (*WrapInitFunc)(void *,void *,void*,void*,void*,void*,void*);
341
342typedef struct {
343  const char *libpath;
344  ap_os_dso_handle_t dlib;
345} ecs_deplibs;
346
347typedef struct {
348  const char *libpath;
349  ap_os_dso_handle_t dlib;
350  WrapInitFunc wrap_init;
351  CGIMainFunc start;
352  time_t mtime;
353  int loaded;
354} ecs_manager;
355
356typedef struct {
357  array_header *deplibs;
358  array_header *handlers;
359  int fork_enabled;
360  int reload_enabled;
361} ecs_server_conf;
362
363const char *ECSInit = "ECSInit";
364const char *ECSCleanUp = "ECSCleanup";
365const char *WrapInit = "cgiwrap_init_emu";
366const char *CGIMain = "main";
367
368static void dummy (ap_os_dso_handle_t dlhandle)
369{
370}
371
372static void slib_cleanup (ap_os_dso_handle_t dlhandle)
373{
374  CleanupFunc cleanupFunc;
375  if ((cleanupFunc = (CleanupFunc)ap_os_dso_sym(dlhandle, ECSCleanUp))) {
376    (*cleanupFunc)();
377  }
378  ap_os_dso_unload(dlhandle);
379#ifdef ECS_DEBUG
380  fprintf(stderr, "Unloading handle %d", dlhandle);
381#endif
382}
383
384void *create_ecs_config (pool *p, server_rec *dummy)
385{
386  ecs_server_conf *new = ap_palloc (p, sizeof(ecs_server_conf));
387  new->deplibs = ap_make_array(p,1,sizeof(ecs_deplibs));
388  new->handlers = ap_make_array(p,1,sizeof(ecs_manager));
389  new->fork_enabled = 0;
390  new->reload_enabled = 0;
391  return (void *) new;
392}
393
394char** e_setup_cgi_env (request_rec* r)
395{
396  char** env;
397
398  ap_add_common_vars(r);
399  ap_add_cgi_vars(r);
400  env = ap_create_environment(r->pool,r->subprocess_env);
401
402  return env;
403}
404
405const char *set_dep_lib (cmd_parms *parms, void *dummy, char *arg)
406{
407  ecs_server_conf *cls = ap_get_module_config (parms->server->module_config,
408      &ecs_module);
409  ecs_deplibs *entry;
410  ap_os_dso_handle_t dlhandle;
411  InitFunc init_func;
412
413  if ((dlhandle = ap_os_dso_load(arg)) == NULL) {
414    return ap_os_dso_error();
415  }
416
417  if ((init_func = (InitFunc)ap_os_dso_sym(dlhandle, ECSInit))) {
418    (*init_func)();
419  }
420
421  ap_register_cleanup (cls->deplibs->pool, dlhandle, slib_cleanup, slib_cleanup);
422
423  entry = (ecs_deplibs*)ap_push_array(cls->deplibs);
424  entry->libpath = ap_pstrdup(cls->deplibs->pool, arg);
425  entry->dlib = dlhandle;
426
427  return NULL;
428}
429
430/* Load an ecs shared library */
431static const char *load_library (ap_pool *p, ecs_manager *entry, int do_stat, char *prefix)
432{
433  ap_os_dso_handle_t dlhandle;
434  InitFunc init_func;
435  CGIMainFunc cgi_main;
436  WrapInitFunc wrap_init;
437  char *err;
438  struct stat s;
439
440  if (do_stat)
441  {
442    if (stat(entry->libpath, &s) == -1)
443    {
444      err = ap_psprintf (p, "Failed to stat library file %s: %d", entry->libpath, errno);
445      return err;
446    }
447    entry->mtime = s.st_mtime;
448  }
449
450  if (entry->loaded == 1)
451  {
452    fprintf (stderr, "Warning: attempting to reload %s but it's already loaded\n", entry->libpath);
453  }
454
455  /* This does a RTLD_NOW, if we want lazy, we're going to have to do it
456   * ourselves */
457  if ((dlhandle = ap_os_dso_load(entry->libpath)) == NULL) {
458    return ap_os_dso_error();
459  }
460
461  if (entry->dlib == dlhandle)
462  {
463    fprintf (stderr, "Warning: Reload of %s returned same handle\n", entry->libpath);
464  }
465
466  if ((init_func = (InitFunc)ap_os_dso_sym(dlhandle, ECSInit))) {
467    (*init_func)();
468  }
469  if (!(wrap_init = (WrapInitFunc)ap_os_dso_sym(dlhandle, WrapInit))) {
470    err = ap_psprintf (p, "Failed to find wrap init function %s in shared object: %s", WrapInit, dlerror());
471    ap_os_dso_unload(dlhandle);
472    return err;
473  }
474  if (!(cgi_main = (CGIMainFunc)ap_os_dso_sym(dlhandle, CGIMain))) {
475    err = ap_psprintf (p, "Failed to find entry function %s in shared object: %s", CGIMain, dlerror());
476    ap_os_dso_unload(dlhandle);
477    return err;
478  }
479
480  /* Um, this may be a problem... */
481  ap_register_cleanup (p, dlhandle, slib_cleanup, dummy);
482
483  entry->dlib = dlhandle;
484  entry->wrap_init = wrap_init;
485  entry->start = cgi_main;
486  entry->loaded = 1;
487
488  fprintf (stderr, "%sLoaded library %s [%d]\n", prefix, entry->libpath, dlhandle);
489
490  return NULL;
491}
492
493const char *set_pre_lib (cmd_parms *parms, void *dummy, char *arg)
494{
495  ecs_server_conf *cls = ap_get_module_config (parms->server->module_config,
496      &ecs_module);
497  ecs_manager *entry;
498
499  entry = (ecs_manager*)ap_push_array(cls->handlers);
500  entry->libpath = ap_pstrdup(cls->handlers->pool, arg);
501
502  return load_library (cls->handlers->pool, entry, 1, "Pre");
503}
504
505const char *set_fork (cmd_parms *parms, void *dummy, int flag)
506{
507  ecs_server_conf *cls = ap_get_module_config (parms->server->module_config,
508      &ecs_module);
509
510  cls->fork_enabled = (flag ? 1 : 0);
511
512  return NULL;
513}
514
515const char *set_reload (cmd_parms *parms, void *dummy, int flag)
516{
517  ecs_server_conf *cls = ap_get_module_config (parms->server->module_config,
518      &ecs_module);
519
520  cls->reload_enabled = (flag ? 1 : 0);
521
522  return NULL;
523}
524
525static ecs_manager *findHandler(array_header *a, char *file)
526{
527  ecs_manager *list = (ecs_manager*)(a->elts);
528  int i;
529
530  for (i = 0; i < a->nelts; i++)
531  {
532    if (!strcmp(list[i].libpath, file))
533      return &(list[i]);
534  }
535  return NULL;
536}
537
538static int run_dl_cgi (ecs_server_conf *sconf, request_rec* r, char* argv0)
539{
540  int ret = 0;
541  void* handle;
542  int cgi_status;
543  int argc;
544  char** argv;
545  WRAPPER_DATA *wdata;
546  ecs_manager *handler;
547  const char *err;
548
549  char** envp = e_setup_cgi_env(r);
550
551  /* Find/open library */
552  handler = findHandler (sconf->handlers, r->filename);
553  if (handler == NULL)
554  {
555    ecs_manager my_handler;
556    my_handler.libpath = ap_pstrdup(sconf->handlers->pool, r->filename);
557    err = load_library(sconf->handlers->pool, &my_handler, 1, "");
558    if (err != NULL)
559    {
560      log_reason("Error opening library:", err, r);
561      ret = ERROR;
562    }
563    else
564    {
565      handler = (ecs_manager*)ap_push_array(sconf->handlers);
566      handler->dlib = my_handler.dlib;
567      handler->wrap_init = my_handler.wrap_init;
568      handler->start = my_handler.start;
569      handler->mtime = my_handler.mtime;
570      handler->loaded = my_handler.loaded;
571      handler->libpath = my_handler.libpath;
572    }
573  }
574  else if (sconf->reload_enabled)
575  {
576    struct stat s;
577    if (stat(handler->libpath, &s) == -1)
578    {
579      log_reason("Unable to stat file: ", handler->libpath, r);
580      ret = ERROR;
581    }
582    else if (!handler->loaded || (s.st_mtime > handler->mtime))
583    {
584      if (handler->loaded)
585      {
586	int x;
587	fprintf (stderr, "Unloading %s\n", handler->libpath);
588	slib_cleanup(handler->dlib);
589	/* Really unload this thing */
590	while ((x < 100) && (dlclose(handler->dlib) != -1)) x++;
591	if (x == 100)
592	  fprintf (stderr, "dlclose() never returned -1");
593	handler->loaded = 0;
594      }
595      err = load_library(sconf->handlers->pool, handler, 0, "Re");
596      if (err != NULL)
597      {
598	log_reason("Error opening library:", err, r);
599	ret = ERROR;
600      }
601      handler->mtime = s.st_mtime;
602    }
603  }
604
605  if (!ret) {
606    if ((!r->args) || (!r->args[0]) || (ap_ind(r->args,'=') >= 0) )
607    {
608      argc = 1;
609      argv = &argv0;
610    } else {
611      argv = ecs_create_argv(r->pool, NULL,NULL,NULL,argv0,r->args);
612      for (argc = 0 ; argv[argc] ; ++argc);
613    }
614  }
615
616  /*  Yow ... at last we can go ...
617
618      Now, what to do if CGI crashes (aaargh)
619      Methinks an atexit ... cleanup perhaps; have to figgerout
620      what the atexit needs to invoke ... yuk!
621
622      Or maybe better to catch SIGSEGV and SIGBUS ?
623      - we don't want coredumps from someone else's bugs, do we?
624      still doesn't guarantee anything very good :-(
625
626      Ugh .. nothing better???
627   */
628  if (!ret)
629  {
630    wdata = (WRAPPER_DATA *) ap_pcalloc (r->pool, sizeof (WRAPPER_DATA));
631    /* We use malloc here because there is no pool alloc command for
632     * realloc... */
633    wdata->hbuf.buf = (char *) malloc (sizeof(char) * 1024);
634    wdata->hbuf.max = 1024;
635    wdata->r = r;
636
637#ifdef ECS_DEBUG
638    fprintf (stderr, "wrap_init()\n");
639#endif
640    handler->wrap_init(wdata, wrap_read, wrap_vprintf, wrap_write, wrap_getenv, wrap_putenv, wrap_iterenv);
641
642#ifdef ECS_DEBUG
643    fprintf (stderr, "cgi_main()\n");
644#endif
645    cgi_status = handler->start(argc,argv,envp);
646    if (cgi_status != 0)
647    {
648      /*log_reason("CGI returned error status", cgi_status, r) ;*/
649      ret = ERROR;
650    }
651
652    if (wdata->returns != OK)
653      ret = wdata->returns;
654
655    free (wdata->hbuf.buf);
656  }
657
658  return ret;
659}
660
661int run_xcgi (ecs_server_conf *conf, request_rec* r, char* argv0)
662{
663  int len_read;
664  char argsbuffer[HUGE_STRING_LEN];
665  int ret = 0;
666
667  ret = run_dl_cgi (conf, r, argv0);
668
669  if (ret == INTERNAL_REDIRECT)
670  {
671    const char* location = ap_table_get (r->headers_out, "Location");
672
673    /* This redirect needs to be a GET no matter what the original
674     * method was.
675     */
676    r->method = ap_pstrdup(r->pool, "GET");
677    r->method_number = M_GET;
678
679    /* We already read the message body (if any), so don't allow
680     * the redirected request to think it has one.  We can ignore
681     * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
682     */
683    ap_table_unset(r->headers_in, "Content-Length");
684
685    ap_internal_redirect_handler (location, r);
686    return OK;
687  }
688
689  return ret;
690}
691
692int ecs_handler (request_rec* r)
693{
694  int retval;
695  char *argv0;
696  int is_included = !strcmp (r->protocol, "INCLUDED");
697  void *sconf = r->server->module_config;
698  ecs_server_conf *conf =
699    (ecs_server_conf *)ap_get_module_config(sconf, &ecs_module);
700
701  ap_error_log2stderr(r->server);
702#ifdef ECS_DEBUG
703  fprintf(stderr, "running ecs_handler %s\n", r->filename);
704#endif
705
706  if((argv0 = strrchr(r->filename,'/')) != NULL)
707    argv0++;
708  else argv0 = r->filename;
709
710  if (!(ap_allow_options (r) & OPT_EXECCGI) )
711    return log_scripterror(r, conf, FORBIDDEN,
712	"Options ExecCGI is off in this directory");
713
714  if (S_ISDIR(r->finfo.st_mode))
715    return log_scripterror(r, conf, FORBIDDEN,
716	"attempt to invoke directory as script");
717  if (r->finfo.st_mode == 0)
718    return log_scripterror(r, conf, NOT_FOUND,
719	"file not found or unable to stat");
720
721#ifdef ECS_DEBUG
722  fprintf (stderr, "ap_setup_client_block\n");
723#endif
724  if ((retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)))
725    return retval;
726
727#ifdef ECS_DEBUG
728  fprintf (stderr, "before run\n");
729#endif
730  return run_xcgi(conf, r, argv0);
731}
732
733handler_rec ecs_handlers[] = {
734  { ECS_MAGIC_TYPE, ecs_handler },
735  { "ecs-cgi", ecs_handler},
736  { NULL }
737};
738
739command_rec ecs_cmds[] = {
740 { "ECSFork", set_fork, NULL, OR_FILEINFO, FLAG,
741   "On or off to enable or disable (default) forking before calling cgi_main" },
742 { "ECSReload", set_reload, NULL, OR_FILEINFO, FLAG,
743   "On or off to enable or disable (default) checking if the shared library\n" \
744   "  has changed and reloading it if it has"},
745 { "ECSDepLib", set_dep_lib, NULL, RSRC_CONF, TAKE1,
746   "The location of a dependent lib to dlopen during init"},
747 { "ECSPreload", set_pre_lib, NULL, RSRC_CONF, TAKE1,
748   "The location of a shared lib handler to preload during init"},
749 { NULL }
750};
751
752module ecs_module = {
753   STANDARD_MODULE_STUFF,
754   NULL,			/* initializer */
755   NULL,			/* dir config creater */
756   NULL,			/* dir merger --- default is to override */
757   create_ecs_config,		/* server config */
758   NULL, /*merge_ecs_config,*/	       	/* merge server config */
759   ecs_cmds,			/* command table */
760   ecs_handlers,		/* handlers */
761   NULL,			/* filename translation */
762   NULL,			/* check_user_id */
763   NULL,			/* check auth */
764   NULL,			/* check access */
765   NULL,			/* type_checker */
766   NULL,			/* fixups */
767   NULL,			/* logger */
768#if MODULE_MAGIC_NUMBER >= 19970103
769   NULL,       			/* [3] header parser */
770#endif
771#if MODULE_MAGIC_NUMBER >= 19970719
772   NULL,       			/* process initializer */
773#endif
774#if MODULE_MAGIC_NUMBER >= 19970728
775   NULL,       			/* process exit/cleanup */
776#endif
777#if MODULE_MAGIC_NUMBER >= 19970902
778   NULL,       			/* [1] post read_request handling */
779#endif
780};
781
782
783/* Here's some stuff that essentially duplicates util_script.c
784   This really should be merged, but if _I_ do that it'll break
785   modularity and leave users with a nasty versioning problem.
786
787   If I get a round tuit sometime, I might ask the Apache folks
788   about integrating some changes in the main source tree.
789*/
790/* If a request includes query info in the URL (stuff after "?"), and
791 * the query info does not contain "=" (indicative of a FORM submission),
792 * then this routine is called to create the argument list to be passed
793 * to the CGI script.  When suexec is enabled, the suexec path, user, and
794 * group are the first three arguments to be passed; if not, all three
795 * must be NULL.  The query info is split into separate arguments, where
796 * "+" is the separator between keyword arguments.
797 */
798char **ecs_create_argv(pool *p, char *path, char *user, char *group,
799                          char *av0, const char *args)
800{
801    int x, numwords;
802    char **av;
803    char *w;
804    int idx = 0;
805
806    /* count the number of keywords */
807
808    for (x = 0, numwords = 1; args[x]; x++)
809        if (args[x] == '+') ++numwords;
810
811    if (numwords > APACHE_ARG_MAX - 5) {
812        numwords = APACHE_ARG_MAX - 5; /* Truncate args to prevent overrun */
813    }
814    av = (char **)ap_palloc(p, (numwords + 5) * sizeof(char *));
815
816    if (path)
817        av[idx++] = path;
818    if (user)
819        av[idx++] = user;
820    if (group)
821        av[idx++] = group;
822
823    av[idx++] = av0;
824
825    for (x = 1; x <= numwords; x++) {
826        w = ap_getword_nulls(p, &args, '+');
827        ap_unescape_url(w);
828        av[idx++] = ap_escape_shell_cmd(p, w);
829    }
830    av[idx] = NULL;
831    return av;
832}
833