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

Contents of /hydra/src/get.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.28 - (show annotations)
Sun Oct 27 10:06:28 2002 UTC (21 years, 5 months ago) by nmav
Branch: MAIN
CVS Tags: hydra_0_0_9
Changes since 1.27: +9 -1 lines
File MIME type: text/plain
Added file access control lists per virtual host. Based on patch for
Boa by Peter Korsgaard <jacmet@sunsite.dk>.

1 /*
2 * Hydra, an http server
3 * Copyright (C) 1995 Paul Phillips <paulp@go2net.com>
4 * Some changes Copyright (C) 1996,99 Larry Doolittle <ldoolitt@boa.org>
5 * Some changes Copyright (C) 1996-2002 Jon Nelson <jnelson@boa.org>
6 * Portions Copyright (C) 2002 Nikos Mavroyanopoulos <nmav@gnutls.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: get.c,v 1.27 2002/10/22 14:06:10 nmav Exp $*/
25
26 #include "boa.h"
27 #include "socket.h"
28 #include "access.h"
29
30 /* local prototypes */
31 int get_cachedir_file(request * req, stat_st *statbuf);
32 int index_directory(request * req, char *dest_filename);
33 static int check_if_stuff(request * req);
34
35 /*
36 * Name: init_get
37 * Description: Initializes a non-script GET or HEAD request.
38 *
39 * Return values:
40 * 0: finished or error, request will be freed
41 * 1: successfully initialized, added to ready queue
42 */
43
44 int init_get(server_params * params, request * req)
45 {
46 int data_fd, saved_errno;
47 stat_st statbuf;
48 volatile int bytes;
49
50 #ifdef ENABLE_ACCESS_LISTS
51 if (!access_allow(req->hostname, req->pathname)) {
52 send_r_forbidden(req);
53 return 0;
54 }
55 #endif
56
57 data_fd = open(req->pathname, O_RDONLY);
58 saved_errno = errno; /* might not get used */
59
60 if (data_fd == -1) {
61 log_error_doc(req);
62 errno = saved_errno;
63 perror("document open");
64
65 if (saved_errno == ENOENT)
66 send_r_not_found(req);
67 else if (saved_errno == EACCES)
68 send_r_forbidden(req);
69 else
70 send_r_bad_request(req);
71 return 0;
72 }
73
74 if (fstat(data_fd, &statbuf) == -1) {
75 /* this is quite impossible, since the file
76 * was opened before.
77 */
78 close(data_fd);
79 send_r_not_found(req);
80 return 0;
81 }
82
83 if (S_ISDIR(statbuf.st_mode)) { /* directory */
84 close(data_fd); /* close dir */
85
86 if (req->pathname[strlen(req->pathname) - 1] != '/') {
87 char buffer[3 * MAX_PATH_LENGTH + 128];
88
89 create_url(buffer, sizeof(buffer), req->secure, req->hostname,
90 params->server_s[req->secure].port, req->request_uri);
91
92 send_r_moved_perm(req, buffer);
93 return 0;
94 }
95 data_fd = get_dir(req, &statbuf); /* updates statbuf */
96
97 if (data_fd == -1) /* couldn't do it */
98 return 0; /* errors reported by get_dir */
99 else if (data_fd <= 1)
100 /* data_fd == 0 -> close it down, 1 -> continue */
101 return data_fd;
102 /* else, data_fd contains the fd of the file... */
103 }
104
105 req->filesize = statbuf.st_size;
106 req->last_modified = statbuf.st_mtime;
107
108 /* Check the If-Match, If-Modified etc stuff.
109 */
110 if (req->if_types)
111 if (check_if_stuff(req) == 0) {
112 close(data_fd);
113 return 0;
114 }
115 /* Move on */
116
117 if (req->range_stop == 0)
118 req->range_stop = statbuf.st_size;
119
120 /* out of range! */
121 if (req->range_start > statbuf.st_size ||
122 req->range_stop > statbuf.st_size ||
123 req->range_stop < req->range_start) {
124 send_r_range_unsatisfiable(req);
125 close(data_fd);
126 return 0;
127 }
128
129 if (req->method == M_HEAD || req->filesize == 0) {
130 send_r_request_file_ok(req);
131 close(data_fd);
132 return 0;
133 }
134
135 req->filepos = req->range_start;
136
137 if (req->range_stop > max_file_size_cache) {
138
139 if (req->range_start == 0 && req->range_stop == statbuf.st_size)
140 send_r_request_file_ok(req); /* All's well */
141 else {
142 /* if ranges were used, then lseek to the start given
143 */
144 if (lseek(data_fd, req->range_start, SEEK_SET) == (off_t) - 1) {
145 close(data_fd);
146 send_r_not_found(req);
147 return 0;
148 }
149 send_r_request_partial(req); /* All's well */
150 }
151
152 req_flush(req); /* this should *always* complete due to
153 the size of the I/O buffers */
154 req->data_fd = data_fd;
155
156 if (req->secure) {
157 req->status = PIPE_READ;
158 req->cgi_status = CGI_BUFFER;
159 } else {
160 /* This sends data directly to the socket, and cannot
161 * be used in TLS connections.
162 */
163 req->status = IOSHUFFLE;
164 }
165
166 req->header_line = req->header_end = req->buffer;
167 req->pipe_range_stop = req->range_stop;
168 return 1;
169 }
170
171 if (req->range_stop == 0) { /* done */
172 send_r_request_file_ok(req); /* All's well *so far* */
173 close(data_fd);
174 return 1;
175 }
176
177 /* NOTE: I (Jon Nelson) tried performing a read(2)
178 * into the output buffer provided the file data would
179 * fit, before mmapping, and if successful, writing that
180 * and stopping there -- all to avoid the cost
181 * of a mmap. Oddly, it was *slower* in benchmarks.
182 */
183 if (max_files_cache > 0) {
184 req->mmap_entry_var = find_mmap(data_fd, &statbuf);
185 if (req->mmap_entry_var == NULL) {
186 req->buffer_end = 0;
187 if (errno == ENOENT)
188 send_r_not_found(req);
189 else if (errno == EACCES)
190 send_r_forbidden(req);
191 else
192 send_r_bad_request(req);
193 close(data_fd);
194 return 0;
195 }
196 req->data_mem = req->mmap_entry_var->mmap;
197 } else { /* File caching is disabled.
198 */
199 req->data_mem =
200 mmap(0, req->range_stop, PROT_READ, MAP_OPTIONS, data_fd, 0);
201 }
202
203 close(data_fd); /* close data file */
204
205 if (req->data_mem == MAP_FAILED) {
206 boa_perror(req, "mmap");
207 return 0;
208 }
209
210 if (req->range_start == 0 && req->range_stop == statbuf.st_size)
211 send_r_request_file_ok(req); /* All's well */
212 else
213 send_r_request_partial(req); /* All's well */
214
215 bytes = BUFFER_SIZE - req->buffer_end;
216
217 /* bytes is now how much the buffer can hold
218 * after the headers
219 */
220
221 if (bytes > 0) {
222 if (bytes > req->range_stop - req->range_start)
223 bytes = req->range_stop - req->range_start;
224
225 if (setjmp(params->env) == 0) {
226 params->handle_sigbus = 1;
227 memcpy(req->buffer + req->buffer_end,
228 &req->data_mem[req->filepos], bytes);
229 params->handle_sigbus = 0;
230 /* OK, SIGBUS **after** this point is very bad! */
231 } else {
232 char buf[30];
233 /* sigbus! */
234 log_error_doc(req);
235 reset_output_buffer(req);
236 send_r_error(req);
237 get_commonlog_time(buf);
238 fprintf(stderr, "%sGot SIGBUS in memcpy!\n", buf);
239 return 0;
240 }
241 req->buffer_end += bytes;
242 req->filepos += bytes;
243 if (req->range_stop == req->filepos) {
244 req_flush(req);
245 req->status = DONE;
246 }
247 }
248
249 /* We lose statbuf here, so make sure response has been sent */
250 return 1;
251 }
252
253 /*
254 * Name: check_if_stuff
255 * Description: Checks the If-Match, If-None-Match headers
256 *
257 * req->last_modified, and req->filesize MUST have been set
258 * before calling this function. This function should be called
259 * if req->if_types != 0.
260 *
261 * Return values:
262 * 1: Successful, continue sending the file
263 * 0: unsuccessful. We send the appropriate stuff. Close the connection.
264 */
265
266 static int check_if_stuff(request * req)
267 {
268 int comp = 0;
269 char *broken_etag[MAX_COMMA_SEP_ELEMENTS];
270 int broken_etag_size, i;
271 char new_etag[MAX_ETAG_LENGTH];
272
273 /* Although we allow multiple If-* directives to be used, we
274 * actually use only one. The priority used is shown below.
275 */
276
277 /* First try IF_MODIFIED_SINCE
278 */
279 if (req->if_types & IF_MODIFIED_SINCE) {
280 if (!modified_since(req->last_modified, req->if_modified_since)) {
281 send_r_not_modified(req);
282 return 0;
283 }
284 return 1;
285 }
286
287
288 /* Then try IF_MATCH
289 */
290 if (req->if_types & IF_MATCH) {
291
292 /* Check for the "*"
293 */
294 if (strncmp(req->if_match_etag, "\"*\"", 3) == 0) {
295 comp = 0; /* comparison is always ok */
296 } else {
297
298 /* Create the current ETag of the file.
299 */
300 create_etag(req->filesize, req->last_modified, new_etag);
301
302 /* Check if one of the ETags sent, match ours
303 */
304 break_comma_list(req->if_match_etag, broken_etag,
305 &broken_etag_size);
306
307 comp = 1;
308 for (i = 0; i < broken_etag_size; i++) {
309 comp = strcmp(broken_etag[i], new_etag);
310 if (comp == 0) /* matches! */
311 break;
312 }
313
314
315 }
316
317 if (comp == 0)
318 return 1;
319 send_r_precondition_failed(req);
320 return 0;
321 }
322
323 /* Then try IF_RANGE
324 */
325 if (req->if_types & IF_RANGE) {
326 if (req->if_range_etag[0] == '"') { /* ETag may contain a date, if If-Range
327 * was used.
328 */
329 /* Check for the "*"
330 */
331 if (strncmp(req->if_range_etag, "\"*\"", 3) == 0) {
332 comp = 0; /* comparison is always ok */
333 } else {
334
335 /* Create the current ETag
336 */
337 create_etag(req->filesize, req->last_modified, new_etag);
338
339 /* Check if one of the ETags sent, match ours
340 */
341
342 break_comma_list(req->if_range_etag, broken_etag,
343 &broken_etag_size);
344
345 comp = 1;
346 for (i = 0; i < broken_etag_size; i++) {
347 comp = strcmp(broken_etag[i], new_etag);
348 if (comp == 0) /* matches! */
349 break;
350 }
351
352
353 }
354 } else {
355 comp = modified_since(req->last_modified, req->if_range_etag);
356 }
357
358
359 /* File didn't change */
360 if (comp == 0)
361 return 1;
362
363 /* File has been changed, but it is Ok, so send the whole
364 * file.
365 */
366 req->range_start = req->range_stop = 0;
367 return 1;
368 }
369
370 /* Then try IF_NONE_MATCH
371 */
372 if (req->if_types & IF_NONE_MATCH) {
373 /* Check for the "*"
374 */
375 if (strncmp(req->if_none_match_etag, "\"*\"", 3) == 0) {
376 comp = 0; /* comparison is always ok */
377 } else {
378
379 /* Create the current ETag
380 */
381 create_etag(req->filesize, req->last_modified, new_etag);
382
383 /* Check if one of the ETags sent, match ours
384 */
385
386 break_comma_list(req->if_none_match_etag, broken_etag,
387 &broken_etag_size);
388
389 comp = 1;
390 for (i = 0; i < broken_etag_size; i++) {
391 comp = strcmp(broken_etag[i], new_etag);
392 if (comp == 0) /* matches! */
393 break;
394 }
395
396
397 }
398
399 if (comp == 0) {
400 send_r_not_modified(req);
401 return 0;
402 } else { /* it was modified */
403 send_r_precondition_failed(req);
404 return 0;
405 }
406 }
407
408 /* Unsupported type ? */
409
410 return 1; /* do the request */
411 }
412
413 /*
414 * Name: process_get
415 * Description: Writes a chunk of data to the socket.
416 *
417 * Return values:
418 * -1: request blocked, move to blocked queue
419 * 0: EOF or error, close it down
420 * 1: successful write, recycle in ready queue
421 */
422
423 int process_get(server_params * params, request * req)
424 {
425 int bytes_written;
426 volatile int bytes_to_write;
427
428 bytes_to_write = req->range_stop - req->filepos;
429 if (bytes_to_write > system_bufsize)
430 bytes_to_write = system_bufsize;
431
432
433 if (setjmp(params->env) == 0) {
434 params->handle_sigbus = 1;
435
436 bytes_written =
437 socket_send(req, req->data_mem + req->filepos, bytes_to_write);
438
439 params->handle_sigbus = 0;
440 /* OK, SIGBUS **after** this point is very bad! */
441 } else {
442 char buf[30];
443 /* sigbus! */
444 log_error_doc(req);
445 /* sending an error here is inappropriate
446 * if we are here, the file is mmapped, and thus,
447 * a content-length has been sent. If we send fewer bytes
448 * the client knows there has been a problem.
449 * We run the risk of accidentally sending the right number
450 * of bytes (or a few too many) and the client
451 * won't be the wiser.
452 */
453 req->status = DEAD;
454 get_commonlog_time(buf);
455 fprintf(stderr, "%sGot SIGBUS in write(2)!\n", buf);
456 return 0;
457 }
458
459 if (bytes_written < 0) {
460 if (errno == EWOULDBLOCK || errno == EAGAIN)
461 return -1;
462 /* request blocked at the pipe level, but keep going */
463 else {
464 if (errno != EPIPE) {
465 log_error_doc(req);
466 /* Can generate lots of log entries, */
467 perror("write");
468 /* OK to disable if your logs get too big */
469 }
470 req->status = DEAD;
471 return 0;
472 }
473 }
474 req->filepos += bytes_written;
475
476 if (req->filepos == req->range_stop) { /* EOF */
477 return 0;
478 } else
479 return 1; /* more to do */
480 }
481
482 /*
483 * Name: get_dir
484 * Description: Called from process_get if the request is a directory.
485 * statbuf must describe directory on input, since we may need its
486 * device, inode, and mtime.
487 * statbuf is updated, since we may need to check mtimes of a cache.
488 * returns:
489 * -1 error
490 * 0 cgi (either gunzip or auto-generated)
491 * >0 file descriptor of file
492 */
493
494 int get_dir(request * req, stat_st *statbuf)
495 {
496
497 char *directory_index;
498 int data_fd;
499
500 directory_index =
501 find_and_open_directory_index(req->pathname, 0, &data_fd);
502
503 if (directory_index) { /* look for index.html first?? */
504 if (data_fd != -1) { /* user's index file */
505 int ret;
506
507 /* Check if we can execute the file
508 */
509
510 strcat(req->request_uri, directory_index);
511
512 ret = is_executable_cgi(req, directory_index);
513 if (ret != 0) { /* it is a CGI */
514 close(data_fd); /* we don't need it */
515 if (ret == -1) {
516 send_r_not_found(req);
517 return -1;
518 }
519 return init_cgi(req);
520 }
521
522 /* Not a cgi */
523
524 fstat(data_fd, statbuf);
525 return data_fd;
526 }
527 if (errno == EACCES) {
528 send_r_forbidden(req);
529 return -1;
530 } else if (errno != ENOENT) {
531 /* if there is an error *other* than EACCES or ENOENT */
532 send_r_not_found(req);
533 return -1;
534 }
535 }
536
537 /* only here if index.html, index.html.gz don't exist */
538 if (dirmaker != NULL) { /* don't look for index.html... maybe automake? */
539 req->response_status = R_REQUEST_OK;
540 SQUASH_KA(req);
541
542 /* the indexer should take care of all headers */
543 if (!req->simple) {
544 req_write(req, HTTP_VERSION " 200 OK\r\n");
545 print_http_headers(req);
546 print_last_modified(req);
547 req_write(req, "Content-Type: " TEXT_HTML CRLF CRLF);
548 req_flush(req);
549 }
550 if (req->method == M_HEAD)
551 return 0;
552
553 req->is_cgi = INDEXER_CGI;
554 return init_cgi(req);
555 /* in this case, 0 means success */
556 } else if (cachedir) {
557 return get_cachedir_file(req, statbuf);
558 } else { /* neither index.html nor autogenerate are allowed */
559 send_r_forbidden(req);
560 return -1; /* nothing worked */
561 }
562 }
563
564 int get_cachedir_file(request * req, stat_st *statbuf)
565 {
566
567 char pathname_with_index[MAX_PATH_LENGTH];
568 int data_fd;
569 time_t real_dir_mtime;
570
571 real_dir_mtime = statbuf->st_mtime;
572 sprintf(pathname_with_index, "%s/dir.%u.%lu",
573 cachedir, (unsigned int) statbuf->st_dev,
574 (unsigned long int)statbuf->st_ino);
575 data_fd = open(pathname_with_index, O_RDONLY);
576
577 if (data_fd != -1) { /* index cache */
578
579 fstat(data_fd, statbuf);
580 if (statbuf->st_mtime > real_dir_mtime) {
581 statbuf->st_mtime = real_dir_mtime; /* lie */
582 strcpy(req->request_uri, find_default_directory_index()); /* for mimetype */
583 return data_fd;
584 }
585 close(data_fd);
586 unlink(pathname_with_index); /* cache is stale, delete it */
587 }
588 if (index_directory(req, pathname_with_index) == -1)
589 return -1;
590
591 data_fd = open(pathname_with_index, O_RDONLY); /* Last chance */
592 if (data_fd != -1) {
593 strcpy(req->request_uri, find_default_directory_index()); /* for mimetype */
594 fstat(data_fd, statbuf);
595 statbuf->st_mtime = real_dir_mtime; /* lie */
596 return data_fd;
597 }
598
599 boa_perror(req, "re-opening dircache");
600 return -1; /* Nothing worked. */
601
602 }
603
604 /*
605 * Name: index_directory
606 * Description: Called from get_cachedir_file if a directory html
607 * has to be generated on the fly
608 * returns -1 for problem, else 0
609 * This version is the fastest, ugliest, and most accurate yet.
610 * It solves the "stale size or type" problem by not ever giving
611 * the size or type. This also speeds it up since no per-file
612 * stat() is required.
613 */
614
615 int index_directory(request * req, char *dest_filename)
616 {
617 DIR *request_dir;
618 FILE *fdstream;
619 struct dirent *dirbuf;
620 int bytes = 0;
621 char *escname = NULL;
622
623 if (chdir(req->pathname) == -1) {
624 if (errno == EACCES || errno == EPERM) {
625 send_r_forbidden(req);
626 } else {
627 log_error_doc(req);
628 perror("chdir");
629 send_r_bad_request(req);
630 }
631 return -1;
632 }
633
634 request_dir = opendir(".");
635 if (request_dir == NULL) {
636 int errno_save = errno;
637 send_r_error(req);
638 log_error_time();
639 fprintf(stderr, "directory \"%s\": ", req->pathname);
640 errno = errno_save;
641 perror("opendir");
642 return -1;
643 }
644
645 fdstream = fopen(dest_filename, "w");
646 if (fdstream == NULL) {
647 boa_perror(req, "dircache fopen");
648 closedir(request_dir);
649 return -1;
650 }
651
652 bytes += fprintf(fdstream,
653 "<HTML><HEAD>\n<TITLE>Index of %s</TITLE>\n</HEAD>\n\n",
654 req->request_uri);
655 bytes +=
656 fprintf(fdstream, "<BODY>\n\n<H2>Index of %s</H2>\n\n<PRE>\n",
657 req->request_uri);
658
659 while ((dirbuf = readdir(request_dir))) {
660 if (!strcmp(dirbuf->d_name, "."))
661 continue;
662
663 if (!strcmp(dirbuf->d_name, "..")) {
664 bytes += fprintf(fdstream,
665 " [DIR] <A HREF=\"../\">Parent Directory</A>\n");
666 continue;
667 }
668
669 if ((escname = escape_string(dirbuf->d_name, NULL)) != NULL) {
670 bytes += fprintf(fdstream, " <A HREF=\"%s\">%s</A>\n",
671 escname, dirbuf->d_name);
672 free(escname);
673 escname = NULL;
674 }
675 }
676 closedir(request_dir);
677 bytes += fprintf(fdstream, "</PRE>\n\n</BODY>\n</HTML>\n");
678
679 fclose(fdstream);
680
681 chdir(server_root);
682
683 req->filesize = bytes; /* for logging transfer size */
684 return 0; /* success */
685 }

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26