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

Contents of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.9 - (show annotations)
Fri Sep 27 11:41:04 2002 UTC (21 years, 6 months ago) by nmav
Branch: MAIN
Changes since 1.8: +4 -4 lines
File MIME type: text/plain
*** empty log message ***

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

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26