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

Annotation of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.1 - (hide annotations)
Sat Sep 21 13:53:18 2002 UTC (21 years, 7 months ago) by nmav
Branch: MAIN
Branch point for: boas
File MIME type: text/plain
Initial revision

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

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26