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

Contents of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.7 - (show annotations)
Wed Sep 25 22:10:03 2002 UTC (21 years, 6 months ago) by nmav
Branch: MAIN
CVS Tags: hydra_0_0_2
Changes since 1.6: +428 -443 lines
File MIME type: text/plain
Cleaned up CGI stuff.

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

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26