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

Annotation of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.18 - (hide annotations)
Tue Oct 1 22:38:24 2002 UTC (21 years, 6 months ago) by nmav
Branch: MAIN
Changes since 1.17: +6 -2 lines
File MIME type: text/plain
Several changes to allow Cookies and POST data, to work in HIC CGIs.
Check if a CGI is accessible before trying to execute it, and send not
found if it is not accesible.

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.18 /* $Id: cgi.c,v 1.17 2002/09/30 17:16:54 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     /* NCSA added */
75     /* common_cgi_env[index++] = env_gen_extra("SERVER_ROOT", server_root); */
76    
77     /* APACHE added */
78     common_cgi_env[index++] =
79     env_gen_extra("SERVER_ADMIN", server_admin, 0);
80     common_cgi_env[index] = NULL;
81    
82     /* Sanity checking -- make *sure* the memory got allocated */
83     if (index > COMMON_CGI_COUNT) {
84     log_error_time();
85     fprintf(stderr, "COMMON_CGI_COUNT not high enough.\n");
86     exit(1);
87     }
88    
89     for (i = 0; i < index; ++i) {
90     if (common_cgi_env[i] == NULL) {
91     log_error_time();
92     fprintf(stderr,
93     "Unable to allocate a component of common_cgi_env - out of memory.\n");
94     exit(1);
95     }
96     }
97 nmav 1.1 }
98    
99     void clear_common_env(void)
100     {
101 nmav 1.7 int i;
102 nmav 1.1
103 nmav 1.7 for (i = 0; i <= COMMON_CGI_COUNT; ++i) {
104     if (common_cgi_env[i] != NULL) {
105     free(common_cgi_env[i]);
106     common_cgi_env[i] = NULL;
107     }
108     }
109 nmav 1.1 }
110    
111     /*
112     * Name: env_gen_extra
113     * (and via a not-so-tricky #define, env_gen)
114     * This routine calls malloc: please free the memory when you are done
115     */
116     static char *env_gen_extra(const char *key, const char *value, int extra)
117     {
118 nmav 1.7 char *result;
119     int key_len, value_len;
120 nmav 1.1
121 nmav 1.7 if (value == NULL) /* ServerAdmin may not be defined, eg */
122     value = "";
123     key_len = strlen(key);
124     value_len = strlen(value);
125     /* leave room for '=' sign and null terminator */
126     result = malloc(extra + key_len + value_len + 2);
127     if (result) {
128     memcpy(result + extra, key, key_len);
129     *(result + extra + key_len) = '=';
130     memcpy(result + extra + key_len + 1, value, value_len);
131     *(result + extra + key_len + value_len + 1) = '\0';
132     } else {
133     log_error_time();
134     perror("malloc");
135     log_error_time();
136     fprintf(stderr,
137     "tried to allocate (key=value) extra=%d: %s=%s\n",
138     extra, key, value);
139     }
140     return result;
141 nmav 1.1 }
142    
143     /*
144     * Name: add_cgi_env
145     *
146     * Description: adds a variable to CGI's environment
147     * Used for HTTP_ headers
148     */
149    
150 nmav 1.10 int add_cgi_env(request * req, const char *key, const char *value,
151     int http_prefix)
152 nmav 1.1 {
153 nmav 1.7 char *p;
154     int prefix_len;
155 nmav 1.1
156 nmav 1.7 if (http_prefix) {
157     prefix_len = 5;
158     } else {
159     prefix_len = 0;
160     }
161    
162     if (req->cgi_env_index < CGI_ENV_MAX) {
163     p = env_gen_extra(key, value, prefix_len);
164     if (!p) {
165     log_error_time();
166     fprintf(stderr, "Unable to generate additional CGI Environment"
167     "variable -- ran out of memory!\n");
168     }
169     if (prefix_len)
170     memcpy(p, "HTTP_", 5);
171     req->cgi_env[req->cgi_env_index++] = p;
172     return 1;
173     } else {
174     log_error_time();
175     fprintf(stderr, "Unable to generate additional CGI Environment"
176     "variable -- not enough space!\n");
177     }
178     return 0;
179 nmav 1.1 }
180    
181     #define my_add_cgi_env(req, key, value) { \
182     int ok = add_cgi_env(req, key, value, 0); \
183     if (!ok) return 0; \
184     }
185    
186 nmav 1.10 const char *hydra_method_str(int method)
187 nmav 1.1 {
188 nmav 1.10 char *w;
189     switch (method) {
190     case M_POST:
191     w = "POST";
192     break;
193     case M_HEAD:
194     w = "HEAD";
195     break;
196     case M_GET:
197     w = "GET";
198     break;
199     default:
200     w = "UNKNOWN";
201     break;
202     }
203     return w;
204 nmav 1.8 }
205    
206     /*
207     * Name: complete_env
208     *
209     * Description: adds the known client header env variables
210     * and terminates the environment array
211     */
212    
213     int complete_env(request * req)
214     {
215     int i;
216     char buf[22];
217 nmav 1.10 const char *w;
218 nmav 1.8
219     for (i = 0; common_cgi_env[i]; i++)
220     req->cgi_env[i] = common_cgi_env[i];
221    
222 nmav 1.10 w = hydra_method_str(req->method);
223 nmav 1.8 my_add_cgi_env(req, "REQUEST_METHOD", w);
224 nmav 1.7
225     if (req->secure) {
226     simple_itoa(ssl_port, buf);
227     } else {
228     simple_itoa(server_port, buf);
229     }
230     my_add_cgi_env(req, "SERVER_PORT", buf);
231     my_add_cgi_env(req, "SERVER_NAME", req->hostname);
232    
233     /* NCSA and APACHE added -- not in CGI spec */
234     /* my_add_cgi_env( req, "DOCUMENT_ROOT", req->document_root); */
235    
236     my_add_cgi_env(req, "SERVER_ADDR", req->local_ip_addr);
237     my_add_cgi_env(req, "SERVER_PROTOCOL", req->http_version);
238     my_add_cgi_env(req, "REQUEST_URI", req->request_uri);
239    
240     if (req->path_info)
241     my_add_cgi_env(req, "PATH_INFO", req->path_info);
242    
243     if (req->path_translated)
244     /* while path_translated depends on path_info,
245     * there are cases when path_translated might
246     * not exist when path_info does
247     */
248     my_add_cgi_env(req, "PATH_TRANSLATED", req->path_translated);
249    
250     my_add_cgi_env(req, "SCRIPT_NAME", req->script_name);
251    
252     if (req->query_string)
253     my_add_cgi_env(req, "QUERY_STRING", req->query_string);
254     my_add_cgi_env(req, "REMOTE_ADDR", req->remote_ip_addr);
255    
256     simple_itoa(req->remote_port, buf);
257     my_add_cgi_env(req, "REMOTE_PORT", buf);
258    
259     if (req->method == M_POST) {
260     if (req->content_type) {
261     my_add_cgi_env(req, "CONTENT_TYPE", req->content_type);
262     } else {
263     my_add_cgi_env(req, "CONTENT_TYPE", default_type);
264     }
265     if (req->content_length) {
266     my_add_cgi_env(req, "CONTENT_LENGTH", req->content_length);
267     }
268     }
269 nmav 1.1 #ifdef ACCEPT_ON
270 nmav 1.7 if (req->accept[0])
271     my_add_cgi_env(req, "HTTP_ACCEPT", req->accept);
272 nmav 1.1 #endif
273    
274 nmav 1.7 if (req->cgi_env_index < CGI_ENV_MAX + 1) {
275     req->cgi_env[req->cgi_env_index] = NULL; /* terminate */
276     return 1;
277     }
278     log_error_time();
279     fprintf(stderr, "Not enough space in CGI environment for remainder"
280     " of variables.\n");
281     return 0;
282 nmav 1.1 }
283    
284     /*
285     * Name: make_args_cgi
286     *
287     * Build argv list for a CGI script according to spec
288     *
289     */
290    
291     void create_argv(request * req, char **aargv)
292     {
293 nmav 1.7 char *p, *q, *r;
294     int aargc;
295 nmav 1.1
296 nmav 1.7 q = req->query_string;
297     aargv[0] = req->pathname;
298 nmav 1.1
299 nmav 1.7 /* here, we handle a special "indexed" query string.
300     * Taken from the CGI/1.1 SPEC:
301     * This is identified by a GET or HEAD request with a query string
302     * with no *unencoded* '=' in it.
303     * For such a request, I'm supposed to parse the search string
304     * into words, according to the following rules:
305    
306     search-string = search-word *( "+" search-word )
307     search-word = 1*schar
308     schar = xunreserved | escaped | xreserved
309     xunreserved = alpha | digit | xsafe | extra
310     xsafe = "$" | "-" | "_" | "."
311     xreserved = ";" | "/" | "?" | ":" | "@" | "&"
312    
313     After parsing, each word is URL-decoded, optionally encoded in a system
314     defined manner, and then the argument list
315     is set to the list of words.
316    
317    
318     Thus, schar is alpha|digit|"$"|"-"|"_"|"."|";"|"/"|"?"|":"|"@"|"&"
319    
320     As of this writing, escape.pl escapes the following chars:
321    
322     "-", "_", ".", "!", "~", "*", "'", "(", ")",
323     "0".."9", "A".."Z", "a".."z",
324     ";", "/", "?", ":", "@", "&", "=", "+", "\$", ","
325    
326     Which therefore means
327     "=", "+", "~", "!", "*", "'", "(", ")", ","
328     are *not* escaped and should be?
329     Wait, we don't do any escaping, and nor should we.
330     According to the RFC draft, we unescape and then re-escape
331     in a "system defined manner" (here: none).
332    
333     The CGI/1.1 draft (03, latest is 1999???) is very unclear here.
334    
335     I am using the latest published RFC, 2396, for what does and does
336     not need escaping.
337    
338     Since boa builds the argument list and does not call /bin/sh,
339     (boa uses execve for CGI)
340     */
341    
342     if (q && !strchr(q, '=')) {
343     /* we have an 'index' style */
344     q = strdup(q);
345     if (!q) {
346     WARN("unable to strdup 'q' in create_argv!");
347     }
348     for (aargc = 1; q && (aargc < CGI_ARGC_MAX);) {
349     r = q;
350     /* for an index-style CGI, + is used to seperate arguments
351     * an escaped '+' is of no concern to us
352     */
353     if ((p = strchr(q, '+'))) {
354     *p = '\0';
355     q = p + 1;
356     } else {
357     q = NULL;
358     }
359     if (unescape_uri(r, NULL)) {
360     /* printf("parameter %d: %s\n",aargc,r); */
361     aargv[aargc++] = r;
362     }
363     }
364     aargv[aargc] = NULL;
365     } else {
366     aargv[1] = NULL;
367     }
368 nmav 1.1 }
369    
370     /*
371     * Name: init_cgi
372     *
373     * Description: Called for GET/POST requests that refer to ScriptAlias
374 nmav 1.7 * directories or application/x-httpd-cgi files. Pipes are used for the
375     * communication with the child.
376 nmav 1.1 * stderr remains tied to our log file; is this good?
377     *
378     * Returns:
379     * 0 - error or NPH, either way the socket is closed
380     * 1 - success
381     */
382    
383     int init_cgi(request * req)
384     {
385 nmav 1.7 int child_pid;
386     int pipes[2];
387    
388     SQUASH_KA(req);
389    
390 nmav 1.10 if (req->is_cgi == NPH || req->is_cgi == CGI || req->is_cgi == HIC_CGI) {
391 nmav 1.7 if (complete_env(req) == 0) {
392     return 0;
393     }
394     }
395 nmav 1.1 #ifdef FASCIST_LOGGING
396 nmav 1.7 {
397     int i;
398     for (i = 0; i < req->cgi_env_index; ++i)
399     fprintf(stderr, "%s - environment variable for cgi: \"%s\"\n",
400     __FILE__, req->cgi_env[i]);
401     }
402 nmav 1.1 #endif
403    
404 nmav 1.7 if (req->is_cgi) {
405     if (pipe(pipes) == -1) {
406     log_error_time();
407     perror("pipe");
408     return 0;
409     }
410    
411     /* set the read end of the socket to non-blocking */
412     if (set_nonblock_fd(pipes[0]) == -1) {
413     log_error_time();
414     perror("cgi-fcntl");
415     close(pipes[0]);
416     close(pipes[1]);
417     return 0;
418     }
419     } else {
420     log_error_time();
421     fprintf(stderr, "Non CGI in init_cgi()!\n");
422     return 0;
423     }
424    
425 nmav 1.10 if (req->is_cgi == HIC_CGI) { /* internally handled cgi */
426 nmav 1.15 #ifdef ENABLE_HIC
427 nmav 1.18 /* Move the post_data_fd pointer to start.
428     */
429     lseek(req->post_data_fd, SEEK_SET, 0);
430    
431 nmav 1.10 if (hic_send_command(req, pipes[1]) == -1) {
432     log_error_time();
433     fprintf(stderr, "Error in HIC\n");
434     close(pipes[0]);
435     close(pipes[1]);
436     return 0;
437     }
438 nmav 1.15 #else /* No hic! */
439     close(pipes[0]);
440     close(pipes[1]);
441     return 0;
442     #endif
443 nmav 1.10 } else { /* plain cgi... do fork */
444     child_pid = fork();
445     switch (child_pid) {
446     case -1:
447     /* fork unsuccessful */
448     log_error_time();
449     perror("fork");
450 nmav 1.7
451 nmav 1.10 close(pipes[0]);
452     close(pipes[1]);
453 nmav 1.7
454 nmav 1.10 send_r_error(req);
455     /* FIXME: There is aproblem here. send_r_error would work
456     for NPH and CGI, but not for GUNZIP. Fix that. */
457     /* i'd like to send_r_error, but.... */
458     return 0;
459     break;
460     case 0:
461     /* child */
462     if (req->is_cgi == CGI || req->is_cgi == NPH) {
463     char *foo = strdup(req->pathname);
464     char *c;
465    
466     if (!foo) {
467     WARN("unable to strdup pathname for req->pathname");
468     _exit(1);
469     }
470     c = strrchr(foo, '/');
471     if (c) {
472     ++c;
473     *c = '\0';
474     } else {
475     /* we have a serious problem */
476     log_error_time();
477     perror("chdir");
478     close(pipes[1]);
479     _exit(1);
480     }
481     if (chdir(foo) != 0) {
482     log_error_time();
483     perror("chdir");
484     close(pipes[1]);
485     _exit(1);
486     }
487 nmav 1.7 }
488 nmav 1.10 close(pipes[0]);
489     /* tie cgi's STDOUT to it's write end of pipe */
490     if (dup2(pipes[1], STDOUT_FILENO) == -1) {
491 nmav 1.7 log_error_time();
492 nmav 1.10 perror("dup2 - pipes");
493 nmav 1.7 close(pipes[1]);
494     _exit(1);
495     }
496 nmav 1.10 close(pipes[1]);
497     if (set_block_fd(STDOUT_FILENO) == -1) {
498 nmav 1.7 log_error_time();
499 nmav 1.10 perror("cgi-fcntl");
500 nmav 1.7 _exit(1);
501     }
502 nmav 1.10
503     /* tie post_data_fd to POST stdin */
504     if (req->method == M_POST) { /* tie stdin to file */
505     lseek(req->post_data_fd, SEEK_SET, 0);
506     dup2(req->post_data_fd, STDIN_FILENO);
507     close(req->post_data_fd);
508     }
509     /* Close access log, so CGI program can't scribble
510     * where it shouldn't
511     */
512     close_access_log();
513    
514     /*
515     * tie STDERR to cgi_log_fd
516     * cgi_log_fd will automatically close, close-on-exec rocks!
517     * if we don't tied STDERR (current log_error) to cgi_log_fd,
518     * then we ought to close it.
519     */
520     if (!cgi_log_fd)
521     dup2(devnullfd, STDERR_FILENO);
522     else
523     dup2(cgi_log_fd, STDERR_FILENO);
524    
525     if (req->is_cgi == NPH || req->is_cgi == CGI) {
526     char *aargv[CGI_ARGC_MAX + 1];
527     create_argv(req, aargv);
528     execve(req->pathname, aargv, req->cgi_env);
529     } else {
530     if (req->is_cgi == INDEXER_CGI)
531     execl(dirmaker, dirmaker, req->pathname, req->request_uri,
532     NULL);
533     }
534     /* execve failed */
535     WARN(req->pathname);
536 nmav 1.7 _exit(1);
537    
538 nmav 1.10 break; /* it doesn't matter, we never make it until here */
539    
540     default:
541     /* parent */
542     /* if here, fork was successful */
543     if (verbose_cgi_logs) {
544     log_error_time();
545     fprintf(stderr, "Forked child \"%s\" pid %d\n",
546     req->pathname, child_pid);
547     }
548 nmav 1.7
549 nmav 1.10 if (req->method == M_POST) {
550     close(req->post_data_fd); /* child closed it too */
551     req->post_data_fd = -1;
552     }
553 nmav 1.7
554 nmav 1.10 /* NPH, etc... all go straight to the fd */
555 nmav 1.7
556 nmav 1.10 close(pipes[1]);
557     break;
558 nmav 1.7 }
559 nmav 1.18 } /* HIC */
560 nmav 1.7
561 nmav 1.10 /* we only get here in parent case, and
562     * success.
563     */
564 nmav 1.7
565 nmav 1.10 req->data_fd = pipes[0];
566 nmav 1.7
567 nmav 1.10 req->status = PIPE_READ;
568 nmav 1.17 if (req->is_cgi == CGI) {
569 nmav 1.10 req->cgi_status = CGI_PARSE; /* got to parse cgi header */
570     /* for cgi_header... I get half the buffer! */
571     req->header_line = req->header_end = (req->buffer + BUFFER_SIZE / 2);
572 nmav 1.17 } else { /* HIC CGIs and NPH CGIs */
573 nmav 1.10 req->cgi_status = CGI_BUFFER;
574     /* I get all the buffer! */
575     req->header_line = req->header_end = req->buffer;
576     }
577 nmav 1.7
578 nmav 1.10 /* reset req->filepos for logging (it's used in pipe.c) */
579     /* still don't know why req->filesize might be reset though */
580     req->filepos = 0;
581 nmav 1.1
582 nmav 1.7 return 1;
583 nmav 1.1 }

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26