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

Contents of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.27 - (show annotations)
Thu Mar 6 12:04:59 2003 UTC (21 years, 1 month ago) by nmav
Branch: MAIN
CVS Tags: hydra_0_1_4
Changes since 1.26: +13 -12 lines
File MIME type: text/plain
Corrected a possible null pointer dereference. Backported from boa 0.94.14rc15.

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

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26