root/luci/trunk/contrib/package/uhttpd/src/uhttpd.c @ 5901

Revision 5901, 16.1 KB (checked in by jow, 3 years ago)

uhttpd: make Lua handler more CGI like and fork child away

Line 
1/*
2 * uhttpd - Tiny single-threaded httpd - Main component
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#define _XOPEN_SOURCE 500   /* crypt() */
20
21#include "uhttpd.h"
22#include "uhttpd-utils.h"
23#include "uhttpd-file.h"
24
25#ifdef HAVE_CGI
26#include "uhttpd-cgi.h"
27#endif
28
29#ifdef HAVE_LUA
30#include "uhttpd-lua.h"
31#endif
32
33#ifdef HAVE_TLS
34#include "uhttpd-tls.h"
35#endif
36
37
38static int run = 1;
39
40static void uh_sigterm(int sig)
41{
42    run = 0;
43}
44
45static void uh_config_parse(const char *path)
46{
47    FILE *c;
48    char line[512];
49    char *user = NULL;
50    char *pass = NULL;
51    char *eol  = NULL;
52
53    if( (c = fopen(path ? path : "/etc/httpd.conf", "r")) != NULL )
54    {
55        memset(line, 0, sizeof(line));
56
57        while( fgets(line, sizeof(line) - 1, c) )
58        {
59            if( (line[0] == '/') && (strchr(line, ':') != NULL) )
60            {
61                if( !(user = strchr(line, ':')) || (*user++ = 0) ||
62                    !(pass = strchr(user, ':')) || (*pass++ = 0) ||
63                    !(eol = strchr(pass, '\n')) || (*eol++  = 0) )
64                        continue;
65
66                if( !uh_auth_add(line, user, pass) )
67                {
68                    fprintf(stderr,
69                        "Can not manage more than %i basic auth realms, "
70                        "will skip the rest\n", UH_LIMIT_AUTHREALMS
71                    );
72
73                    break;
74                } 
75            }
76        }
77
78        fclose(c);
79    }
80}
81
82static int uh_socket_bind(
83    fd_set *serv_fds, int *max_fd, const char *host, const char *port,
84    struct addrinfo *hints, int do_tls, struct config *conf
85) {
86    int sock = -1;
87    int yes = 1;
88    int status;
89    int bound = 0;
90
91    struct listener *l = NULL;
92    struct addrinfo *addrs = NULL, *p = NULL;
93
94    if( (status = getaddrinfo(host, port, hints, &addrs)) != 0 )
95    {
96        fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(status));
97    }
98
99    /* try to bind a new socket to each found address */
100    for( p = addrs; p; p = p->ai_next )
101    {
102        /* get the socket */
103        if( (sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1 )
104        {
105            perror("socket()");
106            goto error;
107        }
108
109        /* "address already in use" */
110        if( setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1 )
111        {
112            perror("setsockopt()");
113            goto error;
114        }
115
116        /* required to get parallel v4 + v6 working */
117        if( p->ai_family == AF_INET6 )
118        {
119            if( setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) == -1 )
120            {
121                perror("setsockopt()");
122                goto error;
123            }
124        }
125
126        /* bind */
127        if( bind(sock, p->ai_addr, p->ai_addrlen) == -1 )
128        {
129            perror("bind()");
130            goto error;
131        }
132
133        /* listen */
134        if( listen(sock, UH_LIMIT_CLIENTS) == -1 )
135        {
136            perror("listen()");
137            goto error;
138        }
139
140        /* add listener to global list */
141        if( ! (l = uh_listener_add(sock, conf)) )
142        {
143            fprintf(stderr,
144                "uh_listener_add(): Can not create more than "
145                "%i listen sockets\n", UH_LIMIT_LISTENERS
146            );
147
148            goto error;
149        }
150
151#ifdef HAVE_TLS
152        /* init TLS */
153        l->tls = do_tls ? conf->tls : NULL;
154#endif
155
156        /* add socket to server fd set */
157        FD_SET(sock, serv_fds);
158        *max_fd = max(*max_fd, sock);
159
160        bound++;
161        continue;
162
163        error:
164        if( sock > 0 )
165            close(sock);
166    }
167
168    freeaddrinfo(addrs);
169
170    return bound;
171}
172
173static struct http_request * uh_http_header_parse(struct client *cl, char *buffer, int buflen)
174{
175    char *method  = &buffer[0];
176    char *path    = NULL;
177    char *version = NULL;
178
179    char *headers = NULL;
180    char *hdrname = NULL;
181    char *hdrdata = NULL;
182
183    int i;
184    int hdrcount = 0;
185
186    static struct http_request req;
187
188    memset(&req, 0, sizeof(req));
189
190
191    /* terminate initial header line */
192    if( (headers = strfind(buffer, buflen, "\r\n", 2)) != NULL )
193    {
194        buffer[buflen-1] = 0;
195
196        *headers++ = 0;
197        *headers++ = 0;
198
199        /* find request path */
200        if( (path = strchr(buffer, ' ')) != NULL )
201            *path++ = 0;
202
203        /* find http version */
204        if( (path != NULL) && ((version = strchr(path, ' ')) != NULL) )
205            *version++ = 0;
206
207
208        /* check method */
209        if( strcmp(method, "GET") && strcmp(method, "HEAD") && strcmp(method, "POST") )
210        {
211            /* invalid method */
212            uh_http_response(cl, 405, "Method Not Allowed");
213            return NULL;
214        }
215        else
216        {
217            switch(method[0])
218            {
219                case 'G':
220                    req.method = UH_HTTP_MSG_GET;
221                    break;
222
223                case 'H':
224                    req.method = UH_HTTP_MSG_HEAD;
225                    break;
226
227                case 'P':
228                    req.method = UH_HTTP_MSG_POST;
229                    break;
230            }
231        }
232
233        /* check path */
234        if( !path || !strlen(path) )
235        {
236            /* malformed request */
237            uh_http_response(cl, 400, "Bad Request");
238            return NULL;
239        }
240        else
241        {
242            req.url = path;
243        }
244
245        /* check version */
246        if( strcmp(version, "HTTP/0.9") && strcmp(version, "HTTP/1.0") && strcmp(version, "HTTP/1.1") )
247        {
248            /* unsupported version */
249            uh_http_response(cl, 400, "Bad Request");
250            return NULL;
251        }
252        else
253        {
254            req.version = strtof(&version[5], NULL);
255        }
256
257
258        /* process header fields */
259        for( i = (int)(headers - buffer); i < buflen; i++ )
260        {
261            /* found eol and have name + value, push out header tuple */
262            if( hdrname && hdrdata && (buffer[i] == '\r' || buffer[i] == '\n') )
263            {
264                buffer[i] = 0;
265
266                /* store */
267                if( (hdrcount + 1) < array_size(req.headers) )
268                {
269                    req.headers[hdrcount++] = hdrname;
270                    req.headers[hdrcount++] = hdrdata;
271
272                    hdrname = hdrdata = NULL;
273                }
274
275                /* too large */
276                else
277                {
278                    uh_http_response(cl, 413, "Request Entity Too Large");
279                    return NULL;
280                }
281            }
282
283            /* have name but no value and found a colon, start of value */
284            else if( hdrname && !hdrdata && ((i+2) < buflen) &&
285                (buffer[i] == ':') && (buffer[i+1] == ' ')
286            ) {
287                buffer[i] = 0;
288                hdrdata = &buffer[i+2];
289            }
290
291            /* have no name and found [A-Z], start of name */
292            else if( !hdrname && isalpha(buffer[i]) && isupper(buffer[i]) )
293            {
294                hdrname = &buffer[i];
295            }
296        }
297
298        /* valid enough */
299        return &req;
300    }
301
302    /* Malformed request */
303    uh_http_response(cl, 400, "Bad Request");
304    return NULL;
305}
306
307
308static struct http_request * uh_http_header_recv(struct client *cl)
309{
310    static char buffer[UH_LIMIT_MSGHEAD];
311    char *bufptr = &buffer[0];
312    char *idxptr = NULL;
313
314    struct timeval timeout;
315
316    fd_set reader;
317
318    ssize_t blen = sizeof(buffer)-1;
319    ssize_t rlen = 0;
320
321
322    memset(buffer, 0, sizeof(buffer));
323
324    while( blen > 0 )
325    {
326        FD_ZERO(&reader);
327        FD_SET(cl->socket, &reader);
328
329        /* fail after 0.1s */
330        timeout.tv_sec  = 0;
331        timeout.tv_usec = 100000;
332
333        /* check whether fd is readable */
334        if( select(cl->socket + 1, &reader, NULL, NULL, &timeout) > 0 )
335        {
336            /* receive data */
337            rlen = uh_tcp_peek(cl, bufptr, blen);
338
339            if( rlen > 0 )
340            {
341                if( (idxptr = strfind(buffer, sizeof(buffer), "\r\n\r\n", 4)) )
342                {
343                    blen -= uh_tcp_recv(cl, bufptr, (int)(idxptr - bufptr) + 4);
344
345                    /* header read complete ... */
346                    return uh_http_header_parse(cl, buffer, sizeof(buffer) - blen - 1);
347                }
348                else
349                {
350                    rlen = uh_tcp_recv(cl, bufptr, rlen);
351                    blen -= rlen;
352                    bufptr += rlen;
353                }
354            }
355            else
356            {
357                /* invalid request (unexpected eof/timeout) */
358                uh_http_response(cl, 408, "Request Timeout");
359                return NULL;
360            }
361        }
362        else
363        {
364            /* invalid request (unexpected eof/timeout) */
365            uh_http_response(cl, 408, "Request Timeout");
366            return NULL;
367        }
368    }
369
370    /* request entity too large */
371    uh_http_response(cl, 413, "Request Entity Too Large");
372    return NULL;
373}
374
375static int uh_path_match(const char *prefix, const char *url)
376{
377    if( (strstr(url, prefix) == url) &&
378        ((prefix[strlen(prefix)-1] == '/') ||
379         (strlen(url) == strlen(prefix))   ||
380         (url[strlen(prefix)] == '/'))
381    ) {
382        return 1;
383    }
384
385    return 0;
386}
387
388
389int main (int argc, char **argv)
390{
391#ifdef HAVE_LUA
392    /* Lua runtime */
393    lua_State *L = NULL;
394#endif
395
396    /* master file descriptor list */
397    fd_set used_fds, serv_fds, read_fds;
398
399    /* working structs */
400    struct addrinfo hints;
401    struct http_request *req;
402    struct path_info *pin;
403    struct client *cl;
404    struct sigaction sa;
405    struct config conf;
406
407    /* maximum file descriptor number */
408    int new_fd, cur_fd, max_fd = 0;
409
410    int tls = 0;
411    int keys = 0;
412    int bound = 0;
413    int nofork = 0;
414
415    /* args */
416    char opt;
417    char bind[128];
418    char *port = NULL;
419
420    /* clear the master and temp sets */
421    FD_ZERO(&used_fds);
422    FD_ZERO(&serv_fds);
423    FD_ZERO(&read_fds);
424
425    /* handle SIGPIPE, SIGCHILD */
426    sa.sa_handler = SIG_IGN;
427    sigaction(SIGPIPE, &sa, NULL);
428    sigaction(SIGCHLD, &sa, NULL);
429
430    sa.sa_handler = uh_sigterm;
431    sigaction(SIGINT,  &sa, NULL);
432    sigaction(SIGTERM, &sa, NULL);
433
434    /* prepare addrinfo hints */
435    memset(&hints, 0, sizeof(hints));
436    hints.ai_family   = AF_UNSPEC;
437    hints.ai_socktype = SOCK_STREAM;
438    hints.ai_flags    = AI_PASSIVE;
439
440    /* parse args */
441    memset(&conf, 0, sizeof(conf));
442    memset(bind, 0, sizeof(bind));
443
444#ifdef HAVE_TLS
445    /* init SSL context */
446    if( ! (conf.tls = uh_tls_ctx_init()) )
447    {
448        fprintf(stderr, "Failed to initalize SSL context\n");
449        exit(1);
450    }
451#endif
452
453    while( (opt = getopt(argc, argv, "fC:K:p:s:h:c:l:L:d:r:m:x:")) > 0 )
454    {
455        switch(opt)
456        {
457            /* [addr:]port */
458            case 'p':
459            case 's':
460                if( (port = strrchr(optarg, ':')) != NULL )
461                {
462                    if( (optarg[0] == '[') && (port > optarg) && (port[-1] == ']') )
463                        memcpy(bind, optarg + 1,
464                            min(sizeof(bind), (int)(port - optarg) - 2));
465                    else
466                        memcpy(bind, optarg,
467                            min(sizeof(bind), (int)(port - optarg)));
468
469                    port++;
470                }
471                else
472                {
473                    port = optarg;
474                }
475
476                if( opt == 's' )
477                    tls = 1;
478
479                /* bind sockets */
480                bound += uh_socket_bind(
481                    &serv_fds, &max_fd, bind[0] ? bind : NULL, port,
482                    &hints, tls, &conf
483                );
484
485                break;
486
487#ifdef HAVE_TLS
488            /* certificate */
489            case 'C':
490                if( SSL_CTX_use_certificate_file(conf.tls, optarg, SSL_FILETYPE_ASN1) < 1 )
491                {
492                    fprintf(stderr, "Invalid certificate file given\n");
493                    exit(1);
494                }
495
496                keys++;
497                break;
498
499            /* key */
500            case 'K':
501                if( SSL_CTX_use_PrivateKey_file(conf.tls, optarg, SSL_FILETYPE_ASN1) < 1 )
502                {
503                    fprintf(stderr, "Invalid private key file given\n");
504                    exit(1);
505                }
506
507                keys++;
508                break;
509#endif
510
511            /* docroot */
512            case 'h':
513                if( ! realpath(optarg, conf.docroot) )
514                {
515                    fprintf(stderr, "Invalid directory %s: %s\n", optarg, strerror(errno));
516                    exit(1);
517                }
518                break;
519
520#ifdef HAVE_CGI
521            /* cgi prefix */
522            case 'x':
523                conf.cgi_prefix = optarg;
524                break;
525#endif
526
527#ifdef HAVE_LUA
528            /* lua prefix */
529            case 'l':
530                conf.lua_prefix = optarg;
531                break;
532
533            /* lua handler */
534            case 'L':
535                conf.lua_handler = optarg;
536                break;
537#endif
538
539            /* no fork */
540            case 'f':
541                nofork = 1;
542                break;
543
544            /* urldecode */
545            case 'd':
546                if( (port = malloc(strlen(optarg)+1)) != NULL )
547                {
548                    memset(port, 0, strlen(optarg)+1);
549                    uh_urldecode(port, strlen(optarg), optarg, strlen(optarg));
550                    printf("%s", port);
551                    free(port);
552                    exit(0);
553                }
554                break;
555
556            /* basic auth realm */
557            case 'r':
558                conf.realm = optarg;
559                break;
560
561            /* md5 crypt */
562            case 'm':
563                printf("%s\n", crypt(optarg, "$1$"));
564                exit(0);
565                break;
566
567            /* config file */
568            case 'c':
569                conf.file = optarg;
570                break;
571
572            default:
573                fprintf(stderr,
574                    "Usage: %s -p [addr:]port [-h docroot]\n"
575                    "   -f              Do not fork to background\n"
576                    "   -c file         Configuration file, default is '/etc/httpd.conf'\n"
577                    "   -p [addr:]port  Bind to specified address and port, multiple allowed\n"
578#ifdef HAVE_TLS
579                    "   -s [addr:]port  Like -p but provide HTTPS on this port\n"
580                    "   -C file         ASN.1 server certificate file\n"
581                    "   -K file         ASN.1 server private key file\n"
582#endif
583                    "   -h directory    Specify the document root, default is '.'\n"
584#ifdef HAVE_LUA
585                    "   -l string       URL prefix for Lua handler, default is '/lua'\n"
586                    "   -L file         Lua handler script, omit to disable Lua\n"
587#endif
588#ifdef HAVE_CGI
589                    "   -x string       URL prefix for CGI handler, default is '/cgi-bin'\n"
590#endif
591                    "   -d string       URL decode given string\n"
592                    "   -r string       Specify basic auth realm\n"
593                    "   -m string       MD5 crypt given string\n"
594                    "\n", argv[0]
595                );
596
597                exit(1);
598        }
599    }
600
601#ifdef HAVE_TLS
602    if( (tls == 1) && (keys < 2) )
603    {
604        fprintf(stderr, "Missing private key or certificate file\n");
605        exit(1);
606    }
607#endif
608
609    if( bound < 1 )
610    {
611        fprintf(stderr, "No sockets bound, unable to continue\n");
612        exit(1);
613    }
614
615    /* default docroot */
616    if( !conf.docroot[0] && !realpath(".", conf.docroot) )
617    {
618        fprintf(stderr, "Can not determine default document root: %s\n",
619            strerror(errno));
620        exit(1);
621    }
622
623    /* default realm */
624    if( ! conf.realm )
625        conf.realm = "Protected Area";
626
627    /* config file */
628    uh_config_parse(conf.file);
629
630#ifdef HAVE_CGI
631    /* default cgi prefix */
632    if( ! conf.cgi_prefix )
633        conf.cgi_prefix = "/cgi-bin";
634#endif
635
636#ifdef HAVE_LUA
637    /* init Lua runtime if handler is specified */
638    if( conf.lua_handler )
639    {
640        /* default lua prefix */
641        if( ! conf.lua_prefix )
642            conf.lua_prefix = "/lua";
643
644        L = uh_lua_init(conf.lua_handler);
645    }
646#endif
647
648    /* fork (if not disabled) */
649    if( ! nofork )
650    {
651        switch( fork() )
652        {
653            case -1:
654                perror("fork()");
655                exit(1);
656
657            case 0:
658                /* daemon setup */
659                if( chdir("/") )
660                    perror("chdir()");
661
662                if( (cur_fd = open("/dev/null", O_WRONLY)) > -1 )
663                    dup2(cur_fd, 0);
664
665                if( (cur_fd = open("/dev/null", O_RDONLY)) > -1 )
666                    dup2(cur_fd, 1);
667
668                if( (cur_fd = open("/dev/null", O_RDONLY)) > -1 )
669                    dup2(cur_fd, 2);
670
671                break;
672
673            default:
674                exit(0);
675        }
676    }
677
678    /* backup server descriptor set */
679    used_fds = serv_fds;
680
681    /* loop */
682    while(run)
683    {
684        /* create a working copy of the used fd set */
685        read_fds = used_fds;
686
687        /* sleep until socket activity */
688        if( select(max_fd + 1, &read_fds, NULL, NULL, NULL) == -1 )
689        {
690            perror("select()");
691            exit(1);
692        }
693
694        /* run through the existing connections looking for data to be read */
695        for( cur_fd = 0; cur_fd <= max_fd; cur_fd++ )
696        {
697            /* is a socket managed by us */
698            if( FD_ISSET(cur_fd, &read_fds) )
699            {
700                /* is one of our listen sockets */
701                if( FD_ISSET(cur_fd, &serv_fds) )
702                {
703                    /* handle new connections */
704                    if( (new_fd = accept(cur_fd, NULL, 0)) != -1 )
705                    {
706                        /* add to global client list */
707                        if( (cl = uh_client_add(new_fd, uh_listener_lookup(cur_fd))) != NULL )
708                        {
709#ifdef HAVE_TLS
710                            /* setup client tls context */
711                            uh_tls_client_accept(cl);
712#endif
713
714                            /* add client socket to global fdset */
715                            FD_SET(new_fd, &used_fds);
716                            max_fd = max(max_fd, new_fd);
717                        }
718
719                        /* insufficient resources */
720                        else
721                        {
722                            fprintf(stderr,
723                                "uh_client_add(): Can not manage more than "
724                                "%i client sockets, connection dropped\n",
725                                UH_LIMIT_CLIENTS
726                            );
727
728                            close(new_fd);
729                        }
730                    }
731                }
732
733                /* is a client socket */
734                else
735                {
736                    if( ! (cl = uh_client_lookup(cur_fd)) )
737                    {
738                        /* this should not happen! */
739                        fprintf(stderr,
740                            "uh_client_lookup(): No entry for fd %i!\n",
741                            cur_fd);
742
743                        goto cleanup;
744                    }
745
746                    /* parse message header */
747                    if( (req = uh_http_header_recv(cl)) != NULL )
748                    {
749#ifdef HAVE_LUA
750                        /* Lua request? */
751                        if( L && uh_path_match(conf.lua_prefix, req->url) )
752                        {
753                            uh_lua_request(cl, req, L);
754                        }
755                        else
756#endif
757                        /* dispatch request */
758                        if( (pin = uh_path_lookup(cl, req->url)) != NULL )
759                        {
760                            /* auth ok? */
761                            if( uh_auth_check(cl, req, pin) )
762                            {
763#ifdef HAVE_CGI
764                                if( uh_path_match(conf.cgi_prefix, pin->name) )
765                                {
766                                    uh_cgi_request(cl, req, pin);
767                                }
768                                else
769#endif
770                                {
771                                    uh_file_request(cl, req, pin);
772                                }
773                            }
774                        }
775
776                        /* 404 */
777                        else
778                        {
779                            uh_http_sendhf(cl, 404, "Not Found",
780                                "No such file or directory");
781                        }
782                    }
783
784                    /* 400 */
785                    else
786                    {
787                        uh_http_sendhf(cl, 400, "Bad Request",
788                            "Malformed request received");
789                    }
790
791#ifdef HAVE_TLS
792                    /* free client tls context */
793                    uh_tls_client_close(cl);
794#endif
795
796                    cleanup:
797
798                    /* close client socket */
799                    close(cur_fd);
800                    FD_CLR(cur_fd, &used_fds);
801
802                    /* remove from global client list */
803                    uh_client_remove(cur_fd);
804                }
805            }
806        }
807    }
808
809#ifdef HAVE_LUA
810    /* destroy the Lua state */
811    if( L != NULL )
812        lua_close(L);
813#endif
814
815    return 0;
816}
817
Note: See TracBrowser for help on using the browser.