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

Contents of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.24 - (show annotations)
Sun Oct 27 10:46:19 2002 UTC (21 years, 5 months ago) by nmav
Branch: MAIN
CVS Tags: hydra_0_0_10, hydra_0_0_9, hydra_0_1_1, hydra_0_1_0
Branch point for: hydra_0_1_0_patches
Changes since 1.23: +3 -3 lines
File MIME type: text/plain
Added the ability to disable HIC threads by using 0 in the
HICThreads configuration directive.
Hydra now uses poll by default. Select is available using the --with-select
configure option.

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

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26