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

Contents of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.25 - (show annotations)
Wed Jan 22 07:51:49 2003 UTC (21 years, 2 months ago) by nmav
Branch: MAIN
Changes since 1.24: +15 -5 lines
File MIME type: text/plain
merged changes from 0.1.x branch.

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.24.2.3 2003/01/17 17:19:15 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_str);
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 /*
376 * Name: init_cgi
377 *
378 * Description: Called for GET/POST requests that refer to ScriptAlias
379 * directories or application/x-httpd-cgi files. Pipes are used for the
380 * communication with the child.
381 * stderr remains tied to our log file; is this good?
382 *
383 * Returns:
384 * 0 - error or NPH, either way the socket is closed
385 * 1 - success
386 */
387
388 int init_cgi(request * req)
389 {
390 int child_pid;
391 int pipes[2];
392
393 SQUASH_KA(req);
394
395 if (req->is_cgi == NPH || req->is_cgi == CGI || req->is_cgi == HIC_CGI
396 || req->is_cgi == CGI_ACTION)
397 {
398 if (req->secure && complete_env_ssl(req) == 0) {
399 return 0;
400 }
401 if (complete_env(req) == 0) {
402 return 0;
403 }
404 }
405 #ifdef FASCIST_LOGGING
406 {
407 int i;
408 for (i = 0; i < req->cgi_env_index; ++i)
409 fprintf(stderr, "%s - environment variable for cgi: \"%s\"\n",
410 __FILE__, req->cgi_env[i]);
411 }
412 #endif
413
414 if (req->is_cgi) {
415 if (pipe(pipes) == -1) {
416 log_error_time();
417 perror("pipe");
418 return 0;
419 }
420
421 /* set the read end of the socket to non-blocking */
422 if (set_nonblock_fd(pipes[0]) == -1) {
423 log_error_time();
424 perror("cgi-fcntl");
425 close(pipes[0]);
426 close(pipes[1]);
427 return 0;
428 }
429 } else {
430 log_error_time();
431 fprintf(stderr, "Non CGI in init_cgi()!\n");
432 return 0;
433 }
434
435 if (req->is_cgi == HIC_CGI) { /* internally handled cgi */
436 #ifdef ENABLE_HIC
437 /* Move the post_data_fd pointer to start.
438 */
439 if (req->post_data_fd.pipe==0)
440 lseek(req->post_data_fd.fds[0], SEEK_SET, 0);
441 else { /* close the write end */
442 close(req->post_data_fd.fds[1]);
443 req->post_data_fd.fds[1] = -1;
444 }
445
446 if (hic_send_command(req, pipes[1]) == -1) {
447 log_error_time();
448 fprintf(stderr, "Error executing HIC command.\n");
449 close(pipes[0]);
450 close(pipes[1]);
451 return 0;
452 }
453 #else /* No hic! */
454 close(pipes[0]);
455 close(pipes[1]);
456 return 0;
457 #endif
458 } else { /* plain cgi... do fork */
459 child_pid = fork();
460 switch (child_pid) {
461 case -1:
462 /* fork unsuccessful */
463 log_error_time();
464 perror("fork");
465
466 close(pipes[0]);
467 close(pipes[1]);
468
469 send_r_error(req);
470 /* FIXME: There is aproblem here. send_r_error would work
471 for NPH and CGI, but not for GUNZIP. Fix that. */
472 /* i'd like to send_r_error, but.... */
473 return 0;
474 break;
475 case 0:
476 /* child */
477 if (req->is_cgi == CGI || req->is_cgi == NPH || req->is_cgi == CGI_ACTION)
478 {
479 int l;
480 char *newpath;
481 char *c;
482
483 c = strrchr(req->pathname, '/');
484 if (!c) {
485 /* there will always be a '.' */
486 log_error_time();
487 WARN("unable to find '/' in req->pathname");
488 close(pipes[1]);
489 _exit(1);
490 }
491
492 *c = '\0';
493
494 if (chdir(req->pathname) != 0) {
495 log_error_time();
496 perror("chdir");
497 close(pipes[1]);
498 _exit(1);
499 }
500
501 req->pathname = ++c;
502 l = strlen(req->pathname) + 3;
503 /* prefix './' */
504 newpath = malloc(sizeof(char) * l);
505 if (!newpath) {
506 /* there will always be a '.' */
507 log_error_time();
508 perror("unable to malloc for newpath");
509 close(pipes[1]);
510 _exit(1);
511 }
512 newpath[0] = '.';
513 newpath[1] = '/';
514 memcpy(&newpath[2], req->pathname, l - 2); /* includes the trailing '\0' */
515 req->pathname = newpath;
516 }
517
518 /* close the 'read' end of the pipes[] */
519 close(pipes[0]);
520
521 /* tie cgi's STDOUT to our write end of pipe */
522 if (dup2(pipes[1], STDOUT_FILENO) == -1) {
523 log_error_time();
524 perror("dup2 - pipes");
525 _exit(1);
526 }
527 close(pipes[1]);
528
529 /* tie post_data_fd to POST stdin */
530 if (req->method == M_POST) { /* tie stdin to file */
531 if (req->post_data_fd.pipe==0) {
532 lseek(req->post_data_fd.fds[0], SEEK_SET, 0);
533 }
534
535 dup2(req->post_data_fd.fds[0], STDIN_FILENO);
536 close_tmp_fd( &req->post_data_fd);
537 }
538
539 umask(cgi_umask); /* change umask *again* u=rwx,g=rxw,o= */
540
541 /*
542 * tie STDERR to cgi_log_fd
543 * cgi_log_fd will automatically close, close-on-exec rocks!
544 * if we don't tied STDERR (current log_error) to cgi_log_fd,
545 * then we ought to close it.
546 */
547 if (cgi_log_fd) {
548 dup2(cgi_log_fd, STDERR_FILENO);
549 close( cgi_log_fd);
550 }
551
552 if (req->is_cgi == NPH || req->is_cgi == CGI) {
553 char *aargv[CGI_ARGC_MAX + 1];
554 create_argv(req, aargv);
555 execve(req->pathname, aargv, req->cgi_env);
556 } else if (req->is_cgi == CGI_ACTION) {
557 char *aargv[CGI_ARGC_MAX + 2];
558 aargv[0] = req->action;
559 create_argv(req, &aargv[1]);
560 execve(req->action, aargv, req->cgi_env);
561 } else {
562 if (req->is_cgi == INDEXER_CGI)
563 execl(dirmaker, dirmaker, req->pathname, req->request_uri,
564 NULL);
565 }
566 /* execve failed */
567 WARN(req->pathname);
568 _exit(1);
569
570 break; /* it doesn't matter, we never make it until here */
571
572 default:
573 /* parent */
574 /* if here, fork was successful */
575 if (verbose_cgi_logs) {
576 log_error_time();
577 fprintf(stderr, "Forked child \"%s\" pid %d\n",
578 req->pathname, child_pid);
579 }
580
581 if (req->method == M_POST) {
582 close_tmp_fd( &req->post_data_fd);
583 }
584
585 /* NPH, etc... all go straight to the fd */
586
587 close(pipes[1]);
588 break;
589 }
590 } /* HIC */
591
592 /* we only get here in parent case, and
593 * success.
594 */
595
596 req->data_fd = pipes[0];
597
598 req->status = PIPE_READ;
599 if (req->is_cgi == CGI || req->is_cgi == HIC_CGI || req->is_cgi == CGI_ACTION)
600 {
601 req->cgi_status = CGI_PARSE; /* got to parse cgi header */
602 /* for cgi_header... I get half the buffer! */
603 req->header_line = req->header_end = (req->buffer + BUFFER_SIZE / 2);
604 } else { /* HIC CGIs and NPH CGIs */
605 req->cgi_status = CGI_BUFFER;
606 /* I get all the buffer! */
607 req->header_line = req->header_end = req->buffer;
608 }
609
610 /* reset req->filepos for logging (it's used in pipe.c) */
611 /* still don't know why req->filesize might be reset though */
612 req->filepos = 0;
613
614 return 1;
615 }

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26