/[hydra]/hydra/src/cgi.c
ViewVC logotype

Annotation of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.24 - (hide annotations)
Sun Oct 27 10:46:19 2002 UTC (21 years, 5 months ago) by nmav
Branch: MAIN
CVS Tags: hydra_0_0_10, hydra_0_0_9, hydra_0_1_1, hydra_0_1_0
Branch point for: hydra_0_1_0_patches
Changes since 1.23: +3 -3 lines
File MIME type: text/plain
Added the ability to disable HIC threads by using 0 in the
HICThreads configuration directive.
Hydra now uses poll by default. Select is available using the --with-select
configure option.

1 nmav 1.1 /*
2 nmav 1.14 * Hydra, an http server
3 nmav 1.1 * Copyright (C) 1995 Paul Phillips <paulp@go2net.com>
4     * Some changes Copyright (C) 1996,97 Larry Doolittle <ldoolitt@boa.org>
5     * Some changes Copyright (C) 1996 Charles F. Randall <crandall@goldsys.com>
6     * Some changes Copyright (C) 1997-2002 Jon Nelson <jnelson@boa.org>
7     *
8     * This program is free software; you can redistribute it and/or modify
9     * it under the terms of the GNU General Public License as published by
10     * the Free Software Foundation; either version 1, or (at your option)
11     * any later version.
12     *
13     * This program is distributed in the hope that it will be useful,
14     * but WITHOUT ANY WARRANTY; without even the implied warranty of
15     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16     * GNU General Public License for more details.
17     *
18     * You should have received a copy of the GNU General Public License
19     * along with this program; if not, write to the Free Software
20     * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21     *
22     */
23    
24 nmav 1.24 /* $Id: cgi.c,v 1.23 2002/10/26 20:58:48 nmav Exp $ */
25 nmav 1.1
26     #include "boa.h"
27    
28     static char *env_gen_extra(const char *key, const char *value, int extra);
29    
30     int verbose_cgi_logs = 0;
31     /* The +1 is for the the NULL in complete_env */
32     static char *common_cgi_env[COMMON_CGI_COUNT + 1];
33    
34     /*
35     * Name: create_common_env
36     *
37     * Description: Set up the environment variables that are common to
38     * all CGI scripts
39     */
40    
41     void create_common_env()
42     {
43 nmav 1.7 int index = 0, i;
44 nmav 1.1
45    
46 nmav 1.7 /* NOTE NOTE NOTE:
47     If you (the reader) someday modify this chunk of code to
48     handle more "common" CGI environment variables, then bump the
49     value COMMON_CGI_COUNT in defines.h UP
50    
51     Also, in the case of document_root and server_admin, two variables
52     that may or may not be defined depending on the way the server
53     is configured, we check for null values and use an empty
54     string to denote a NULL value to the environment, as per the
55     specification. The quote for which follows:
56    
57     "In all cases, a missing environment variable is
58     equivalent to a zero-length (NULL) value, and vice versa."
59     */
60     common_cgi_env[index++] = env_gen_extra("PATH",
61     ((cgi_path !=
62     NULL) ? cgi_path :
63     DEFAULT_PATH), 0);
64     common_cgi_env[index++] =
65     env_gen_extra("SERVER_SOFTWARE", SERVER_NAME "/" SERVER_VERSION, 0);
66     common_cgi_env[index++] =
67     env_gen_extra("GATEWAY_INTERFACE", CGI_VERSION, 0);
68    
69     /* removed the SERVER_PORT which may change due to SSL support
70     * Also removed the DOCUMENT_ROOT, SERVER_NAME, which are now per request.
71     */
72    
73    
74 nmav 1.22 /* NCSA added */
75     #ifdef USE_NCSA_CGI_ENV
76     common_cgi_env[index++] = env_gen_extra("SERVER_ROOT", server_root);
77     #endif
78 nmav 1.7
79     /* APACHE added */
80     common_cgi_env[index++] =
81     env_gen_extra("SERVER_ADMIN", server_admin, 0);
82     common_cgi_env[index] = NULL;
83    
84     /* Sanity checking -- make *sure* the memory got allocated */
85     if (index > COMMON_CGI_COUNT) {
86     log_error_time();
87     fprintf(stderr, "COMMON_CGI_COUNT not high enough.\n");
88     exit(1);
89     }
90    
91     for (i = 0; i < index; ++i) {
92     if (common_cgi_env[i] == NULL) {
93     log_error_time();
94     fprintf(stderr,
95     "Unable to allocate a component of common_cgi_env - out of memory.\n");
96     exit(1);
97     }
98     }
99 nmav 1.1 }
100    
101     void clear_common_env(void)
102     {
103 nmav 1.7 int i;
104 nmav 1.1
105 nmav 1.7 for (i = 0; i <= COMMON_CGI_COUNT; ++i) {
106     if (common_cgi_env[i] != NULL) {
107     free(common_cgi_env[i]);
108     common_cgi_env[i] = NULL;
109     }
110     }
111 nmav 1.1 }
112    
113     /*
114     * Name: env_gen_extra
115     * (and via a not-so-tricky #define, env_gen)
116     * This routine calls malloc: please free the memory when you are done
117     */
118     static char *env_gen_extra(const char *key, const char *value, int extra)
119     {
120 nmav 1.7 char *result;
121     int key_len, value_len;
122 nmav 1.1
123 nmav 1.7 if (value == NULL) /* ServerAdmin may not be defined, eg */
124     value = "";
125     key_len = strlen(key);
126     value_len = strlen(value);
127     /* leave room for '=' sign and null terminator */
128     result = malloc(extra + key_len + value_len + 2);
129     if (result) {
130     memcpy(result + extra, key, key_len);
131     *(result + extra + key_len) = '=';
132     memcpy(result + extra + key_len + 1, value, value_len);
133     *(result + extra + key_len + value_len + 1) = '\0';
134     } else {
135     log_error_time();
136     perror("malloc");
137     log_error_time();
138     fprintf(stderr,
139     "tried to allocate (key=value) extra=%d: %s=%s\n",
140     extra, key, value);
141     }
142     return result;
143 nmav 1.1 }
144    
145     /*
146     * Name: add_cgi_env
147     *
148     * Description: adds a variable to CGI's environment
149     * Used for HTTP_ headers
150     */
151    
152 nmav 1.10 int add_cgi_env(request * req, const char *key, const char *value,
153     int http_prefix)
154 nmav 1.1 {
155 nmav 1.7 char *p;
156     int prefix_len;
157 nmav 1.1
158 nmav 1.7 if (http_prefix) {
159     prefix_len = 5;
160     } else {
161     prefix_len = 0;
162     }
163    
164     if (req->cgi_env_index < CGI_ENV_MAX) {
165     p = env_gen_extra(key, value, prefix_len);
166     if (!p) {
167     log_error_time();
168     fprintf(stderr, "Unable to generate additional CGI Environment"
169     "variable -- ran out of memory!\n");
170     }
171     if (prefix_len)
172     memcpy(p, "HTTP_", 5);
173     req->cgi_env[req->cgi_env_index++] = p;
174     return 1;
175     } else {
176     log_error_time();
177     fprintf(stderr, "Unable to generate additional CGI Environment"
178     "variable -- not enough space!\n");
179     }
180     return 0;
181 nmav 1.1 }
182    
183     #define my_add_cgi_env(req, key, value) { \
184     int ok = add_cgi_env(req, key, value, 0); \
185     if (!ok) return 0; \
186     }
187    
188 nmav 1.10 const char *hydra_method_str(int method)
189 nmav 1.1 {
190 nmav 1.10 char *w;
191     switch (method) {
192     case M_POST:
193     w = "POST";
194     break;
195     case M_HEAD:
196     w = "HEAD";
197     break;
198     case M_GET:
199     w = "GET";
200     break;
201     default:
202     w = "UNKNOWN";
203     break;
204     }
205     return w;
206 nmav 1.8 }
207    
208     /*
209     * Name: complete_env
210     *
211     * Description: adds the known client header env variables
212     * and terminates the environment array
213     */
214    
215     int complete_env(request * req)
216     {
217     int i;
218     char buf[22];
219 nmav 1.10 const char *w;
220 nmav 1.8
221     for (i = 0; common_cgi_env[i]; i++)
222     req->cgi_env[i] = common_cgi_env[i];
223    
224 nmav 1.10 w = hydra_method_str(req->method);
225 nmav 1.8 my_add_cgi_env(req, "REQUEST_METHOD", w);
226 nmav 1.7
227     if (req->secure) {
228     simple_itoa(ssl_port, buf);
229     } else {
230     simple_itoa(server_port, buf);
231     }
232     my_add_cgi_env(req, "SERVER_PORT", buf);
233     my_add_cgi_env(req, "SERVER_NAME", req->hostname);
234    
235     /* NCSA and APACHE added -- not in CGI spec */
236 nmav 1.22 #ifdef USE_NCSA_CGI_ENV
237     my_add_cgi_env( req, "DOCUMENT_ROOT", req->document_root);
238     #endif
239 nmav 1.7
240     my_add_cgi_env(req, "SERVER_ADDR", req->local_ip_addr);
241     my_add_cgi_env(req, "SERVER_PROTOCOL", req->http_version);
242     my_add_cgi_env(req, "REQUEST_URI", req->request_uri);
243    
244     if (req->path_info)
245     my_add_cgi_env(req, "PATH_INFO", req->path_info);
246    
247     if (req->path_translated)
248     /* while path_translated depends on path_info,
249     * there are cases when path_translated might
250     * not exist when path_info does
251     */
252     my_add_cgi_env(req, "PATH_TRANSLATED", req->path_translated);
253    
254     my_add_cgi_env(req, "SCRIPT_NAME", req->script_name);
255    
256     if (req->query_string)
257     my_add_cgi_env(req, "QUERY_STRING", req->query_string);
258     my_add_cgi_env(req, "REMOTE_ADDR", req->remote_ip_addr);
259    
260     simple_itoa(req->remote_port, buf);
261     my_add_cgi_env(req, "REMOTE_PORT", buf);
262    
263     if (req->method == M_POST) {
264     if (req->content_type) {
265     my_add_cgi_env(req, "CONTENT_TYPE", req->content_type);
266     } else {
267     my_add_cgi_env(req, "CONTENT_TYPE", default_type);
268     }
269     if (req->content_length) {
270     my_add_cgi_env(req, "CONTENT_LENGTH", req->content_length);
271     }
272     }
273 nmav 1.1 #ifdef ACCEPT_ON
274 nmav 1.7 if (req->accept[0])
275     my_add_cgi_env(req, "HTTP_ACCEPT", req->accept);
276 nmav 1.1 #endif
277    
278 nmav 1.7 if (req->cgi_env_index < CGI_ENV_MAX + 1) {
279     req->cgi_env[req->cgi_env_index] = NULL; /* terminate */
280     return 1;
281     }
282     log_error_time();
283     fprintf(stderr, "Not enough space in CGI environment for remainder"
284     " of variables.\n");
285     return 0;
286 nmav 1.1 }
287    
288     /*
289     * Name: make_args_cgi
290     *
291     * Build argv list for a CGI script according to spec
292     *
293     */
294    
295     void create_argv(request * req, char **aargv)
296     {
297 nmav 1.7 char *p, *q, *r;
298     int aargc;
299 nmav 1.1
300 nmav 1.7 q = req->query_string;
301     aargv[0] = req->pathname;
302 nmav 1.1
303 nmav 1.7 /* here, we handle a special "indexed" query string.
304     * Taken from the CGI/1.1 SPEC:
305     * This is identified by a GET or HEAD request with a query string
306     * with no *unencoded* '=' in it.
307     * For such a request, I'm supposed to parse the search string
308     * into words, according to the following rules:
309    
310     search-string = search-word *( "+" search-word )
311     search-word = 1*schar
312     schar = xunreserved | escaped | xreserved
313     xunreserved = alpha | digit | xsafe | extra
314     xsafe = "$" | "-" | "_" | "."
315     xreserved = ";" | "/" | "?" | ":" | "@" | "&"
316    
317     After parsing, each word is URL-decoded, optionally encoded in a system
318     defined manner, and then the argument list
319     is set to the list of words.
320    
321    
322     Thus, schar is alpha|digit|"$"|"-"|"_"|"."|";"|"/"|"?"|":"|"@"|"&"
323    
324     As of this writing, escape.pl escapes the following chars:
325    
326     "-", "_", ".", "!", "~", "*", "'", "(", ")",
327     "0".."9", "A".."Z", "a".."z",
328     ";", "/", "?", ":", "@", "&", "=", "+", "\$", ","
329    
330     Which therefore means
331     "=", "+", "~", "!", "*", "'", "(", ")", ","
332     are *not* escaped and should be?
333     Wait, we don't do any escaping, and nor should we.
334     According to the RFC draft, we unescape and then re-escape
335     in a "system defined manner" (here: none).
336    
337     The CGI/1.1 draft (03, latest is 1999???) is very unclear here.
338    
339     I am using the latest published RFC, 2396, for what does and does
340     not need escaping.
341    
342     Since boa builds the argument list and does not call /bin/sh,
343     (boa uses execve for CGI)
344     */
345    
346     if (q && !strchr(q, '=')) {
347     /* we have an 'index' style */
348     q = strdup(q);
349     if (!q) {
350     WARN("unable to strdup 'q' in create_argv!");
351     }
352     for (aargc = 1; q && (aargc < CGI_ARGC_MAX);) {
353     r = q;
354     /* for an index-style CGI, + is used to seperate arguments
355     * an escaped '+' is of no concern to us
356     */
357     if ((p = strchr(q, '+'))) {
358     *p = '\0';
359     q = p + 1;
360     } else {
361     q = NULL;
362     }
363     if (unescape_uri(r, NULL)) {
364     /* printf("parameter %d: %s\n",aargc,r); */
365     aargv[aargc++] = r;
366     }
367     }
368     aargv[aargc] = NULL;
369     } else {
370     aargv[1] = NULL;
371     }
372 nmav 1.1 }
373    
374     /*
375     * Name: init_cgi
376     *
377     * Description: Called for GET/POST requests that refer to ScriptAlias
378 nmav 1.7 * directories or application/x-httpd-cgi files. Pipes are used for the
379     * communication with the child.
380 nmav 1.1 * stderr remains tied to our log file; is this good?
381     *
382     * Returns:
383     * 0 - error or NPH, either way the socket is closed
384     * 1 - success
385     */
386    
387     int init_cgi(request * req)
388     {
389 nmav 1.7 int child_pid;
390     int pipes[2];
391    
392     SQUASH_KA(req);
393    
394 nmav 1.10 if (req->is_cgi == NPH || req->is_cgi == CGI || req->is_cgi == HIC_CGI) {
395 nmav 1.20 if (req->secure && complete_env_ssl(req) == 0) {
396     return 0;
397     }
398 nmav 1.7 if (complete_env(req) == 0) {
399     return 0;
400     }
401     }
402 nmav 1.1 #ifdef FASCIST_LOGGING
403 nmav 1.7 {
404     int i;
405     for (i = 0; i < req->cgi_env_index; ++i)
406     fprintf(stderr, "%s - environment variable for cgi: \"%s\"\n",
407     __FILE__, req->cgi_env[i]);
408     }
409 nmav 1.1 #endif
410    
411 nmav 1.7 if (req->is_cgi) {
412     if (pipe(pipes) == -1) {
413     log_error_time();
414     perror("pipe");
415     return 0;
416     }
417    
418     /* set the read end of the socket to non-blocking */
419     if (set_nonblock_fd(pipes[0]) == -1) {
420     log_error_time();
421     perror("cgi-fcntl");
422     close(pipes[0]);
423     close(pipes[1]);
424     return 0;
425     }
426     } else {
427     log_error_time();
428     fprintf(stderr, "Non CGI in init_cgi()!\n");
429     return 0;
430     }
431    
432 nmav 1.10 if (req->is_cgi == HIC_CGI) { /* internally handled cgi */
433 nmav 1.15 #ifdef ENABLE_HIC
434 nmav 1.18 /* Move the post_data_fd pointer to start.
435     */
436 nmav 1.23 if (req->post_data_fd.pipe==0)
437     lseek(req->post_data_fd.fds[0], SEEK_SET, 0);
438     else { /* close the write end */
439     close(req->post_data_fd.fds[1]);
440     req->post_data_fd.fds[1] = -1;
441     }
442    
443 nmav 1.10 if (hic_send_command(req, pipes[1]) == -1) {
444 nmav 1.24 log_error_time();
445     fprintf(stderr, "Error executing HIC command.\n");
446 nmav 1.10 close(pipes[0]);
447     close(pipes[1]);
448     return 0;
449     }
450 nmav 1.15 #else /* No hic! */
451     close(pipes[0]);
452     close(pipes[1]);
453     return 0;
454     #endif
455 nmav 1.10 } else { /* plain cgi... do fork */
456     child_pid = fork();
457     switch (child_pid) {
458     case -1:
459     /* fork unsuccessful */
460     log_error_time();
461     perror("fork");
462 nmav 1.7
463 nmav 1.10 close(pipes[0]);
464     close(pipes[1]);
465 nmav 1.7
466 nmav 1.10 send_r_error(req);
467     /* FIXME: There is aproblem here. send_r_error would work
468     for NPH and CGI, but not for GUNZIP. Fix that. */
469     /* i'd like to send_r_error, but.... */
470     return 0;
471     break;
472     case 0:
473     /* child */
474     if (req->is_cgi == CGI || req->is_cgi == NPH) {
475 nmav 1.22 int l;
476     char *newpath;
477 nmav 1.10 char *c;
478    
479 nmav 1.22 c = strrchr(req->pathname, '/');
480     if (!c) {
481     /* there will always be a '.' */
482     log_error_time();
483     WARN("unable to find '/' in req->pathname");
484     close(pipes[1]);
485 nmav 1.10 _exit(1);
486     }
487 nmav 1.22
488     *c = '\0';
489    
490     if (chdir(req->pathname) != 0) {
491 nmav 1.10 log_error_time();
492     perror("chdir");
493     close(pipes[1]);
494     _exit(1);
495     }
496 nmav 1.22
497     req->pathname = ++c;
498     l = strlen(req->pathname) + 3;
499     /* prefix './' */
500     newpath = malloc(sizeof(char) * l);
501     if (!newpath) {
502     /* there will always be a '.' */
503 nmav 1.10 log_error_time();
504 nmav 1.22 perror("unable to malloc for newpath");
505 nmav 1.10 close(pipes[1]);
506     _exit(1);
507     }
508 nmav 1.22 newpath[0] = '.';
509     newpath[1] = '/';
510     memcpy(&newpath[2], req->pathname, l - 2); /* includes the trailing '\0' */
511     req->pathname = newpath;
512 nmav 1.7 }
513 nmav 1.22
514     /* close the 'read' end of the pipes[] */
515     close(pipes[0]);
516    
517     /* tie cgi's STDOUT to our write end of pipe */
518     if (dup2(pipes[1], STDOUT_FILENO) == -1) {
519     log_error_time();
520     perror("dup2 - pipes");
521     _exit(1);
522     }
523     close(pipes[1]);
524    
525 nmav 1.10 /* tie post_data_fd to POST stdin */
526     if (req->method == M_POST) { /* tie stdin to file */
527 nmav 1.23 if (req->post_data_fd.pipe==0) {
528     lseek(req->post_data_fd.fds[0], SEEK_SET, 0);
529     }
530    
531     dup2(req->post_data_fd.fds[0], STDIN_FILENO);
532     close_tmp_fd( &req->post_data_fd);
533 nmav 1.10 }
534 nmav 1.22
535     umask(cgi_umask); /* change umask *again* u=rwx,g=rxw,o= */
536 nmav 1.10
537     /*
538     * tie STDERR to cgi_log_fd
539     * cgi_log_fd will automatically close, close-on-exec rocks!
540     * if we don't tied STDERR (current log_error) to cgi_log_fd,
541     * then we ought to close it.
542     */
543 nmav 1.22 if (cgi_log_fd) {
544     dup2(cgi_log_fd, STDERR_FILENO);
545     close( cgi_log_fd);
546     }
547 nmav 1.10
548     if (req->is_cgi == NPH || req->is_cgi == CGI) {
549     char *aargv[CGI_ARGC_MAX + 1];
550 nmav 1.19 create_argv(req, aargv);
551 nmav 1.10 execve(req->pathname, aargv, req->cgi_env);
552     } else {
553     if (req->is_cgi == INDEXER_CGI)
554     execl(dirmaker, dirmaker, req->pathname, req->request_uri,
555     NULL);
556     }
557     /* execve failed */
558     WARN(req->pathname);
559 nmav 1.7 _exit(1);
560    
561 nmav 1.10 break; /* it doesn't matter, we never make it until here */
562    
563     default:
564     /* parent */
565     /* if here, fork was successful */
566     if (verbose_cgi_logs) {
567     log_error_time();
568     fprintf(stderr, "Forked child \"%s\" pid %d\n",
569     req->pathname, child_pid);
570     }
571 nmav 1.7
572 nmav 1.10 if (req->method == M_POST) {
573 nmav 1.23 close_tmp_fd( &req->post_data_fd);
574 nmav 1.10 }
575 nmav 1.7
576 nmav 1.10 /* NPH, etc... all go straight to the fd */
577 nmav 1.7
578 nmav 1.10 close(pipes[1]);
579     break;
580 nmav 1.7 }
581 nmav 1.18 } /* HIC */
582 nmav 1.7
583 nmav 1.10 /* we only get here in parent case, and
584     * success.
585     */
586 nmav 1.7
587 nmav 1.10 req->data_fd = pipes[0];
588 nmav 1.7
589 nmav 1.10 req->status = PIPE_READ;
590 nmav 1.21 if (req->is_cgi == CGI || req->is_cgi == HIC_CGI) {
591 nmav 1.10 req->cgi_status = CGI_PARSE; /* got to parse cgi header */
592     /* for cgi_header... I get half the buffer! */
593     req->header_line = req->header_end = (req->buffer + BUFFER_SIZE / 2);
594 nmav 1.17 } else { /* HIC CGIs and NPH CGIs */
595 nmav 1.10 req->cgi_status = CGI_BUFFER;
596     /* I get all the buffer! */
597     req->header_line = req->header_end = req->buffer;
598     }
599 nmav 1.7
600 nmav 1.10 /* reset req->filepos for logging (it's used in pipe.c) */
601     /* still don't know why req->filesize might be reset though */
602     req->filepos = 0;
603 nmav 1.1
604 nmav 1.7 return 1;
605 nmav 1.1 }

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26