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

Annotation of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.7 - (hide annotations)
Wed Sep 25 22:10:03 2002 UTC (21 years, 6 months ago) by nmav
Branch: MAIN
CVS Tags: hydra_0_0_2
Changes since 1.6: +428 -443 lines
File MIME type: text/plain
Cleaned up CGI stuff.

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

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26