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

Annotation of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.8 - (hide annotations)
Fri Sep 27 10:35:20 2002 UTC (21 years, 6 months ago) by nmav
Branch: MAIN
Changes since 1.7: +23 -18 lines
File MIME type: text/plain
cleanups

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

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26