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

Annotation of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.21 - (hide annotations)
Sun Oct 6 09:42:50 2002 UTC (21 years, 6 months ago) by nmav
Branch: MAIN
CVS Tags: hydra_0_0_7
Changes since 1.20: +2 -2 lines
File MIME type: text/plain
The headers of HIC-CGIs are now parsed by the server.

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.21 /* $Id: cgi.c,v 1.20 2002/10/04 19:09:12 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.20 if (req->secure && complete_env_ssl(req) == 0) {
392     return 0;
393     }
394 nmav 1.7 if (complete_env(req) == 0) {
395     return 0;
396     }
397     }
398 nmav 1.1 #ifdef FASCIST_LOGGING
399 nmav 1.7 {
400     int i;
401     for (i = 0; i < req->cgi_env_index; ++i)
402     fprintf(stderr, "%s - environment variable for cgi: \"%s\"\n",
403     __FILE__, req->cgi_env[i]);
404     }
405 nmav 1.1 #endif
406    
407 nmav 1.7 if (req->is_cgi) {
408     if (pipe(pipes) == -1) {
409     log_error_time();
410     perror("pipe");
411     return 0;
412     }
413    
414     /* set the read end of the socket to non-blocking */
415     if (set_nonblock_fd(pipes[0]) == -1) {
416     log_error_time();
417     perror("cgi-fcntl");
418     close(pipes[0]);
419     close(pipes[1]);
420     return 0;
421     }
422     } else {
423     log_error_time();
424     fprintf(stderr, "Non CGI in init_cgi()!\n");
425     return 0;
426     }
427    
428 nmav 1.10 if (req->is_cgi == HIC_CGI) { /* internally handled cgi */
429 nmav 1.15 #ifdef ENABLE_HIC
430 nmav 1.18 /* Move the post_data_fd pointer to start.
431     */
432     lseek(req->post_data_fd, SEEK_SET, 0);
433    
434 nmav 1.10 if (hic_send_command(req, pipes[1]) == -1) {
435     log_error_time();
436     fprintf(stderr, "Error in HIC\n");
437     close(pipes[0]);
438     close(pipes[1]);
439     return 0;
440     }
441 nmav 1.15 #else /* No hic! */
442     close(pipes[0]);
443     close(pipes[1]);
444     return 0;
445     #endif
446 nmav 1.10 } else { /* plain cgi... do fork */
447     child_pid = fork();
448     switch (child_pid) {
449     case -1:
450     /* fork unsuccessful */
451     log_error_time();
452     perror("fork");
453 nmav 1.7
454 nmav 1.10 close(pipes[0]);
455     close(pipes[1]);
456 nmav 1.7
457 nmav 1.10 send_r_error(req);
458     /* FIXME: There is aproblem here. send_r_error would work
459     for NPH and CGI, but not for GUNZIP. Fix that. */
460     /* i'd like to send_r_error, but.... */
461     return 0;
462     break;
463     case 0:
464     /* child */
465     if (req->is_cgi == CGI || req->is_cgi == NPH) {
466     char *foo = strdup(req->pathname);
467     char *c;
468    
469     if (!foo) {
470     WARN("unable to strdup pathname for req->pathname");
471     _exit(1);
472     }
473     c = strrchr(foo, '/');
474     if (c) {
475     ++c;
476     *c = '\0';
477     } else {
478     /* we have a serious problem */
479     log_error_time();
480     perror("chdir");
481     close(pipes[1]);
482     _exit(1);
483     }
484     if (chdir(foo) != 0) {
485     log_error_time();
486     perror("chdir");
487     close(pipes[1]);
488     _exit(1);
489     }
490 nmav 1.7 }
491 nmav 1.10 close(pipes[0]);
492     /* tie cgi's STDOUT to it's write end of pipe */
493     if (dup2(pipes[1], STDOUT_FILENO) == -1) {
494 nmav 1.7 log_error_time();
495 nmav 1.10 perror("dup2 - pipes");
496 nmav 1.7 close(pipes[1]);
497     _exit(1);
498     }
499 nmav 1.10 close(pipes[1]);
500     if (set_block_fd(STDOUT_FILENO) == -1) {
501 nmav 1.7 log_error_time();
502 nmav 1.10 perror("cgi-fcntl");
503 nmav 1.7 _exit(1);
504     }
505 nmav 1.10
506     /* tie post_data_fd to POST stdin */
507     if (req->method == M_POST) { /* tie stdin to file */
508     lseek(req->post_data_fd, SEEK_SET, 0);
509     dup2(req->post_data_fd, STDIN_FILENO);
510     close(req->post_data_fd);
511     }
512     /* Close access log, so CGI program can't scribble
513     * where it shouldn't
514     */
515     close_access_log();
516    
517     /*
518     * tie STDERR to cgi_log_fd
519     * cgi_log_fd will automatically close, close-on-exec rocks!
520     * if we don't tied STDERR (current log_error) to cgi_log_fd,
521     * then we ought to close it.
522     */
523     if (!cgi_log_fd)
524     dup2(devnullfd, STDERR_FILENO);
525     else
526     dup2(cgi_log_fd, STDERR_FILENO);
527    
528     if (req->is_cgi == NPH || req->is_cgi == CGI) {
529     char *aargv[CGI_ARGC_MAX + 1];
530 nmav 1.19 create_argv(req, aargv);
531 nmav 1.10 execve(req->pathname, aargv, req->cgi_env);
532     } else {
533     if (req->is_cgi == INDEXER_CGI)
534     execl(dirmaker, dirmaker, req->pathname, req->request_uri,
535     NULL);
536     }
537     /* execve failed */
538     WARN(req->pathname);
539 nmav 1.7 _exit(1);
540    
541 nmav 1.10 break; /* it doesn't matter, we never make it until here */
542    
543     default:
544     /* parent */
545     /* if here, fork was successful */
546     if (verbose_cgi_logs) {
547     log_error_time();
548     fprintf(stderr, "Forked child \"%s\" pid %d\n",
549     req->pathname, child_pid);
550     }
551 nmav 1.7
552 nmav 1.10 if (req->method == M_POST) {
553     close(req->post_data_fd); /* child closed it too */
554     req->post_data_fd = -1;
555     }
556 nmav 1.7
557 nmav 1.10 /* NPH, etc... all go straight to the fd */
558 nmav 1.7
559 nmav 1.10 close(pipes[1]);
560     break;
561 nmav 1.7 }
562 nmav 1.18 } /* HIC */
563 nmav 1.7
564 nmav 1.10 /* we only get here in parent case, and
565     * success.
566     */
567 nmav 1.7
568 nmav 1.10 req->data_fd = pipes[0];
569 nmav 1.7
570 nmav 1.10 req->status = PIPE_READ;
571 nmav 1.21 if (req->is_cgi == CGI || req->is_cgi == HIC_CGI) {
572 nmav 1.10 req->cgi_status = CGI_PARSE; /* got to parse cgi header */
573     /* for cgi_header... I get half the buffer! */
574     req->header_line = req->header_end = (req->buffer + BUFFER_SIZE / 2);
575 nmav 1.17 } else { /* HIC CGIs and NPH CGIs */
576 nmav 1.10 req->cgi_status = CGI_BUFFER;
577     /* I get all the buffer! */
578     req->header_line = req->header_end = req->buffer;
579     }
580 nmav 1.7
581 nmav 1.10 /* reset req->filepos for logging (it's used in pipe.c) */
582     /* still don't know why req->filesize might be reset though */
583     req->filepos = 0;
584 nmav 1.1
585 nmav 1.7 return 1;
586 nmav 1.1 }

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26