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

Contents of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.19 - (show annotations)
Wed Oct 2 08:54:09 2002 UTC (21 years, 6 months ago) by nmav
Branch: MAIN
CVS Tags: hydra_0_0_6, hydra_0_0_5
Changes since 1.18: +2 -2 lines
File MIME type: text/plain
Several changes to allow setting the correct query string in HIC cgis.

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

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26