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

Contents of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.6 - (show annotations)
Wed Sep 25 19:55:53 2002 UTC (21 years, 6 months ago) by nmav
Branch: MAIN
Changes since 1.5: +2 -7 lines
File MIME type: text/plain
Added support for multiple directory indexes. Droped support for uncompressing gziped files.

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

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26