1 | /* |
2 | * uhttpd - Tiny single-threaded httpd - CGI handler |
3 | * |
4 | * Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org> |
5 | * |
6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
7 | * you may not use this file except in compliance with the License. |
8 | * You may obtain a copy of the License at |
9 | * |
10 | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | * |
12 | * Unless required by applicable law or agreed to in writing, software |
13 | * distributed under the License is distributed on an "AS IS" BASIS, |
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
15 | * See the License for the specific language governing permissions and |
16 | * limitations under the License. |
17 | */ |
18 | |
19 | #include "uhttpd.h" |
20 | #include "uhttpd-utils.h" |
21 | #include "uhttpd-cgi.h" |
22 | |
23 | static struct http_response * uh_cgi_header_parse(char *buf, int len, int *off) |
24 | { |
25 | char *bufptr = NULL; |
26 | char *hdrname = NULL; |
27 | int hdrcount = 0; |
28 | int pos = 0; |
29 | |
30 | static struct http_response res; |
31 | |
32 | |
33 | if( ((bufptr = strfind(buf, len, "\r\n\r\n", 4)) != NULL) || |
34 | ((bufptr = strfind(buf, len, "\n\n", 2)) != NULL) |
35 | ) { |
36 | *off = (int)(bufptr - buf) + ((bufptr[0] == '\r') ? 4 : 2); |
37 | |
38 | memset(&res, 0, sizeof(res)); |
39 | |
40 | res.statuscode = 200; |
41 | res.statusmsg = "OK"; |
42 | |
43 | bufptr = &buf[0]; |
44 | |
45 | for( pos = 0; pos < len; pos++ ) |
46 | { |
47 | if( !hdrname && (buf[pos] == ':') ) |
48 | { |
49 | buf[pos++] = 0; |
50 | |
51 | if( (pos < len) && (buf[pos] == ' ') ) |
52 | pos++; |
53 | |
54 | if( pos < len ) |
55 | { |
56 | hdrname = bufptr; |
57 | bufptr = &buf[pos]; |
58 | } |
59 | } |
60 | |
61 | else if( (buf[pos] == '\r') || (buf[pos] == '\n') ) |
62 | { |
63 | buf[pos++] = 0; |
64 | |
65 | if( ! hdrname ) |
66 | break; |
67 | |
68 | if( (pos < len) && (buf[pos] == '\n') ) |
69 | pos++; |
70 | |
71 | if( pos <= len ) |
72 | { |
73 | if( (hdrcount + 1) < array_size(res.headers) ) |
74 | { |
75 | if( ! strcasecmp(hdrname, "Status") ) |
76 | { |
77 | res.statuscode = atoi(bufptr); |
78 | |
79 | if( res.statuscode < 100 ) |
80 | res.statuscode = 200; |
81 | |
82 | if( ((bufptr = strchr(bufptr, ' ')) != NULL) && (&bufptr[1] != 0) ) |
83 | res.statusmsg = &bufptr[1]; |
84 | } |
85 | else |
86 | { |
87 | res.headers[hdrcount++] = hdrname; |
88 | res.headers[hdrcount++] = bufptr; |
89 | } |
90 | |
91 | bufptr = &buf[pos]; |
92 | hdrname = NULL; |
93 | } |
94 | else |
95 | { |
96 | return NULL; |
97 | } |
98 | } |
99 | } |
100 | } |
101 | |
102 | return &res; |
103 | } |
104 | |
105 | return NULL; |
106 | } |
107 | |
108 | static char * uh_cgi_header_lookup(struct http_response *res, const char *hdrname) |
109 | { |
110 | int i; |
111 | |
112 | foreach_header(i, res->headers) |
113 | { |
114 | if( ! strcasecmp(res->headers[i], hdrname) ) |
115 | return res->headers[i+1]; |
116 | } |
117 | |
118 | return NULL; |
119 | } |
120 | |
121 | static int uh_cgi_error_500(struct client *cl, struct http_request *req, const char *message) |
122 | { |
123 | if( uh_http_sendf(cl, NULL, |
124 | "HTTP/%.1f 500 Internal Server Error\r\n" |
125 | "Content-Type: text/plain\r\n%s\r\n", |
126 | req->version, |
127 | (req->version > 1.0) |
128 | ? "Transfer-Encoding: chunked\r\n" : "" |
129 | ) >= 0 |
130 | ) { |
131 | return uh_http_send(cl, req, message, -1); |
132 | } |
133 | |
134 | return -1; |
135 | } |
136 | |
137 | |
138 | void uh_cgi_request( |
139 | struct client *cl, struct http_request *req, |
140 | struct path_info *pi, struct interpreter *ip |
141 | ) { |
142 | int i, hdroff, bufoff, rv; |
143 | int hdrlen = 0; |
144 | int buflen = 0; |
145 | int fd_max = 0; |
146 | int content_length = 0; |
147 | int header_sent = 0; |
148 | |
149 | int rfd[2] = { 0, 0 }; |
150 | int wfd[2] = { 0, 0 }; |
151 | |
152 | char buf[UH_LIMIT_MSGHEAD]; |
153 | char hdr[UH_LIMIT_MSGHEAD]; |
154 | |
155 | pid_t child; |
156 | |
157 | fd_set reader; |
158 | fd_set writer; |
159 | |
160 | struct sigaction sa; |
161 | struct timeval timeout; |
162 | struct http_response *res; |
163 | |
164 | |
165 | /* spawn pipes for me->child, child->me */ |
166 | if( (pipe(rfd) < 0) || (pipe(wfd) < 0) ) |
167 | { |
168 | uh_http_sendhf(cl, 500, "Internal Server Error", |
169 | "Failed to create pipe: %s", strerror(errno)); |
170 | |
171 | if( rfd[0] > 0 ) close(rfd[0]); |
172 | if( rfd[1] > 0 ) close(rfd[1]); |
173 | if( wfd[0] > 0 ) close(wfd[0]); |
174 | if( wfd[1] > 0 ) close(wfd[1]); |
175 | |
176 | return; |
177 | } |
178 | |
179 | /* fork off child process */ |
180 | switch( (child = fork()) ) |
181 | { |
182 | /* oops */ |
183 | case -1: |
184 | uh_http_sendhf(cl, 500, "Internal Server Error", |
185 | "Failed to fork child: %s", strerror(errno)); |
186 | return; |
187 | |
188 | /* exec child */ |
189 | case 0: |
190 | /* restore SIGTERM */ |
191 | sa.sa_flags = 0; |
192 | sa.sa_handler = SIG_DFL; |
193 | sigemptyset(&sa.sa_mask); |
194 | sigaction(SIGTERM, &sa, NULL); |
195 | |
196 | /* close loose pipe ends */ |
197 | close(rfd[0]); |
198 | close(wfd[1]); |
199 | |
200 | /* patch stdout and stdin to pipes */ |
201 | dup2(rfd[1], 1); |
202 | dup2(wfd[0], 0); |
203 | |
204 | /* check for regular, world-executable file _or_ interpreter */ |
205 | if( ((pi->stat.st_mode & S_IFREG) && |
206 | (pi->stat.st_mode & S_IXOTH)) || (ip != NULL) |
207 | ) { |
208 | /* build environment */ |
209 | clearenv(); |
210 | |
211 | /* common information */ |
212 | setenv("GATEWAY_INTERFACE", "CGI/1.1", 1); |
213 | setenv("SERVER_SOFTWARE", "uHTTPd", 1); |
214 | setenv("PATH", "/sbin:/usr/sbin:/bin:/usr/bin", 1); |
215 | |
216 | #ifdef HAVE_TLS |
217 | /* https? */ |
218 | if( cl->tls ) |
219 | setenv("HTTPS", "on", 1); |
220 | #endif |
221 | |
222 | /* addresses */ |
223 | setenv("SERVER_NAME", sa_straddr(&cl->servaddr), 1); |
224 | setenv("SERVER_ADDR", sa_straddr(&cl->servaddr), 1); |
225 | setenv("SERVER_PORT", sa_strport(&cl->servaddr), 1); |
226 | setenv("REMOTE_HOST", sa_straddr(&cl->peeraddr), 1); |
227 | setenv("REMOTE_ADDR", sa_straddr(&cl->peeraddr), 1); |
228 | setenv("REMOTE_PORT", sa_strport(&cl->peeraddr), 1); |
229 | |
230 | /* path information */ |
231 | setenv("SCRIPT_NAME", pi->name, 1); |
232 | setenv("SCRIPT_FILENAME", pi->phys, 1); |
233 | setenv("DOCUMENT_ROOT", pi->root, 1); |
234 | setenv("QUERY_STRING", pi->query ? pi->query : "", 1); |
235 | |
236 | if( pi->info ) |
237 | setenv("PATH_INFO", pi->info, 1); |
238 | |
239 | /* REDIRECT_STATUS, php-cgi wants it */ |
240 | switch( req->redirect_status ) |
241 | { |
242 | case 404: |
243 | setenv("REDIRECT_STATUS", "404", 1); |
244 | break; |
245 | |
246 | default: |
247 | setenv("REDIRECT_STATUS", "200", 1); |
248 | break; |
249 | } |
250 | |
251 | /* http version */ |
252 | if( req->version > 1.0 ) |
253 | setenv("SERVER_PROTOCOL", "HTTP/1.1", 1); |
254 | else |
255 | setenv("SERVER_PROTOCOL", "HTTP/1.0", 1); |
256 | |
257 | /* request method */ |
258 | switch( req->method ) |
259 | { |
260 | case UH_HTTP_MSG_GET: |
261 | setenv("REQUEST_METHOD", "GET", 1); |
262 | break; |
263 | |
264 | case UH_HTTP_MSG_HEAD: |
265 | setenv("REQUEST_METHOD", "HEAD", 1); |
266 | break; |
267 | |
268 | case UH_HTTP_MSG_POST: |
269 | setenv("REQUEST_METHOD", "POST", 1); |
270 | break; |
271 | } |
272 | |
273 | /* request url */ |
274 | setenv("REQUEST_URI", req->url, 1); |
275 | |
276 | /* remote user */ |
277 | if( req->realm ) |
278 | setenv("REMOTE_USER", req->realm->user, 1); |
279 | |
280 | /* request message headers */ |
281 | foreach_header(i, req->headers) |
282 | { |
283 | if( ! strcasecmp(req->headers[i], "Accept") ) |
284 | setenv("HTTP_ACCEPT", req->headers[i+1], 1); |
285 | |
286 | else if( ! strcasecmp(req->headers[i], "Accept-Charset") ) |
287 | setenv("HTTP_ACCEPT_CHARSET", req->headers[i+1], 1); |
288 | |
289 | else if( ! strcasecmp(req->headers[i], "Accept-Encoding") ) |
290 | setenv("HTTP_ACCEPT_ENCODING", req->headers[i+1], 1); |
291 | |
292 | else if( ! strcasecmp(req->headers[i], "Accept-Language") ) |
293 | setenv("HTTP_ACCEPT_LANGUAGE", req->headers[i+1], 1); |
294 | |
295 | else if( ! strcasecmp(req->headers[i], "Authorization") ) |
296 | setenv("HTTP_AUTHORIZATION", req->headers[i+1], 1); |
297 | |
298 | else if( ! strcasecmp(req->headers[i], "Connection") ) |
299 | setenv("HTTP_CONNECTION", req->headers[i+1], 1); |
300 | |
301 | else if( ! strcasecmp(req->headers[i], "Cookie") ) |
302 | setenv("HTTP_COOKIE", req->headers[i+1], 1); |
303 | |
304 | else if( ! strcasecmp(req->headers[i], "Host") ) |
305 | setenv("HTTP_HOST", req->headers[i+1], 1); |
306 | |
307 | else if( ! strcasecmp(req->headers[i], "Referer") ) |
308 | setenv("HTTP_REFERER", req->headers[i+1], 1); |
309 | |
310 | else if( ! strcasecmp(req->headers[i], "User-Agent") ) |
311 | setenv("HTTP_USER_AGENT", req->headers[i+1], 1); |
312 | |
313 | else if( ! strcasecmp(req->headers[i], "Content-Type") ) |
314 | setenv("CONTENT_TYPE", req->headers[i+1], 1); |
315 | |
316 | else if( ! strcasecmp(req->headers[i], "Content-Length") ) |
317 | setenv("CONTENT_LENGTH", req->headers[i+1], 1); |
318 | } |
319 | |
320 | |
321 | /* execute child code ... */ |
322 | if( chdir(pi->root) ) |
323 | perror("chdir()"); |
324 | |
325 | if( ip != NULL ) |
326 | execl(ip->path, ip->path, pi->phys, NULL); |
327 | else |
328 | execl(pi->phys, pi->phys, NULL); |
329 | |
330 | /* in case it fails ... */ |
331 | printf( |
332 | "Status: 500 Internal Server Error\r\n\r\n" |
333 | "Unable to launch the requested CGI program:\n" |
334 | " %s: %s\n", |
335 | ip ? ip->path : pi->phys, strerror(errno) |
336 | ); |
337 | } |
338 | |
339 | /* 403 */ |
340 | else |
341 | { |
342 | printf( |
343 | "Status: 403 Forbidden\r\n\r\n" |
344 | "Access to this resource is forbidden\n" |
345 | ); |
346 | } |
347 | |
348 | close(wfd[0]); |
349 | close(rfd[1]); |
350 | exit(0); |
351 | |
352 | break; |
353 | |
354 | /* parent; handle I/O relaying */ |
355 | default: |
356 | /* close unneeded pipe ends */ |
357 | close(rfd[1]); |
358 | close(wfd[0]); |
359 | |
360 | /* max watch fd */ |
361 | fd_max = max(rfd[0], wfd[1]) + 1; |
362 | |
363 | /* find content length */ |
364 | if( req->method == UH_HTTP_MSG_POST ) |
365 | { |
366 | foreach_header(i, req->headers) |
367 | { |
368 | if( ! strcasecmp(req->headers[i], "Content-Length") ) |
369 | { |
370 | content_length = atoi(req->headers[i+1]); |
371 | break; |
372 | } |
373 | } |
374 | } |
375 | |
376 | |
377 | memset(hdr, 0, sizeof(hdr)); |
378 | |
379 | /* I/O loop, watch our pipe ends and dispatch child reads/writes from/to socket */ |
380 | while( 1 ) |
381 | { |
382 | FD_ZERO(&reader); |
383 | FD_ZERO(&writer); |
384 | |
385 | FD_SET(rfd[0], &reader); |
386 | FD_SET(wfd[1], &writer); |
387 | |
388 | timeout.tv_sec = (header_sent < 1) ? cl->server->conf->script_timeout : 3; |
389 | timeout.tv_usec = 0; |
390 | |
391 | ensure_out(rv = select_intr(fd_max, &reader, |
392 | (content_length > -1) ? &writer : NULL, NULL, &timeout)); |
393 | |
394 | /* timeout */ |
395 | if( rv == 0 ) |
396 | { |
397 | ensure_out(kill(child, 0)); |
398 | } |
399 | |
400 | /* wait until we can read or write or both */ |
401 | else if( rv > 0 ) |
402 | { |
403 | /* ready to write to cgi program */ |
404 | if( FD_ISSET(wfd[1], &writer) ) |
405 | { |
406 | /* there is unread post data waiting */ |
407 | if( content_length > 0 ) |
408 | { |
409 | /* read it from socket ... */ |
410 | ensure_out(buflen = uh_tcp_recv(cl, buf, |
411 | min(content_length, sizeof(buf)))); |
412 | |
413 | if( buflen > 0 ) |
414 | { |
415 | /* ... and write it to child's stdin */ |
416 | if( write(wfd[1], buf, buflen) < 0 ) |
417 | perror("write()"); |
418 | |
419 | content_length -= buflen; |
420 | } |
421 | |
422 | /* unexpected eof! */ |
423 | else |
424 | { |
425 | if( write(wfd[1], "", 0) < 0 ) |
426 | perror("write()"); |
427 | |
428 | content_length = 0; |
429 | } |
430 | } |
431 | |
432 | /* there is no more post data, close pipe to child's stdin */ |
433 | else if( content_length > -1 ) |
434 | { |
435 | close(wfd[1]); |
436 | content_length = -1; |
437 | } |
438 | } |
439 | |
440 | /* ready to read from cgi program */ |
441 | if( FD_ISSET(rfd[0], &reader) ) |
442 | { |
443 | /* read data from child ... */ |
444 | if( (buflen = read(rfd[0], buf, sizeof(buf))) > 0 ) |
445 | { |
446 | /* we have not pushed out headers yet, parse input */ |
447 | if( ! header_sent ) |
448 | { |
449 | /* head buffer not full and no end yet */ |
450 | if( hdrlen < sizeof(hdr) ) |
451 | { |
452 | bufoff = min(buflen, sizeof(hdr) - hdrlen); |
453 | memcpy(&hdr[hdrlen], buf, bufoff); |
454 | hdrlen += bufoff; |
455 | } |
456 | else |
457 | { |
458 | bufoff = 0; |
459 | } |
460 | |
461 | |
462 | /* try to parse header ... */ |
463 | if( (res = uh_cgi_header_parse(hdr, hdrlen, &hdroff)) != NULL ) |
464 | { |
465 | /* write status */ |
466 | ensure_out(uh_http_sendf(cl, NULL, |
467 | "HTTP/%.1f %03d %s\r\n" |
468 | "Connection: close\r\n", |
469 | req->version, res->statuscode, |
470 | res->statusmsg)); |
471 | |
472 | /* add Content-Type if no Location or Content-Type */ |
473 | if( !uh_cgi_header_lookup(res, "Location") && |
474 | !uh_cgi_header_lookup(res, "Content-Type") |
475 | ) { |
476 | ensure_out(uh_http_send(cl, NULL, |
477 | "Content-Type: text/plain\r\n", -1)); |
478 | } |
479 | |
480 | /* if request was HTTP 1.1 we'll respond chunked */ |
481 | if( (req->version > 1.0) && |
482 | !uh_cgi_header_lookup(res, "Transfer-Encoding") |
483 | ) { |
484 | ensure_out(uh_http_send(cl, NULL, |
485 | "Transfer-Encoding: chunked\r\n", -1)); |
486 | } |
487 | |
488 | /* write headers from CGI program */ |
489 | foreach_header(i, res->headers) |
490 | { |
491 | ensure_out(uh_http_sendf(cl, NULL, "%s: %s\r\n", |
492 | res->headers[i], res->headers[i+1])); |
493 | } |
494 | |
495 | /* terminate header */ |
496 | ensure_out(uh_http_send(cl, NULL, "\r\n", -1)); |
497 | |
498 | /* push out remaining head buffer */ |
499 | if( hdroff < hdrlen ) |
500 | ensure_out(uh_http_send(cl, req, &hdr[hdroff], hdrlen - hdroff)); |
501 | } |
502 | |
503 | /* ... failed and head buffer exceeded */ |
504 | else if( hdrlen >= sizeof(hdr) ) |
505 | { |
506 | ensure_out(uh_cgi_error_500(cl, req, |
507 | "The CGI program generated an invalid response:\n\n")); |
508 | |
509 | ensure_out(uh_http_send(cl, req, hdr, hdrlen)); |
510 | } |
511 | |
512 | /* ... failed but free buffer space, try again */ |
513 | else |
514 | { |
515 | continue; |
516 | } |
517 | |
518 | /* push out remaining read buffer */ |
519 | if( bufoff < buflen ) |
520 | ensure_out(uh_http_send(cl, req, &buf[bufoff], buflen - bufoff)); |
521 | |
522 | header_sent = 1; |
523 | continue; |
524 | } |
525 | |
526 | |
527 | /* headers complete, pass through buffer to socket */ |
528 | ensure_out(uh_http_send(cl, req, buf, buflen)); |
529 | } |
530 | |
531 | /* looks like eof from child */ |
532 | else |
533 | { |
534 | /* cgi script did not output useful stuff at all */ |
535 | if( ! header_sent ) |
536 | { |
537 | /* I would do this ... |
538 | * |
539 | * uh_cgi_error_500(cl, req, |
540 | * "The CGI program generated an " |
541 | * "invalid response:\n\n"); |
542 | * |
543 | * ... but in order to stay as compatible as possible, |
544 | * treat whatever we got as text/plain response and |
545 | * build the required headers here. |
546 | */ |
547 | |
548 | ensure_out(uh_http_sendf(cl, NULL, |
549 | "HTTP/%.1f 200 OK\r\n" |
550 | "Content-Type: text/plain\r\n" |
551 | "%s\r\n", |
552 | req->version, (req->version > 1.0) |
553 | ? "Transfer-Encoding: chunked\r\n" : "" |
554 | )); |
555 | |
556 | ensure_out(uh_http_send(cl, req, hdr, hdrlen)); |
557 | } |
558 | |
559 | /* send final chunk if we're in chunked transfer mode */ |
560 | ensure_out(uh_http_send(cl, req, "", 0)); |
561 | break; |
562 | } |
563 | } |
564 | } |
565 | |
566 | /* timeout exceeded or interrupted by SIGCHLD */ |
567 | else |
568 | { |
569 | if( (errno != EINTR) && ! header_sent ) |
570 | { |
571 | ensure_out(uh_http_sendhf(cl, 504, "Gateway Timeout", |
572 | "The CGI script took too long to produce " |
573 | "a response")); |
574 | } |
575 | |
576 | /* send final chunk if we're in chunked transfer mode */ |
577 | ensure_out(uh_http_send(cl, req, "", 0)); |
578 | |
579 | break; |
580 | } |
581 | } |
582 | |
583 | out: |
584 | close(rfd[0]); |
585 | close(wfd[1]); |
586 | |
587 | if( !kill(child, 0) ) |
588 | { |
589 | kill(child, SIGTERM); |
590 | waitpid(child, NULL, 0); |
591 | } |
592 | |
593 | break; |
594 | } |
595 | } |
596 | |
597 | |