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

Contents of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.22 - (show annotations)
Mon Oct 21 18:46:26 2002 UTC (21 years, 5 months ago) by nmav
Branch: MAIN
CVS Tags: hydra_0_0_8
Changes since 1.21: +50 -38 lines
File MIME type: text/plain
Added several stuff from Boa 0.94.14rc1

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.21 2002/10/06 09:42:50 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 lseek(req->post_data_fd, SEEK_SET, 0);
437
438 if (hic_send_command(req, pipes[1]) == -1) {
439 log_error_time();
440 fprintf(stderr, "Error in HIC\n");
441 close(pipes[0]);
442 close(pipes[1]);
443 return 0;
444 }
445 #else /* No hic! */
446 close(pipes[0]);
447 close(pipes[1]);
448 return 0;
449 #endif
450 } else { /* plain cgi... do fork */
451 child_pid = fork();
452 switch (child_pid) {
453 case -1:
454 /* fork unsuccessful */
455 log_error_time();
456 perror("fork");
457
458 close(pipes[0]);
459 close(pipes[1]);
460
461 send_r_error(req);
462 /* FIXME: There is aproblem here. send_r_error would work
463 for NPH and CGI, but not for GUNZIP. Fix that. */
464 /* i'd like to send_r_error, but.... */
465 return 0;
466 break;
467 case 0:
468 /* child */
469 if (req->is_cgi == CGI || req->is_cgi == NPH) {
470 int l;
471 char *newpath;
472 char *c;
473
474 c = strrchr(req->pathname, '/');
475 if (!c) {
476 /* there will always be a '.' */
477 log_error_time();
478 WARN("unable to find '/' in req->pathname");
479 close(pipes[1]);
480 _exit(1);
481 }
482
483 *c = '\0';
484
485 if (chdir(req->pathname) != 0) {
486 log_error_time();
487 perror("chdir");
488 close(pipes[1]);
489 _exit(1);
490 }
491
492 req->pathname = ++c;
493 l = strlen(req->pathname) + 3;
494 /* prefix './' */
495 newpath = malloc(sizeof(char) * l);
496 if (!newpath) {
497 /* there will always be a '.' */
498 log_error_time();
499 perror("unable to malloc for newpath");
500 close(pipes[1]);
501 _exit(1);
502 }
503 newpath[0] = '.';
504 newpath[1] = '/';
505 memcpy(&newpath[2], req->pathname, l - 2); /* includes the trailing '\0' */
506 req->pathname = newpath;
507 }
508
509 /* close the 'read' end of the pipes[] */
510 close(pipes[0]);
511
512 /* tie cgi's STDOUT to our write end of pipe */
513 if (dup2(pipes[1], STDOUT_FILENO) == -1) {
514 log_error_time();
515 perror("dup2 - pipes");
516 _exit(1);
517 }
518 close(pipes[1]);
519
520 /* tie post_data_fd to POST stdin */
521 if (req->method == M_POST) { /* tie stdin to file */
522 lseek(req->post_data_fd, SEEK_SET, 0);
523 dup2(req->post_data_fd, STDIN_FILENO);
524 close(req->post_data_fd);
525 }
526
527 umask(cgi_umask); /* change umask *again* u=rwx,g=rxw,o= */
528
529 /*
530 * tie STDERR to cgi_log_fd
531 * cgi_log_fd will automatically close, close-on-exec rocks!
532 * if we don't tied STDERR (current log_error) to cgi_log_fd,
533 * then we ought to close it.
534 */
535 if (cgi_log_fd) {
536 dup2(cgi_log_fd, STDERR_FILENO);
537 close( cgi_log_fd);
538 }
539
540 if (req->is_cgi == NPH || req->is_cgi == CGI) {
541 char *aargv[CGI_ARGC_MAX + 1];
542 create_argv(req, aargv);
543 execve(req->pathname, aargv, req->cgi_env);
544 } else {
545 if (req->is_cgi == INDEXER_CGI)
546 execl(dirmaker, dirmaker, req->pathname, req->request_uri,
547 NULL);
548 }
549 /* execve failed */
550 WARN(req->pathname);
551 _exit(1);
552
553 break; /* it doesn't matter, we never make it until here */
554
555 default:
556 /* parent */
557 /* if here, fork was successful */
558 if (verbose_cgi_logs) {
559 log_error_time();
560 fprintf(stderr, "Forked child \"%s\" pid %d\n",
561 req->pathname, child_pid);
562 }
563
564 if (req->method == M_POST) {
565 close(req->post_data_fd); /* child closed it too */
566 req->post_data_fd = -1;
567 }
568
569 /* NPH, etc... all go straight to the fd */
570
571 close(pipes[1]);
572 break;
573 }
574 } /* HIC */
575
576 /* we only get here in parent case, and
577 * success.
578 */
579
580 req->data_fd = pipes[0];
581
582 req->status = PIPE_READ;
583 if (req->is_cgi == CGI || req->is_cgi == HIC_CGI) {
584 req->cgi_status = CGI_PARSE; /* got to parse cgi header */
585 /* for cgi_header... I get half the buffer! */
586 req->header_line = req->header_end = (req->buffer + BUFFER_SIZE / 2);
587 } else { /* HIC CGIs and NPH CGIs */
588 req->cgi_status = CGI_BUFFER;
589 /* I get all the buffer! */
590 req->header_line = req->header_end = req->buffer;
591 }
592
593 /* reset req->filepos for logging (it's used in pipe.c) */
594 /* still don't know why req->filesize might be reset though */
595 req->filepos = 0;
596
597 return 1;
598 }

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26