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

Revision 5893, 15.9 KB (checked in by jow, 3 years ago)

uhttpd: finish basic auth support, read realms from /etc/httpd.conf

Line 
1/*
2 * uhttpd - Tiny non-forking 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
375
376int main (int argc, char **argv)
377{
378#ifdef HAVE_LUA
379    /* Lua runtime */
380    lua_State *L = NULL;
381#endif
382
383    /* master file descriptor list */
384    fd_set used_fds, serv_fds, read_fds;
385
386    /* working structs */
387    struct addrinfo hints;
388    struct http_request *req;
389    struct path_info *pin;
390    struct client *cl;
391    struct sigaction sa;
392    struct config conf;
393
394    /* maximum file descriptor number */
395    int new_fd, cur_fd, max_fd = 0;
396
397    int tls = 0;
398    int keys = 0;
399    int bound = 0;
400    int nofork = 0;
401
402    /* args */
403    char opt;
404    char bind[128];
405    char *port = NULL;
406
407    /* clear the master and temp sets */
408    FD_ZERO(&used_fds);
409    FD_ZERO(&serv_fds);
410    FD_ZERO(&read_fds);
411
412    /* handle SIGPIPE, SIGCHILD */
413    sa.sa_handler = SIG_IGN;
414    sigaction(SIGPIPE, &sa, NULL);
415    sigaction(SIGCHLD, &sa, NULL);
416
417    sa.sa_handler = uh_sigterm;
418    sigaction(SIGINT,  &sa, NULL);
419    sigaction(SIGTERM, &sa, NULL);
420
421    /* prepare addrinfo hints */
422    memset(&hints, 0, sizeof(hints));
423    hints.ai_family   = AF_UNSPEC;
424    hints.ai_socktype = SOCK_STREAM;
425    hints.ai_flags    = AI_PASSIVE;
426
427    /* parse args */
428    memset(&conf, 0, sizeof(conf));
429    memset(bind, 0, sizeof(bind));
430
431#ifdef HAVE_TLS
432    /* init SSL context */
433    if( ! (conf.tls = uh_tls_ctx_init()) )
434    {
435        fprintf(stderr, "Failed to initalize SSL context\n");
436        exit(1);
437    }
438#endif
439
440    while( (opt = getopt(argc, argv, "fC:K:p:s:h:c:l:L:d:r:m:x:")) > 0 )
441    {
442        switch(opt)
443        {
444            /* [addr:]port */
445            case 'p':
446            case 's':
447                if( (port = strrchr(optarg, ':')) != NULL )
448                {
449                    if( (optarg[0] == '[') && (port > optarg) && (port[-1] == ']') )
450                        memcpy(bind, optarg + 1,
451                            min(sizeof(bind), (int)(port - optarg) - 2));
452                    else
453                        memcpy(bind, optarg,
454                            min(sizeof(bind), (int)(port - optarg)));
455
456                    port++;
457                }
458                else
459                {
460                    port = optarg;
461                }
462
463                if( opt == 's' )
464                    tls = 1;
465
466                /* bind sockets */
467                bound += uh_socket_bind(
468                    &serv_fds, &max_fd, bind[0] ? bind : NULL, port,
469                    &hints, tls, &conf
470                );
471
472                break;
473
474#ifdef HAVE_TLS
475            /* certificate */
476            case 'C':
477                if( SSL_CTX_use_certificate_file(conf.tls, optarg, SSL_FILETYPE_ASN1) < 1 )
478                {
479                    fprintf(stderr, "Invalid certificate file given\n");
480                    exit(1);
481                }
482
483                keys++;
484                break;
485
486            /* key */
487            case 'K':
488                if( SSL_CTX_use_PrivateKey_file(conf.tls, optarg, SSL_FILETYPE_ASN1) < 1 )
489                {
490                    fprintf(stderr, "Invalid private key file given\n");
491                    exit(1);
492                }
493
494                keys++;
495                break;
496#endif
497
498            /* docroot */
499            case 'h':
500                if( ! realpath(optarg, conf.docroot) )
501                {
502                    fprintf(stderr, "Invalid directory %s: %s\n", optarg, strerror(errno));
503                    exit(1);
504                }
505                break;
506
507#ifdef HAVE_CGI
508            /* cgi prefix */
509            case 'x':
510                conf.cgi_prefix = optarg;
511                break;
512#endif
513
514#ifdef HAVE_LUA
515            /* lua prefix */
516            case 'l':
517                conf.lua_prefix = optarg;
518                break;
519
520            /* lua handler */
521            case 'L':
522                conf.lua_handler = optarg;
523                break;
524#endif
525
526            /* no fork */
527            case 'f':
528                nofork = 1;
529                break;
530
531            /* urldecode */
532            case 'd':
533                if( (port = malloc(strlen(optarg)+1)) != NULL )
534                {
535                    memset(port, 0, strlen(optarg)+1);
536                    uh_urldecode(port, strlen(optarg), optarg, strlen(optarg));
537                    printf("%s", port);
538                    free(port);
539                    exit(0);
540                }
541                break;
542
543            /* basic auth realm */
544            case 'r':
545                conf.realm = optarg;
546                break;
547
548            /* md5 crypt */
549            case 'm':
550                printf("%s\n", crypt(optarg, "$1$"));
551                exit(0);
552                break;
553
554            /* config file */
555            case 'c':
556                conf.file = optarg;
557                break;
558
559            default:
560                fprintf(stderr,
561                    "Usage: %s -p [addr:]port [-h docroot]\n"
562                    "   -f              Do not fork to background\n"
563                    "   -c file         Configuration file, default is '/etc/httpd.conf'\n"
564                    "   -p [addr:]port  Bind to specified address and port, multiple allowed\n"
565#ifdef HAVE_TLS
566                    "   -s [addr:]port  Like -p but provide HTTPS on this port\n"
567                    "   -C file         ASN.1 server certificate file\n"
568                    "   -K file         ASN.1 server private key file\n"
569#endif
570                    "   -h directory    Specify the document root, default is '.'\n"
571#ifdef HAVE_LUA
572                    "   -l string       URL prefix for Lua handler, default is '/lua'\n"
573                    "   -L file         Lua handler script, omit to disable Lua\n"
574#endif
575#ifdef HAVE_CGI
576                    "   -x string       URL prefix for CGI handler, default is '/cgi-bin'\n"
577#endif
578                    "   -d string       URL decode given string\n"
579                    "   -r string       Specify basic auth realm\n"
580                    "   -m string       MD5 crypt given string\n"
581                    "\n", argv[0]
582                );
583
584                exit(1);
585        }
586    }
587
588#ifdef HAVE_TLS
589    if( (tls == 1) && (keys < 2) )
590    {
591        fprintf(stderr, "Missing private key or certificate file\n");
592        exit(1);
593    }
594#endif
595
596    if( bound < 1 )
597    {
598        fprintf(stderr, "No sockets bound, unable to continue\n");
599        exit(1);
600    }
601
602    /* default docroot */
603    if( !conf.docroot[0] && !realpath(".", conf.docroot) )
604    {
605        fprintf(stderr, "Can not determine default document root: %s\n",
606            strerror(errno));
607        exit(1);
608    }
609
610    /* default realm */
611    if( ! conf.realm )
612        conf.realm = "Protected Area";
613
614    /* config file */
615    uh_config_parse(conf.file);
616
617#ifdef HAVE_CGI
618    /* default cgi prefix */
619    if( ! conf.cgi_prefix )
620        conf.cgi_prefix = "/cgi-bin";
621#endif
622
623#ifdef HAVE_LUA
624    /* init Lua runtime if handler is specified */
625    if( conf.lua_handler )
626    {
627        /* default lua prefix */
628        if( ! conf.lua_prefix )
629            conf.lua_prefix = "/lua";
630
631        L = uh_lua_init(conf.lua_handler);
632    }
633#endif
634
635    /* fork (if not disabled) */
636    if( ! nofork )
637    {
638        switch( fork() )
639        {
640            case -1:
641                perror("fork()");
642                exit(1);
643
644            case 0:
645                /* daemon setup */
646                if( chdir("/") )
647                    perror("chdir()");
648
649                if( (cur_fd = open("/dev/null", O_WRONLY)) > -1 )
650                    dup2(cur_fd, 0);
651
652                if( (cur_fd = open("/dev/null", O_RDONLY)) > -1 )
653                    dup2(cur_fd, 1);
654
655                if( (cur_fd = open("/dev/null", O_RDONLY)) > -1 )
656                    dup2(cur_fd, 2);
657
658                break;
659
660            default:
661                exit(0);
662        }
663    }
664
665    /* backup server descriptor set */
666    used_fds = serv_fds;
667
668    /* loop */
669    while(run)
670    {
671        /* create a working copy of the used fd set */
672        read_fds = used_fds;
673
674        /* sleep until socket activity */
675        if( select(max_fd + 1, &read_fds, NULL, NULL, NULL) == -1 )
676        {
677            perror("select()");
678            exit(1);
679        }
680
681        /* run through the existing connections looking for data to be read */
682        for( cur_fd = 0; cur_fd <= max_fd; cur_fd++ )
683        {
684            /* is a socket managed by us */
685            if( FD_ISSET(cur_fd, &read_fds) )
686            {
687                /* is one of our listen sockets */
688                if( FD_ISSET(cur_fd, &serv_fds) )
689                {
690                    /* handle new connections */
691                    if( (new_fd = accept(cur_fd, NULL, 0)) != -1 )
692                    {
693                        /* add to global client list */
694                        if( (cl = uh_client_add(new_fd, uh_listener_lookup(cur_fd))) != NULL )
695                        {
696#ifdef HAVE_TLS
697                            /* setup client tls context */
698                            uh_tls_client_accept(cl);
699#endif
700
701                            /* add client socket to global fdset */
702                            FD_SET(new_fd, &used_fds);
703                            max_fd = max(max_fd, new_fd);
704                        }
705
706                        /* insufficient resources */
707                        else
708                        {
709                            fprintf(stderr,
710                                "uh_client_add(): Can not manage more than "
711                                "%i client sockets, connection dropped\n",
712                                UH_LIMIT_CLIENTS
713                            );
714
715                            close(new_fd);
716                        }
717                    }
718                }
719
720                /* is a client socket */
721                else
722                {
723                    if( ! (cl = uh_client_lookup(cur_fd)) )
724                    {
725                        /* this should not happen! */
726                        fprintf(stderr,
727                            "uh_client_lookup(): No entry for fd %i!\n",
728                            cur_fd);
729
730                        goto cleanup;
731                    }
732
733                    /* parse message header */
734                    if( (req = uh_http_header_recv(cl)) != NULL )
735                    {
736#ifdef HAVE_LUA
737                        /* Lua request? */
738                        if( L && strstr(req->url, conf.lua_prefix) == req->url )
739                        {
740                            uh_lua_request(cl, req, L);
741                        }
742                        else
743#endif
744                        /* dispatch request */
745                        if( (pin = uh_path_lookup(cl, req->url)) != NULL )
746                        {
747                            /* auth ok? */
748                            if( uh_auth_check(cl, req, pin) )
749                            {
750#ifdef HAVE_CGI
751                                if( strstr(pin->name, conf.cgi_prefix) == pin->name )
752                                {
753                                    uh_cgi_request(cl, req, pin);
754                                }
755                                else
756#endif
757                                {
758                                    uh_file_request(cl, req, pin);
759                                }
760                            }
761                        }
762
763                        /* 404 */
764                        else
765                        {
766                            uh_http_sendhf(cl, 404, "Not Found",
767                                "No such file or directory");
768                        }
769                    }
770
771                    /* 400 */
772                    else
773                    {
774                        uh_http_sendhf(cl, 400, "Bad Request",
775                            "Malformed request received");
776                    }
777
778#ifdef HAVE_TLS
779                    /* free client tls context */
780                    uh_tls_client_close(cl);
781#endif
782
783                    cleanup:
784
785                    /* close client socket */
786                    close(cur_fd);
787                    FD_CLR(cur_fd, &used_fds);
788
789                    /* remove from global client list */
790                    uh_client_remove(cur_fd);
791                }
792            }
793        }
794    }
795
796#ifdef HAVE_LUA
797    /* destroy the Lua state */
798    if( L != NULL )
799        lua_close(L);
800#endif
801
802    return 0;
803}
804
Note: See TracBrowser for help on using the browser.