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

Contents of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.3 - (show annotations)
Mon Sep 23 19:28:41 2002 UTC (21 years, 6 months ago) by nmav
Branch: MAIN
CVS Tags: BOAS_WITH_RANGES_AND_CGI
Changes since 1.2: +10 -6 lines
File MIME type: text/plain
Cleaned up the CGI handling. Now gunzip, indexer are treated like real cgis. Several other cleanups.

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

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26