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

Contents of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.1.1.1 - (show annotations) (vendor branch)
Sat Sep 21 13:53:18 2002 UTC (21 years, 6 months ago) by nmav
Branch: boas
CVS Tags: start
Changes since 1.1: +0 -0 lines
File MIME type: text/plain
Imported sources

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