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

Contents of /hydra/src/cgi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.28 - (show annotations)
Sat Jul 24 17:35:37 2004 UTC (19 years, 8 months ago) by nmav
Branch: MAIN
CVS Tags: hydra_0_1_7, hydra_0_1_6
Changes since 1.27: +4 -1 lines
File MIME type: text/plain
* Some changes to support the new PHP REQUEST_STATUS environment
  variable.
* Some fixes for gnutls.

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.27 2003/03/06 12:04:59 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 ix = 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[ix++] = env_gen_extra("PATH",
61 ((cgi_path !=
62 NULL) ? cgi_path :
63 DEFAULT_PATH), 0);
64 common_cgi_env[ix++] =
65 env_gen_extra("SERVER_SOFTWARE", SERVER_NAME "/" SERVER_VERSION, 0);
66 common_cgi_env[ix++] =
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[ix++] = env_gen_extra("SERVER_ROOT", server_root);
77 #endif
78
79 /* APACHE added */
80 common_cgi_env[ix++] =
81 env_gen_extra("SERVER_ADMIN", server_admin, 0);
82 common_cgi_env[ix] = NULL;
83
84 /* Sanity checking -- make *sure* the memory got allocated */
85 if (ix > 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 < ix; ++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_doc(req);
168 fprintf(stderr, "Unable to generate additional CGI Environment"
169 "variable -- ran out of memory!\n");
170 return 0;
171 }
172 if (prefix_len)
173 memcpy(p, "HTTP_", 5);
174 req->cgi_env[req->cgi_env_index++] = p;
175 return 1;
176 } else {
177 log_error_doc(req);
178 fprintf(stderr, "Unable to generate additional CGI Environment"
179 "variable -- not enough space!\n");
180 }
181 return 0;
182 }
183
184 #define my_add_cgi_env(req, key, value) { \
185 int ok = add_cgi_env(req, key, value, 0); \
186 if (!ok) return 0; \
187 }
188
189 const char *hydra_method_str(int method)
190 {
191 char *w;
192 switch (method) {
193 case M_POST:
194 w = "POST";
195 break;
196 case M_HEAD:
197 w = "HEAD";
198 break;
199 case M_GET:
200 w = "GET";
201 break;
202 default:
203 w = "UNKNOWN";
204 break;
205 }
206 return w;
207 }
208
209 /*
210 * Name: complete_env
211 *
212 * Description: adds the known client header env variables
213 * and terminates the environment array
214 */
215
216 int complete_env(request * req)
217 {
218 int i;
219 char buf[22];
220 const char *w;
221
222 for (i = 0; common_cgi_env[i]; i++)
223 req->cgi_env[i] = common_cgi_env[i];
224
225 w = hydra_method_str(req->method);
226 my_add_cgi_env(req, "REQUEST_METHOD", w);
227
228 if (req->action)
229 my_add_cgi_env(req, "REDIRECT_STATUS", "200");
230
231 if (req->secure) {
232 simple_itoa(ssl_port, buf);
233 } else {
234 simple_itoa(server_port, buf);
235 }
236 my_add_cgi_env(req, "SERVER_PORT", buf);
237 my_add_cgi_env(req, "SERVER_NAME", req->hostname);
238
239 /* NCSA and APACHE added -- not in CGI spec */
240 #ifdef USE_NCSA_CGI_ENV
241 my_add_cgi_env( req, "DOCUMENT_ROOT", req->document_root);
242 #endif
243
244 my_add_cgi_env(req, "SERVER_ADDR", req->local_ip_addr);
245 my_add_cgi_env(req, "SERVER_PROTOCOL", req->http_version_str);
246 my_add_cgi_env(req, "REQUEST_URI", req->request_uri);
247
248 if (req->path_info)
249 my_add_cgi_env(req, "PATH_INFO", req->path_info);
250
251 if (req->path_translated)
252 /* while path_translated depends on path_info,
253 * there are cases when path_translated might
254 * not exist when path_info does
255 */
256 my_add_cgi_env(req, "PATH_TRANSLATED", req->path_translated);
257
258 my_add_cgi_env(req, "SCRIPT_NAME", req->script_name);
259
260 if (req->query_string)
261 my_add_cgi_env(req, "QUERY_STRING", req->query_string);
262 my_add_cgi_env(req, "REMOTE_ADDR", req->remote_ip_addr);
263
264 simple_itoa(req->remote_port, buf);
265 my_add_cgi_env(req, "REMOTE_PORT", buf);
266
267 if (req->method == M_POST) {
268 if (req->content_type) {
269 my_add_cgi_env(req, "CONTENT_TYPE", req->content_type);
270 } else {
271 my_add_cgi_env(req, "CONTENT_TYPE", default_type);
272 }
273 if (req->content_length) {
274 my_add_cgi_env(req, "CONTENT_LENGTH", req->content_length);
275 }
276 }
277 #ifdef ACCEPT_ON
278 if (req->accept[0])
279 my_add_cgi_env(req, "HTTP_ACCEPT", req->accept);
280 #endif
281
282 if (req->cgi_env_index < CGI_ENV_MAX + 1) {
283 req->cgi_env[req->cgi_env_index] = NULL; /* terminate */
284 return 1;
285 }
286 log_error_time();
287 fprintf(stderr, "Not enough space in CGI environment for remainder"
288 " of variables.\n");
289 return 0;
290 }
291
292 /*
293 * Name: make_args_cgi
294 *
295 * Build argv list for a CGI script according to spec
296 *
297 */
298
299 void create_argv(request * req, char **aargv)
300 {
301 char *p, *q, *r;
302 int aargc;
303
304 q = req->query_string;
305 aargv[0] = req->pathname;
306
307 /* here, we handle a special "indexed" query string.
308 * Taken from the CGI/1.1 SPEC:
309 * This is identified by a GET or HEAD request with a query string
310 * with no *unencoded* '=' in it.
311 * For such a request, I'm supposed to parse the search string
312 * into words, according to the following rules:
313
314 search-string = search-word *( "+" search-word )
315 search-word = 1*schar
316 schar = xunreserved | escaped | xreserved
317 xunreserved = alpha | digit | xsafe | extra
318 xsafe = "$" | "-" | "_" | "."
319 xreserved = ";" | "/" | "?" | ":" | "@" | "&"
320
321 After parsing, each word is URL-decoded, optionally encoded in a system
322 defined manner, and then the argument list
323 is set to the list of words.
324
325
326 Thus, schar is alpha|digit|"$"|"-"|"_"|"."|";"|"/"|"?"|":"|"@"|"&"
327
328 As of this writing, escape.pl escapes the following chars:
329
330 "-", "_", ".", "!", "~", "*", "'", "(", ")",
331 "0".."9", "A".."Z", "a".."z",
332 ";", "/", "?", ":", "@", "&", "=", "+", "\$", ","
333
334 Which therefore means
335 "=", "+", "~", "!", "*", "'", "(", ")", ","
336 are *not* escaped and should be?
337 Wait, we don't do any escaping, and nor should we.
338 According to the RFC draft, we unescape and then re-escape
339 in a "system defined manner" (here: none).
340
341 The CGI/1.1 draft (03, latest is 1999???) is very unclear here.
342
343 I am using the latest published RFC, 2396, for what does and does
344 not need escaping.
345
346 Since boa builds the argument list and does not call /bin/sh,
347 (boa uses execve for CGI)
348 */
349
350 if (q && !strchr(q, '=')) {
351 /* we have an 'index' style */
352 q = strdup(q);
353 if (!q) {
354 WARN("unable to strdup 'q' in create_argv!");
355 }
356 for (aargc = 1; q && (aargc < CGI_ARGC_MAX);) {
357 r = q;
358 /* for an index-style CGI, + is used to seperate arguments
359 * an escaped '+' is of no concern to us
360 */
361 if ((p = strchr(q, '+'))) {
362 *p = '\0';
363 q = p + 1;
364 } else {
365 q = NULL;
366 }
367 if (unescape_uri(r, NULL)) {
368 /* printf("parameter %d: %s\n",aargc,r); */
369 aargv[aargc++] = r;
370 }
371 }
372 aargv[aargc] = NULL;
373 } else {
374 aargv[1] = NULL;
375 }
376 }
377
378
379 /*
380 * Name: init_cgi
381 *
382 * Description: Called for GET/POST requests that refer to ScriptAlias
383 * directories or application/x-httpd-cgi files. Pipes are used for the
384 * communication with the child.
385 * stderr remains tied to our log file; is this good?
386 *
387 * Returns:
388 * 0 - error or NPH, either way the socket is closed
389 * 1 - success
390 */
391
392 int init_cgi(request * req)
393 {
394 int child_pid;
395 int pipes[2];
396
397 SQUASH_KA(req);
398
399 if (req->is_cgi == NPH || req->is_cgi == CGI || req->is_cgi == HIC_CGI
400 || req->is_cgi == CGI_ACTION)
401 {
402 if (req->secure && complete_env_ssl(req) == 0) {
403 return 0;
404 }
405 if (complete_env(req) == 0) {
406 return 0;
407 }
408 }
409 #ifdef FASCIST_LOGGING
410 {
411 int i;
412 for (i = 0; i < req->cgi_env_index; ++i)
413 fprintf(stderr, "%s - environment variable for cgi: \"%s\"\n",
414 __FILE__, req->cgi_env[i]);
415 }
416 #endif
417
418 if (req->is_cgi) {
419 if (pipe(pipes) == -1) {
420 log_error_time();
421 perror("pipe");
422 return 0;
423 }
424
425 /* set the read end of the socket to non-blocking */
426 if (set_nonblock_fd(pipes[0]) == -1) {
427 log_error_time();
428 perror("cgi-fcntl");
429 close(pipes[0]);
430 close(pipes[1]);
431 return 0;
432 }
433 } else {
434 log_error_time();
435 fprintf(stderr, "Non CGI in init_cgi()!\n");
436 return 0;
437 }
438
439 if (req->is_cgi == HIC_CGI) { /* internally handled cgi */
440 #ifdef ENABLE_HIC
441 /* Move the post_data_fd pointer to start.
442 */
443 if (req->post_data_fd.pipe==0) {
444 if (lseek(req->post_data_fd.fds[0], SEEK_SET, 0) == (off_t)-1) {
445 log_error_time();
446 perror("lseek");
447 close(pipes[0]);
448 close(pipes[1]);
449 return 0;
450 }
451 } else { /* close the write end */
452 close(req->post_data_fd.fds[1]);
453 req->post_data_fd.fds[1] = -1;
454 }
455
456 if (hic_send_command(req, pipes[1]) == -1) {
457 log_error_time();
458 fprintf(stderr, "Error executing HIC command.\n");
459 close(pipes[0]);
460 close(pipes[1]);
461 return 0;
462 }
463 #else /* No hic! */
464 close(pipes[0]);
465 close(pipes[1]);
466 return 0;
467 #endif
468 } else { /* plain cgi... do fork */
469 child_pid = fork();
470 switch (child_pid) {
471 case -1:
472 /* fork unsuccessful */
473 log_error_time();
474 perror("fork");
475
476 close(pipes[0]);
477 close(pipes[1]);
478
479 send_r_error(req);
480 /* FIXME: There is aproblem here. send_r_error would work
481 for NPH and CGI, but not for GUNZIP. Fix that. */
482 /* i'd like to send_r_error, but.... */
483 return 0;
484 break;
485 case 0:
486 /* child */
487 if (req->is_cgi == CGI || req->is_cgi == NPH || req->is_cgi == CGI_ACTION)
488 {
489 int l;
490 char *newpath;
491 char *c;
492
493 c = strrchr(req->pathname, '/');
494 if (!c) {
495 /* there will always be a '.' */
496 log_error_time();
497 WARN("unable to find '/' in req->pathname");
498 close(pipes[1]);
499 _exit(1);
500 }
501
502 *c = '\0';
503
504 if (chdir(req->pathname) != 0) {
505 log_error_time();
506 perror("chdir");
507 close(pipes[1]);
508 _exit(1);
509 }
510
511 req->pathname = ++c;
512 l = strlen(req->pathname) + 3;
513 /* prefix './' */
514 newpath = malloc(sizeof(char) * l);
515 if (!newpath) {
516 /* there will always be a '.' */
517 log_error_time();
518 perror("unable to malloc for newpath");
519 close(pipes[1]);
520 _exit(1);
521 }
522 newpath[0] = '.';
523 newpath[1] = '/';
524 memcpy(&newpath[2], req->pathname, l - 2); /* includes the trailing '\0' */
525 req->pathname = newpath;
526 }
527
528 /* close the 'read' end of the pipes[] */
529 close(pipes[0]);
530
531 /* tie cgi's STDOUT to our write end of pipe */
532 if (dup2(pipes[1], STDOUT_FILENO) == -1) {
533 log_error_time();
534 perror("dup2 - pipes");
535 _exit(1);
536 }
537 close(pipes[1]);
538
539 /* tie post_data_fd to POST stdin */
540 if (req->method == M_POST) { /* tie stdin to file */
541 if (req->post_data_fd.pipe==0) {
542 if (lseek(req->post_data_fd.fds[0], SEEK_SET, 0) == (off_t)-1) {
543 log_error_time();
544 perror("lseek");
545 _exit(1);
546 }
547 }
548
549 dup2(req->post_data_fd.fds[0], STDIN_FILENO);
550 close_tmp_fd( &req->post_data_fd);
551 }
552
553 umask(cgi_umask); /* change umask *again* u=rwx,g=rxw,o= */
554
555 /*
556 * tie STDERR to cgi_log_fd
557 * cgi_log_fd will automatically close, close-on-exec rocks!
558 * if we don't tied STDERR (current log_error) to cgi_log_fd,
559 * then we ought to close it.
560 */
561 if (cgi_log_fd) {
562 dup2(cgi_log_fd, STDERR_FILENO);
563 close( cgi_log_fd);
564 }
565
566 if (req->is_cgi == NPH || req->is_cgi == CGI) {
567 char *aargv[CGI_ARGC_MAX + 1];
568 create_argv(req, aargv);
569 execve(req->pathname, aargv, req->cgi_env);
570 } else if (req->is_cgi == CGI_ACTION) {
571 char *aargv[CGI_ARGC_MAX + 2];
572 aargv[0] = req->action;
573 create_argv(req, &aargv[1]);
574 execve(req->action, aargv, req->cgi_env);
575 } else {
576 if (req->is_cgi == INDEXER_CGI)
577 execl(dirmaker, dirmaker, req->pathname, req->request_uri,
578 NULL);
579 }
580 /* execve failed */
581 WARN(req->pathname);
582 _exit(1);
583
584 break; /* it doesn't matter, we never make it until here */
585
586 default:
587 /* parent */
588 /* if here, fork was successful */
589 if (verbose_cgi_logs) {
590 log_error_time();
591 fprintf(stderr, "Forked child \"%s\" pid %d\n",
592 req->pathname, child_pid);
593 }
594
595 if (req->method == M_POST) {
596 close_tmp_fd( &req->post_data_fd);
597 }
598
599 /* NPH, etc... all go straight to the fd */
600
601 close(pipes[1]);
602 break;
603 }
604 } /* HIC */
605
606 /* we only get here in parent case, and
607 * success.
608 */
609
610 req->data_fd = pipes[0];
611
612 req->status = PIPE_READ;
613 if (req->is_cgi == CGI || req->is_cgi == HIC_CGI || req->is_cgi == CGI_ACTION)
614 {
615 req->cgi_status = CGI_PARSE; /* got to parse cgi header */
616 /* for cgi_header... I get half the buffer! */
617 req->header_line = req->header_end = (req->buffer + BUFFER_SIZE / 2);
618 } else { /* HIC CGIs and NPH CGIs */
619 req->cgi_status = CGI_BUFFER;
620 /* I get all the buffer! */
621 req->header_line = req->header_end = req->buffer;
622 }
623
624 /* reset req->filepos for logging (it's used in pipe.c) */
625 /* still don't know why req->filesize might be reset though */
626 req->filepos = 0;
627
628 return 1;
629 }

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26