├── logo.jpg ├── icons ├── back.gif ├── back.png ├── file.gif ├── folder.gif └── folder.png ├── src ├── dh2048.cc ├── newdh ├── multicore.h ├── tests │ ├── test1.pl │ ├── test2.pl │ ├── test4.pl │ ├── test3.pl │ ├── test5.pl │ ├── test6.pl │ ├── test9.pl │ ├── test7.pl │ ├── test8.pl │ ├── test10.py │ ├── tfo.c │ └── webstress.cc ├── Makefile ├── config.h ├── Makefile.android ├── Makefile.android.aarch64 ├── dh.cc ├── Makefile.osx ├── misc.h ├── Makefile.linux ├── Makefile.bsd ├── socket.h ├── flavor.h ├── multicore.cc ├── rproxy.h ├── log.h ├── ssl.h ├── sandbox-linux.cc ├── flavor-osx.cc ├── flavor-linux.cc ├── config.cc ├── flavor-android.cc ├── flavor-bsd.cc ├── client.h ├── frontend-main.cc ├── log.cc ├── lonely.h ├── ssl.cc ├── socket.cc ├── client.cc ├── main.cc ├── misc.cc └── rproxy.cc ├── sample.conf ├── LICENSE ├── Changelog ├── HINTS └── README.md /logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stealth/lophttpd/HEAD/logo.jpg -------------------------------------------------------------------------------- /icons/back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stealth/lophttpd/HEAD/icons/back.gif -------------------------------------------------------------------------------- /icons/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stealth/lophttpd/HEAD/icons/back.png -------------------------------------------------------------------------------- /icons/file.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stealth/lophttpd/HEAD/icons/file.gif -------------------------------------------------------------------------------- /icons/folder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stealth/lophttpd/HEAD/icons/folder.gif -------------------------------------------------------------------------------- /icons/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stealth/lophttpd/HEAD/icons/folder.png -------------------------------------------------------------------------------- /src/dh2048.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | DH *get_dh2048() 4 | { 5 | return NULL; 6 | } 7 | -------------------------------------------------------------------------------- /src/newdh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | print "Generating 2048 bit DH parameters ...\n\n"; 4 | system("openssl dhparam -5 -noout -C 2048 > dh2048.cc"); 5 | 6 | -------------------------------------------------------------------------------- /src/multicore.h: -------------------------------------------------------------------------------- 1 | #ifndef lophttpd_multicore_h 2 | #define lophttpd_multicore_h 3 | 4 | namespace misc { 5 | 6 | extern int my_core; 7 | 8 | int init_multicore(); 9 | 10 | int setup_multicore(int); 11 | 12 | } 13 | 14 | #endif 15 | 16 | -------------------------------------------------------------------------------- /src/tests/test1.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use IO::Socket; 4 | 5 | my $host = shift || "127.0.0.1"; 6 | 7 | my $peer = new IO::Socket::INET->new(PeerAddr => $host, 8 | PeerPort => 80, 9 | Proto => 'tcp') or die $!; 10 | 11 | my $msg=<read($msg, 1024) == 0; 20 | print $msg; 21 | } 22 | 23 | close($peer); 24 | 25 | -------------------------------------------------------------------------------- /src/tests/test2.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use IO::Socket; 4 | 5 | my $host = shift || "127.0.0.1"; 6 | 7 | my $peer = new IO::Socket::INET->new(PeerAddr => $host, 8 | PeerPort => 80, 9 | Proto => 'tcp') or die $!; 10 | 11 | my $msg=<read($msg, 1024) == 0; 20 | print $msg; 21 | } 22 | 23 | 24 | close($peer); 25 | 26 | -------------------------------------------------------------------------------- /src/tests/test4.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use IO::Socket; 4 | 5 | my $host = shift || "127.0.0.1"; 6 | 7 | my $peer = new IO::Socket::INET->new(PeerAddr => $host, 8 | PeerPort => 80, 9 | Proto => 'tcp') or die $!; 10 | 11 | my $msg=<read($msg, 1024) == 0; 20 | print $msg; 21 | } 22 | 23 | close($peer); 24 | 25 | -------------------------------------------------------------------------------- /src/tests/test3.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use IO::Socket; 4 | 5 | my $host = shift || "127.0.0.1"; 6 | 7 | my $peer = new IO::Socket::INET->new(PeerAddr => $host, 8 | PeerPort => 80, 9 | Proto => 'tcp') or die $!; 10 | 11 | my $msg=<read($msg, 1024) == 0; 20 | print $msg; 21 | } 22 | 23 | close($peer); 24 | 25 | -------------------------------------------------------------------------------- /src/tests/test5.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use IO::Socket; 4 | 5 | my $host = shift || "127.0.0.1"; 6 | 7 | my $peer = new IO::Socket::INET->new(PeerAddr => $host, 8 | PeerPort => 80, 9 | Proto => 'tcp') or die $!; 10 | 11 | my $msg=<read($msg, 1024) == 0; 20 | print $msg; 21 | } 22 | 23 | close($peer); 24 | 25 | -------------------------------------------------------------------------------- /src/tests/test6.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use IO::Socket; 4 | 5 | my $host = shift || "127.0.0.1"; 6 | 7 | my $peer = new IO::Socket::INET->new(PeerAddr => $host, 8 | PeerPort => 80, 9 | Proto => 'tcp') or die $!; 10 | 11 | my $msg=<read($msg, 1024) == 0; 20 | print $msg; 21 | } 22 | 23 | close($peer); 24 | 25 | -------------------------------------------------------------------------------- /src/tests/test9.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # frontend testcase for Location: redirects 4 | 5 | use IO::Socket; 6 | 7 | my $sock = new IO::Socket::INET->new( 8 | LocalPort => 8080, 9 | Proto => 'tcp', 10 | Reuse => 1, 11 | Type => SOCK_STREAM, 12 | Listen => 1) or die $!; 13 | 14 | my $msg = ""; 15 | my $reply = "HTTP/1.1 300 Redirect blah\r\nX-blah: blahblah\r\n". 16 | "Location:http://127.0.0.1:8080/\r\n\r\n"; 17 | 18 | while (1) { 19 | my $peer = $sock->accept(); 20 | 21 | <$peer>; 22 | print $peer $reply; 23 | close($peer); 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | MAKEFILE=Makefile.linux 2 | 3 | ifeq ($(shell uname -o), GNU/Linux) 4 | MAKEFILE=Makefile.linux 5 | else ifeq ($(shell uname -o), FreeBSD) 6 | MAKEFILE=Makefile.bsd 7 | else ifeq ($(shell uname -o), NetBSD) 8 | MAKEFILE=Makefile.bsd 9 | else ifeq ($(shell uname -o), OpenBSD) 10 | MAKEFILE=Makefile.bsd 11 | else ifeq ($(shell uname -o), Darwin) 12 | MAKEFILE=Makefile.osx 13 | endif 14 | 15 | .PHONY: all clean buildclean 16 | 17 | all: 18 | make -f $(MAKEFILE) 19 | 20 | clean: 21 | make -f $(MAKEFILE) clean 22 | 23 | buildclean: 24 | make -f $(MAKEFILE) buildclean 25 | 26 | -------------------------------------------------------------------------------- /src/tests/test7.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use IO::Socket; 4 | 5 | my $host = shift || "127.0.0.1"; 6 | 7 | my $peer = new IO::Socket::INET->new(PeerAddr => $host, 8 | PeerPort => 80, 9 | Proto => 'tcp') or die $!; 10 | 11 | my $msg1=<read($msg, 1024) == 0; 26 | print $msg; 27 | } 28 | 29 | close($peer); 30 | 31 | -------------------------------------------------------------------------------- /src/tests/test8.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use IO::Socket; 4 | 5 | my $host = shift || "127.0.0.1"; 6 | 7 | my $peer = new IO::Socket::INET->new(PeerAddr => $host, 8 | PeerPort => 80, 9 | Proto => 'tcp') or die $!; 10 | 11 | my $msg1=<read($msg, 1024) == 0; 26 | print $msg; 27 | } 28 | 29 | close($peer); 30 | 31 | -------------------------------------------------------------------------------- /src/tests/test10.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import sys 5 | import errno 6 | 7 | def usage(): 8 | print('\ntest10.py to create 100k files inside \n') 9 | exit(1) 10 | 11 | def die(arg, e): 12 | print(arg, os.strerror(e.errno)) 13 | exit(e.errno) 14 | 15 | def main(): 16 | if len(sys.argv) == 1: 17 | usage() 18 | 19 | try: 20 | os.mkdir(sys.argv[1], 0755) 21 | except Exception as e: 22 | if e.errno == errno.EEXIST: 23 | pass 24 | else: 25 | die('Failed to create directory.', e) 26 | 27 | try: 28 | os.chdir(sys.argv[1]) 29 | for i in range(100000): 30 | f = open('file-%s' % i, 'w') 31 | f.write('X'*1024) 32 | f.close() 33 | except Exception as e: 34 | die('Failed to create the files.', e) 35 | 36 | 37 | if __name__ == '__main__': 38 | main() 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/tests/tfo.c: -------------------------------------------------------------------------------- 1 | // testing TCP fast open 2 | // cc tfo.c -std=c11 -pedantic -o tfo 3 | // ./tfo 127.0.0.1 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | void die(const char *msg) 14 | { 15 | perror(msg); 16 | exit(errno); 17 | } 18 | 19 | int main(int argc, char **argv) 20 | { 21 | if (argc != 2) 22 | die("no IP given"); 23 | 24 | int sfd = socket(PF_INET, SOCK_STREAM, 0); 25 | if (sfd < 0) 26 | die("socket"); 27 | struct sockaddr_in sin = {AF_INET, htons(80)}; 28 | if (inet_pton(AF_INET, argv[1], &sin.sin_addr) != 1) 29 | die("inet_pton"); 30 | if (sendto(sfd, "HEAD / HTTP/1.0\r\n\r\n", 19, MSG_FASTOPEN, (const struct sockaddr *)&sin, sizeof(sin)) < 0) 31 | die("sendto"); 32 | 33 | char buf[4096] = {0}; 34 | ssize_t r = 0; 35 | if ((r = read(sfd, buf, sizeof(buf) - 1)) < 0) 36 | die("read"); 37 | close(sfd); 38 | write(1, buf, r); 39 | return 0; 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef lophttpd_config_h 2 | #define lophttpd_config_h 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace httpd_config 13 | { 14 | extern std::string root, base; 15 | extern std::string upload; 16 | extern std::map kfile, cfile; 17 | extern bool gen_index, virtual_hosts, is_chrooted, quiet, use_ssl, tfo; 18 | extern bool rand_upload, no_error_kill, rand_upload_quiet; 19 | extern std::string user, logfile, log_provider; 20 | extern uid_t user_uid, user_gid; 21 | extern std::string host, port; 22 | extern int cores, master; 23 | extern uint16_t mss; 24 | extern uint32_t max_connections; 25 | extern uint32_t ncache; 26 | extern int client_sched; 27 | } 28 | 29 | 30 | namespace rproxy_config { 31 | 32 | 33 | struct backend { 34 | std::string host, path; 35 | struct addrinfo ai; 36 | uint16_t port; 37 | }; 38 | 39 | 40 | extern std::map > url_map; 41 | extern std::map location_map; 42 | extern std::string user, root, logfile, host, port, location, logprovider; 43 | 44 | int parse(const std::string &); 45 | 46 | const char *why(); 47 | 48 | } 49 | 50 | #endif 51 | 52 | -------------------------------------------------------------------------------- /sample.conf: -------------------------------------------------------------------------------- 1 | # frontend reverse proxy sample config file 2 | 3 | # which host to listen (default ANY) 4 | host 0.0.0.0 5 | 6 | # local port to listen 7 | port 80 8 | 9 | # user and chroot directory 10 | user wwwrun 11 | chroot /var/run/ 12 | 13 | logfile /var/log/frontend 14 | 15 | # valid providers are file, mmap or aio 16 | # 'file' is default, 'mmap' is fastest, 'aio' experimental 17 | logprovider file 18 | 19 | 20 | # Now the mappings ... 21 | # 22 | # 23 | 24 | # #port specifier is not required for port 80 25 | # 26 | # The URI must match what a potential "Location:" redirect reply from one of 27 | # the backends also would contain as URI (without sub paths) 28 | # and it must be resolvable by DNS if its not an IP address. Otherwise redirects 29 | # do not work. 30 | # 31 | # '#' comments in the same line as the 'map' command are not allowed as it would 32 | # clash with port specifier 33 | # 34 | 35 | map /foo http://127.0.0.1#8080/ 36 | 37 | # multiple assignments for the same /path are possile 38 | # (load balancing) 39 | 40 | map /foo http://127.0.0.2#8080/ 41 | #... 42 | 43 | # The path component is always required, even if its just / 44 | map /bar http://bar.example.com/ 45 | 46 | 47 | # What do we tell the outside world where all the Locations: 48 | # are? (subpath components will be translated back and forth by frontend and the above 49 | # path mappings will be appended automatically) 50 | # 51 | # No trailing slashes allowed (unlike in "map"). 52 | # 53 | # In this example a 'Location:http://127.0.0.1:8080/' from the first backend would be 54 | # translated to 'Location:http://outside.example.com/foo/' 55 | # 56 | location http://outside.example.com 57 | 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2014 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | -------------------------------------------------------------------------------- /src/Makefile.android: -------------------------------------------------------------------------------- 1 | # 2 | # This is the Makefile for the Android flavor, ARM32 3 | # 4 | 5 | ROOT=/opt 6 | NDK=android-ndk-r17b 7 | 8 | PREFIX=$(ROOT)/$(NDK)/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi- 9 | SYSROOT=--sysroot=$(ROOT)/$(NDK)/platforms/android-24/arch-arm/ 10 | SYSROOT+=-isysroot $(ROOT)/$(NDK)/sysroot 11 | 12 | INC=-isystem $(ROOT)/$(NDK)/sysroot/usr/include/arm-linux-androideabi\ 13 | -I$(ROOT)/$(NDK)/sources/cxx-stl/gnu-libstdc++/include\ 14 | -I$(ROOT)/$(NDK)/sources/cxx-stl/gnu-libstdc++/4.9/include/\ 15 | -I$(ROOT)/$(NDK)/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/include 16 | 17 | LIB=-Wl,$(ROOT)/$(NDK)/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/libgnustl_static.a 18 | 19 | DEFS=-DANDROID 20 | DEFS+=-D_FILE_OFFSET_BITS=64 21 | 22 | CXX=$(PREFIX)gcc -O2 -Wall -pedantic -std=c++11 $(SYSROOT) $(INC) $(DEFS) 23 | LD=$(PREFIX)gcc $(SYSROOT) 24 | 25 | 26 | .PHONY: all clean distclean 27 | 28 | all: build build/lhttpd 29 | 30 | build: 31 | mkdir build || true 32 | 33 | clean: 34 | rm -f build/*.o 35 | 36 | distclean: 37 | rm -rf build 38 | 39 | 40 | build/lhttpd: build/lonely.o build/socket.o build/main.o build/misc.o build/log.o build/multicore.o build/config.o build/flavor.o build/client.o 41 | $(LD) $^ -o $@ $(LIB) -static 42 | 43 | build/multicore.o: multicore.cc multicore.h 44 | $(CXX) -c $< -o $@ 45 | 46 | build/log.o: log.cc log.h 47 | $(CXX) -c $< -o $@ 48 | 49 | build/misc.o: misc.cc misc.h 50 | $(CXX) -c $< -o $@ 51 | 52 | build/main.o: main.cc 53 | $(CXX) -c $< -o $@ 54 | 55 | build/socket.o: socket.cc socket.h 56 | $(CXX) -c $< -o $@ 57 | 58 | build/lonely.o: lonely.cc lonely.h 59 | $(CXX) -c $< -o $@ 60 | 61 | build/flavor.o: flavor-android.cc flavor.h 62 | $(CXX) -c $< -o $@ 63 | 64 | build/config.o: config.cc config.h 65 | $(CXX) -c $< -o $@ 66 | 67 | build/client.o: client.cc client.h 68 | $(CXX) -c $< -o $@ 69 | 70 | -------------------------------------------------------------------------------- /src/Makefile.android.aarch64: -------------------------------------------------------------------------------- 1 | # 2 | # This is the Makefile for the Android flavor, Aarch64 3 | # 4 | 5 | ROOT=/opt 6 | NDK=android-ndk-r17b 7 | 8 | PREFIX=$(ROOT)/$(NDK)/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android- 9 | SYSROOT=--sysroot=$(ROOT)/$(NDK)/platforms/android-24/arch-arm64/ 10 | SYSROOT+=-isysroot $(ROOT)/$(NDK)/sysroot 11 | 12 | INC=-isystem $(ROOT)/$(NDK)/sysroot/usr/include/aarch64-linux-android\ 13 | -I$(ROOT)/$(NDK)/sources/cxx-stl/gnu-libstdc++/include\ 14 | -I$(ROOT)/$(NDK)/sources/cxx-stl/gnu-libstdc++/4.9/include/\ 15 | -I$(ROOT)/$(NDK)/sources/cxx-stl/gnu-libstdc++/4.9/libs/arm64-v8a/include 16 | 17 | LIB=-Wl,$(ROOT)/$(NDK)/sources/cxx-stl/gnu-libstdc++/4.9/libs/arm64-v8a/libgnustl_static.a 18 | 19 | DEFS=-DANDROID 20 | DEFS+=-D_FILE_OFFSET_BITS=64 21 | 22 | CXX=$(PREFIX)gcc -O2 -Wall -pedantic -std=c++11 $(SYSROOT) $(INC) $(DEFS) 23 | LD=$(PREFIX)gcc $(SYSROOT) 24 | 25 | 26 | .PHONY: all clean distclean 27 | 28 | all: build build/lhttpd 29 | 30 | build: 31 | mkdir build || true 32 | 33 | clean: 34 | rm -f build/*.o 35 | 36 | distclean: 37 | rm -rf build 38 | 39 | 40 | build/lhttpd: build/lonely.o build/socket.o build/main.o build/misc.o build/log.o build/multicore.o build/config.o build/flavor.o build/client.o 41 | $(LD) $^ -o $@ $(LIB) -static 42 | 43 | build/multicore.o: multicore.cc multicore.h 44 | $(CXX) -c $< -o $@ 45 | 46 | build/log.o: log.cc log.h 47 | $(CXX) -c $< -o $@ 48 | 49 | build/misc.o: misc.cc misc.h 50 | $(CXX) -c $< -o $@ 51 | 52 | build/main.o: main.cc 53 | $(CXX) -c $< -o $@ 54 | 55 | build/socket.o: socket.cc socket.h 56 | $(CXX) -c $< -o $@ 57 | 58 | build/lonely.o: lonely.cc lonely.h 59 | $(CXX) -c $< -o $@ 60 | 61 | build/flavor.o: flavor-android.cc flavor.h 62 | $(CXX) -c $< -o $@ 63 | 64 | build/config.o: config.cc config.h 65 | $(CXX) -c $< -o $@ 66 | 67 | build/client.o: client.cc client.h 68 | $(CXX) -c $< -o $@ 69 | 70 | -------------------------------------------------------------------------------- /src/dh.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2015 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include "dh2048.cc" 39 | 40 | 41 | static DH *dh2048 = NULL; 42 | 43 | 44 | DH *dh_callback(SSL *ssl, int is_exported, int keylen) 45 | { 46 | return dh2048; 47 | } 48 | 49 | 50 | int enable_dh(SSL_CTX *ctx) 51 | { 52 | if ((dh2048 = get_dh2048()) != NULL) { 53 | SSL_CTX_set_tmp_dh_callback(ctx, dh_callback); 54 | return 1; 55 | } 56 | return 0; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/Makefile.osx: -------------------------------------------------------------------------------- 1 | # 2 | # This is the Makefile for the OSX flavor 3 | # 4 | # You need to install Xcode and its commandline tools 5 | 6 | # On OSX keep in mind that Apple marks the system openssl lib 7 | # functions as deprecated, in favor of their own crypto libs. This 8 | # leads to (unnecessary) warnings. (goto fail for the win). 9 | # You can add your own openssl includes/lib dirs here, to get rid of it. 10 | 11 | INC= 12 | LIBS= 13 | DEFS= 14 | LDFLAGS= 15 | 16 | 17 | DEFS+=-DUSE_SSL 18 | LIBS+=-lssl -lcrypto 19 | 20 | #override lophttps secure cipher list 21 | #DEFS+=-DUSE_CIPHERS=\"ALL:!ADH:!LOW:!EXP:!RC4:!MD5:kDHE:@STRENGTH\" 22 | 23 | CXXFLAGS=-std=c++11 -Wall -O2 -pedantic $(INC) $(DEFS) 24 | CXX=c++ 25 | LD=c++ 26 | 27 | all: lhttpd frontend 28 | 29 | clean: 30 | rm -f *.o 31 | 32 | distclean: clean 33 | rm -f lhttpd 34 | 35 | lhttpd: lonely.o socket.o main.o misc.o log.o multicore.o config.o flavor.o client.o dh.o ssl.o 36 | $(LD) $(LDFLAGS) lonely.o socket.o main.o misc.o log.o multicore.o config.o flavor.o\ 37 | client.o dh.o ssl.o -o lhttpd $(LIBS) 38 | 39 | 40 | frontend: lonely.o socket.o frontend-main.o log.o multicore.o rproxy.o config.o misc.o flavor.o client.o dh.o ssl.o 41 | $(LD) $(LDFLAGS) lonely.o socket.o frontend-main.o misc.o log.o multicore.o rproxy.o\ 42 | config.o flavor.o client.o dh.o ssl.o -o frontend $(LIBS) 43 | 44 | frontend-main.o: frontend-main.cc 45 | $(CXX) $(CXXFLAGS) -c frontend-main.cc 46 | 47 | rproxy.o: rproxy.cc rproxy.h 48 | $(CXX) $(CXXFLAGS) -c rproxy.cc 49 | 50 | config.o: config.cc config.h 51 | $(CXX) $(CXXFLAGS) -c config.cc 52 | 53 | multicore.o: multicore.cc multicore.h 54 | $(CXX) $(CXXFLAGS) -c multicore.cc 55 | 56 | log.o: log.cc log.h 57 | $(CXX) $(CXXFLAGS) -c log.cc 58 | 59 | misc.o: misc.cc misc.h 60 | $(CXX) $(CXXFLAGS) -c misc.cc 61 | 62 | main.o: main.cc 63 | $(CXX) $(CXXFLAGS) -c main.cc 64 | 65 | flavor.o: flavor-osx.cc flavor.h 66 | $(CXX) $(CXXFLAGS) -c flavor-osx.cc -o flavor.o 67 | 68 | socket.o: socket.cc socket.h 69 | $(CXX) $(CXXFLAGS) -c socket.cc 70 | 71 | lonely.o: lonely.cc lonely.h 72 | $(CXX) $(CXXFLAGS) -c lonely.cc 73 | 74 | client.o: client.cc client.h 75 | $(CXX) $(CXXFLAGS) -c client.cc 76 | 77 | dh.o: dh.cc dh2048.cc 78 | $(CXX) $(CXXFLAGS) -c dh.cc 79 | 80 | ssl.o: ssl.cc ssl.h 81 | $(CXX) $(CXXFLAGS) -c ssl.cc 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/misc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2014 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef lophttpd_misc_h 34 | #define lophttpd_misc_h 35 | 36 | #include 37 | #include 38 | 39 | namespace misc { 40 | 41 | extern std::string err; 42 | 43 | extern std::map dir2index; 44 | 45 | void generate_index(const std::string &path); 46 | 47 | int find_ctype(const std::string &); 48 | 49 | const char *why(); 50 | 51 | enum { 52 | CONTENT_DATA = 0, 53 | CONTENT_HTML = 1 54 | }; 55 | 56 | struct ctypes { 57 | std::string extension, c_type; 58 | }; 59 | 60 | extern struct ctypes content_types[]; 61 | 62 | } 63 | 64 | #endif 65 | 66 | -------------------------------------------------------------------------------- /src/Makefile.linux: -------------------------------------------------------------------------------- 1 | # 2 | # This is the Makefile for the Linux flavor 3 | # 4 | 5 | # comment out if you dont need SSL/TLS 6 | 7 | INC= 8 | LIBS= 9 | DEFS= 10 | LDFLAGS= 11 | 12 | DEFS+=-D_FILE_OFFSET_BITS=64 13 | DEFS+=-DUSE_SSL 14 | 15 | # comment in for libressl and adjust to your path 16 | # remember to put libressl lib path to /etc/ld.so.config or LD_LIBRARY_PATH 17 | #DEFS+=-I/opt/ssl/libressl-2.8.1/include 18 | #LIBS+=-L/opt/ssl/libressl-2.8.1/lib 19 | 20 | # enable Linux seccomp sandboxing 21 | #DEFS+=-DUSE_SANDBOX 22 | 23 | #override lophttps secure cipher list 24 | #DEFS+=-DUSE_CIPHERS=\"ALL:!ADH:!LOW:!EXP:!RC4:!MD5:kDHE:@STRENGTH\" 25 | 26 | CXXFLAGS=-std=c++11 -Wall -O2 -pedantic $(INC) $(DEFS) 27 | CXX=c++ 28 | LD=c++ 29 | 30 | LIBS+=-lssl -lcrypto 31 | 32 | # if your CXX=clang 33 | #LIBS+=-lstdc++ 34 | 35 | .PHONY: all clean distclean 36 | 37 | all: build build/lhttpd build/frontend 38 | 39 | build: 40 | mkdir build || true 41 | 42 | clean: 43 | rm -f build/*.o 44 | 45 | distclean: 46 | rm -rf build 47 | 48 | build/lhttpd: build/lonely.o build/socket.o build/main.o build/misc.o build/log.o build/multicore.o build/config.o build/flavor.o build/client.o build/dh.o build/ssl.o 49 | $(LD) $(LDFLAGS) $^ -o $@ $(LIBS) 50 | 51 | 52 | build/frontend: build/lonely.o build/socket.o build/frontend-main.o build/log.o build/multicore.o build/rproxy.o build/config.o build/misc.o build/flavor.o build/client.o build/dh.o build/ssl.o 53 | $(LD) $(LDFLAGS) $^ -o $@ $(LIBS) 54 | 55 | build/frontend-main.o: frontend-main.cc 56 | $(CXX) $(CXXFLAGS) -c $< -o $@ 57 | 58 | build/rproxy.o: rproxy.cc rproxy.h 59 | $(CXX) $(CXXFLAGS) -c $< -o $@ 60 | 61 | build/config.o: config.cc config.h 62 | $(CXX) $(CXXFLAGS) -c $< -o $@ 63 | 64 | build/multicore.o: multicore.cc multicore.h 65 | $(CXX) $(CXXFLAGS) -c $< -o $@ 66 | 67 | build/log.o: log.cc log.h 68 | $(CXX) $(CXXFLAGS) -c $< -o $@ 69 | 70 | build/misc.o: misc.cc misc.h 71 | $(CXX) $(CXXFLAGS) -c $< -o $@ 72 | 73 | build/main.o: main.cc 74 | $(CXX) $(CXXFLAGS) -c $< -o $@ 75 | 76 | build/flavor.o: flavor-linux.cc flavor.h 77 | $(CXX) $(CXXFLAGS) -c $< -o $@ 78 | 79 | build/socket.o: socket.cc socket.h 80 | $(CXX) $(CXXFLAGS) -c $< -o $@ 81 | 82 | build/lonely.o: lonely.cc lonely.h 83 | $(CXX) $(CXXFLAGS) -c $< -o $@ 84 | 85 | build/client.o: client.cc client.h 86 | $(CXX) $(CXXFLAGS) -c $< -o $@ 87 | 88 | build/dh.o: dh.cc dh2048.cc 89 | $(CXX) $(CXXFLAGS) -c $< -o $@ 90 | 91 | build/ssl.o: ssl.cc ssl.h 92 | $(CXX) $(CXXFLAGS) -c $< -o $@ 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/Makefile.bsd: -------------------------------------------------------------------------------- 1 | # 2 | # This is the Makefile for the BSD flavor 3 | # 4 | 5 | # comment out if you dont need SSL/TLS 6 | 7 | INC= 8 | LIBS= 9 | DEFS= 10 | LDFLAGS= 11 | 12 | DEFS+=-D_FILE_OFFSET_BITS=64 13 | DEFS+=-DUSE_SSL 14 | 15 | 16 | #override lophttps secure cipher list 17 | #DEFS+=-DUSE_CIPHERS=\"ALL:!ADH:!LOW:!EXP:!RC4:!MD5:kDHE:@STRENGTH\" 18 | 19 | CXXFLAGS=-std=c++11 -Wall -O2 -pedantic $(INC) $(DEFS) 20 | CXX=c++ 21 | LD=c++ 22 | 23 | LIBS+=-lssl -lcrypto 24 | 25 | # if your CXX=clang 26 | #LIBS+=-lstdc++ 27 | 28 | .PHONY: all clean distclean 29 | 30 | all: build build/lhttpd build/frontend 31 | 32 | build: 33 | mkdir build || true 34 | 35 | clean: 36 | rm -f build/*.o 37 | 38 | distclean: 39 | rm -rf build 40 | 41 | build/lhttpd: build/lonely.o build/socket.o build/main.o build/misc.o build/log.o build/multicore.o build/config.o build/flavor.o build/client.o build/dh.o build/ssl.o 42 | $(LD) $(LDFLAGS) build/lonely.o build/socket.o build/main.o build/misc.o build/log.o build/multicore.o build/config.o build/flavor.o build/client.o build/dh.o build/ssl.o -o $@ $(LIBS) 43 | 44 | 45 | build/frontend: build/lonely.o build/socket.o build/frontend-main.o build/log.o build/multicore.o build/rproxy.o build/config.o build/misc.o build/flavor.o build/client.o build/dh.o build/ssl.o 46 | $(LD) $(LDFLAGS) build/lonely.o build/socket.o build/frontend-main.o build/log.o build/multicore.o build/rproxy.o build/config.o build/misc.o build/flavor.o build/client.o build/dh.o build/ssl.o -o $@ $(LIBS) 47 | 48 | build/frontend-main.o: frontend-main.cc 49 | $(CXX) $(CXXFLAGS) -c frontend-main.cc -o $@ 50 | 51 | build/rproxy.o: rproxy.cc rproxy.h 52 | $(CXX) $(CXXFLAGS) -c rproxy.cc -o $@ 53 | 54 | build/config.o: config.cc config.h 55 | $(CXX) $(CXXFLAGS) -c config.cc -o $@ 56 | 57 | build/multicore.o: multicore.cc multicore.h 58 | $(CXX) $(CXXFLAGS) -c multicore.cc -o $@ 59 | 60 | build/log.o: log.cc log.h 61 | $(CXX) $(CXXFLAGS) -c log.cc -o $@ 62 | 63 | build/misc.o: misc.cc misc.h 64 | $(CXX) $(CXXFLAGS) -c misc.cc -o $@ 65 | 66 | build/main.o: main.cc 67 | $(CXX) $(CXXFLAGS) -c main.cc -o $@ 68 | 69 | build/flavor.o: flavor-bsd.cc flavor.h 70 | $(CXX) $(CXXFLAGS) -c flavor-bsd.cc -o $@ 71 | 72 | build/socket.o: socket.cc socket.h 73 | $(CXX) $(CXXFLAGS) -c socket.cc -o $@ 74 | 75 | build/lonely.o: lonely.cc lonely.h 76 | $(CXX) $(CXXFLAGS) -c lonely.cc -o $@ 77 | 78 | build/client.o: client.cc client.h 79 | $(CXX) $(CXXFLAGS) -c client.cc -o $@ 80 | 81 | build/dh.o: dh.cc dh2048.cc 82 | $(CXX) $(CXXFLAGS) -c dh.cc -o $@ 83 | 84 | build/ssl.o: ssl.cc ssl.h 85 | $(CXX) $(CXXFLAGS) -c ssl.cc -o $@ 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/socket.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2001-2018 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | #ifndef lophttpd_socket_h 33 | #define lophttpd_socket_h 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | namespace ns_socket { 43 | 44 | const char *why(); 45 | 46 | int nodelay(int sock); 47 | 48 | int reuse(int sock); 49 | 50 | int dstaddr(int sock, sockaddr_in *dst); 51 | 52 | int bind_local(int sock, const sockaddr *, socklen_t, bool do_listen, bool fastopen = 0); 53 | 54 | int bind_local(int sock, uint16_t port, bool do_listen, int tries); 55 | 56 | int tcp_connect_nb(const struct addrinfo &, uint16_t); 57 | 58 | int finish_connecting(int); 59 | 60 | int readn(int fd, void *buf, size_t len); 61 | 62 | int writen(int fd, const void *buf, size_t len); 63 | 64 | } // namespace 65 | 66 | #endif 67 | 68 | -------------------------------------------------------------------------------- /src/flavor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2018 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | 34 | #ifndef lophttpd_flavor_h 35 | #define lophttpd_flavor_h 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | namespace flavor { 43 | 44 | enum { 45 | NONBLOCK = 1 46 | }; 47 | 48 | 49 | // return a (unblocking if NONBLOCK flag is set) accepted socket 50 | int accept(int, struct sockaddr *, socklen_t *, int); 51 | 52 | bool servable_device(const struct stat &); 53 | 54 | bool servable_file(const struct stat &); 55 | 56 | int device_size(const std::string &, off_t &); 57 | 58 | int in_send_queue(int); 59 | 60 | // calls sendfile() if !is_dev indicates that. returns 0 on success, -1 on error. 61 | // uses normal read/write if sendfile cannot be used. updates offset, left and copied accordingly 62 | ssize_t sendfile(int peer, int fd, off_t *offset, size_t n, off_t &left, off_t &copied, int); 63 | 64 | int sandbox(); 65 | 66 | } 67 | 68 | 69 | #endif 70 | 71 | -------------------------------------------------------------------------------- /src/multicore.cc: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 3 | #endif 4 | 5 | #include "multicore.h" 6 | 7 | 8 | 9 | #if defined __linux__ && !defined ANDROID 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "misc.h" 19 | #include "config.h" 20 | 21 | 22 | namespace misc { 23 | 24 | using namespace std; 25 | 26 | int ncpus = 1; 27 | int my_core = 0; 28 | 29 | static int get_cores() 30 | { 31 | int n = 1; 32 | char buf[256]; 33 | 34 | FILE *f = fopen("/proc/cpuinfo", "r"); 35 | if (!f) { 36 | err = "misc::get_cores:"; 37 | err += strerror(errno); 38 | return -1; 39 | } 40 | for (;!feof(f);) { 41 | memset(buf, 0, sizeof(buf)); 42 | if (fgets(buf, sizeof(buf), f) == NULL) 43 | break; 44 | if (string(buf).find("processor") != string::npos) 45 | ++n; 46 | } 47 | 48 | fclose(f); 49 | return n - 1; 50 | } 51 | 52 | 53 | int init_multicore() 54 | { 55 | ncpus = get_cores(); 56 | if (ncpus <= 0) 57 | ncpus = 1; 58 | return ncpus; 59 | } 60 | 61 | 62 | int setup_multicore(int n) 63 | { 64 | // one core is this thread 65 | if (n == 1) 66 | return 0; 67 | 68 | // any invalid number of cores is treated as the 69 | // whole set 70 | if (n <= 0 || n > ncpus) 71 | n = ncpus; 72 | 73 | cpu_set_t *cpuset = CPU_ALLOC(n); 74 | if (!cpuset) { 75 | err = "misc::setup_multicore: OOM"; 76 | return -1; 77 | } 78 | size_t size = CPU_ALLOC_SIZE(n); 79 | CPU_ZERO_S(size, cpuset); 80 | CPU_SET_S(0, size, cpuset); 81 | if (sched_setaffinity(getpid(), size, cpuset) < 0) { 82 | err = "misc::setup_multicore::sched_setaffinity:"; 83 | err += strerror(errno); 84 | CPU_FREE(cpuset); 85 | return -1; 86 | } 87 | 88 | my_core = 0; 89 | pid_t pid = 0; 90 | // fork a child for each core 91 | for (int i = 1; i < n; ++i) { 92 | pid = fork(); 93 | if (pid < 0) { 94 | err = "misc::setup_multicore::fork:"; 95 | err += strerror(errno); 96 | CPU_FREE(cpuset); 97 | return -1; 98 | } else if (pid > 0) 99 | continue; 100 | CPU_ZERO_S(size, cpuset); 101 | CPU_SET_S(i, size, cpuset); 102 | if (sched_setaffinity(getpid(), size, cpuset) < 0) { 103 | err = "misc::setup_multicore::sched_setaffinity:"; 104 | err += strerror(errno); 105 | CPU_FREE(cpuset); 106 | return -1; 107 | } 108 | my_core = i; 109 | httpd_config::master = 0; 110 | break; 111 | } 112 | 113 | CPU_FREE(cpuset); 114 | return 0; 115 | } 116 | 117 | } 118 | 119 | #else 120 | 121 | namespace misc { 122 | 123 | int my_core = 0; 124 | 125 | int init_multicore() 126 | { 127 | return 0; 128 | } 129 | 130 | int setup_multicore(int n) 131 | { 132 | return 0; 133 | } 134 | 135 | } 136 | 137 | #endif 138 | 139 | -------------------------------------------------------------------------------- /src/rproxy.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2014 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef __rproxy_h__ 34 | #define __rproxy_h__ 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include "config.h" 49 | #include "lonely.h" 50 | #include "log.h" 51 | 52 | 53 | 54 | class rproxy : public lonely { 55 | private: 56 | std::map, struct rproxy_config::backend> client_map; 57 | 58 | int mangle_request_header(); 59 | 60 | int mangle_server_reply(); 61 | 62 | ssize_t more_bytes(); 63 | 64 | ssize_t more_client_bytes(); 65 | 66 | ssize_t more_server_bytes(); 67 | 68 | int send_error(http_error_code_t, bool kill_conn = 1); 69 | 70 | int de_escape_path(std::string &); 71 | 72 | static const uint8_t timeout_header; 73 | 74 | public: 75 | rproxy() {}; 76 | 77 | virtual ~rproxy() { }; 78 | 79 | virtual int loop(); 80 | }; 81 | 82 | 83 | #endif 84 | 85 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2012 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | 34 | #ifndef lophttpd_log_h 35 | #define lophttpd_log_h 36 | 37 | #include 38 | #include 39 | 40 | #ifndef ANDROID 41 | #include 42 | #endif 43 | 44 | 45 | class log_provider { 46 | private: 47 | int log_fd; 48 | 49 | // Need to disable -pedantic for stupid glinc header, having a 0-sized array for alignment inside a struct 50 | // of aio! 51 | #pragma GCC diagnostic push 52 | #pragma GCC diagnostic ignored "-Wpedantic" 53 | 54 | #ifndef ANDROID 55 | struct aiocb log_aio; 56 | #endif 57 | #pragma GCC diagnostic pop 58 | 59 | void *log_area; 60 | off_t log_index, log_size; 61 | 62 | int write_log(const std::string &); 63 | 64 | int mmap_log(const std::string &); 65 | 66 | int aio_log(const std::string &); 67 | 68 | int (log_provider::*do_log)(const std::string &); 69 | 70 | std::string err; 71 | 72 | public: 73 | 74 | const char *why() { return err.c_str(); }; 75 | 76 | int log(const std::string &); 77 | 78 | int open_log(const std::string &, const std::string &, int core); 79 | 80 | log_provider() 81 | : log_fd(-1), log_area((void *)-1), log_index(0), log_size(0) 82 | { 83 | }; 84 | 85 | ~log_provider(); 86 | }; 87 | 88 | #endif 89 | 90 | -------------------------------------------------------------------------------- /Changelog: -------------------------------------------------------------------------------- 1 | 0.98 2 | ---- 3 | 4 | + Added https (TLSv1) support 5 | 6 | 0.97 7 | ---- 8 | 9 | + better encapsulation for client state machine 10 | (work to be done for adding SSL support) 11 | 12 | 0.95 13 | ---- 14 | 15 | + various optimizations 16 | + better and faster proxying in frontend 17 | + can serve large /proc and /sys files now 18 | + added -Q and -E 19 | 20 | 0.94 21 | ---- 22 | 23 | + can serve /proc and /sys files 24 | + can upload files, if enabled at runtime 25 | + added a logo 26 | + changed generated index.html to be smaller for 27 | large directories (100k files) 28 | 29 | 0.93 30 | ---- 31 | 32 | + Added -flavor to remove #ifdef's 33 | + Added Android targets 34 | + can serve block device files now 35 | 36 | 0.91 37 | ---- 38 | + Thu Dec 15 39 | + Fixed clear_cache() 40 | + enhanced webstressing tool 41 | + made sendfile() chunksize configurable 42 | + Using accept4() on Linux 43 | + Changed caching of open/stat to give less surface for memory 44 | exhaustion attacks 45 | 46 | 47 | 0.90 48 | ---- 49 | 50 | + Fixed handling of Range: introduced in 0.89 51 | + Fixed handling of Content-Length 52 | + added parallel download testing tool 53 | + fixed shutdown vs. close problems 54 | 55 | 56 | 0.89 57 | ---- 58 | + Fri Dec 2 59 | + Now supporting Range: headers for partial downloads 60 | + RFC-complinat HEAD/POST handling 61 | + Added all other RFC required Methods (CONNECT etc) 62 | + Successfully tested on FreeBSD8.1 63 | 64 | 0.88 65 | ---- 66 | + Tue Nov 29 67 | + Imported lophttpd cvs to github (git://github.com/stealth/lophttpd.git) 68 | + every core (if using multicore) now gets its own logfile (suffixed with .) 69 | so we dont leed file locking anymore and all log providers now also work 70 | with multicore support 71 | + changed gmtime logging to localtime 72 | 73 | 0.87 74 | ---- 75 | 76 | + Thu Feb 17 77 | + Add Date: string to HTTP replies so httpdate works 78 | properly 79 | 80 | 0.86 81 | ---- 82 | + Sat Okt 30 2010 83 | + Adding multicore support for Linux 84 | 85 | 0.85 86 | ---- 87 | + Sat Jun 26 2010 88 | + Fixed a bug that lead to truncation of large files. 89 | Content-Length was using %d rather than %zu :( 90 | 91 | 0.83 92 | ---- 93 | + Wed Jun 16 2010 94 | + made mmap provider portable 95 | 96 | 0.82 97 | ---- 98 | 99 | + Mon Jun 7 2010 100 | + Added log providers. For heavy loaded machines one can try to 101 | get some benefit by using mmap or aio log provider in order to 102 | prevent millions of write()s to disk. 103 | 104 | 0.80 105 | ---- 106 | 107 | + Sun May 30 2010 108 | + Security: Fixed access to unmapped memory if large directories 109 | are autoindexed 110 | Thanks to Alexander Hagenah for sending a bug report 111 | + Security: Fixed handling of large amounts of "wget -r" for many 112 | files, including potential out of bounds write to the poll 113 | array 114 | + Introducing a timeout in which valid headers must arrive 115 | in order to handle slow links and clients which may send large 116 | requests 117 | 118 | -------------------------------------------------------------------------------- /src/ssl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2018 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef lophttpd_ssl_h 34 | #define lophttpd_ssl_h 35 | 36 | #include 37 | #include 38 | 39 | #ifdef USE_SSL 40 | extern "C" { 41 | #include 42 | #include 43 | #include 44 | } 45 | 46 | 47 | #endif 48 | 49 | 50 | // encapsulating the SSL_CTXs for use with SNI's, for easier handling 51 | // with the callbacks and inside the main loop 52 | 53 | class ssl_container { 54 | 55 | private: 56 | 57 | #ifdef USE_SSL 58 | 59 | friend int sni_handler(SSL *ssl, int *ad, void *arg); 60 | 61 | #if OPENSSL_VERSION_NUMBER >= 0x10000000L 62 | const SSL_METHOD *ssl_method; 63 | #else 64 | SSL_METHOD *ssl_method; 65 | #endif 66 | 67 | std::map host2ctx; 68 | #endif 69 | 70 | std::string err; 71 | 72 | public: 73 | 74 | ssl_container() 75 | : err("") 76 | { 77 | } 78 | 79 | ~ssl_container() 80 | { 81 | } 82 | 83 | const char *why() 84 | { 85 | return err.c_str(); 86 | } 87 | 88 | int init(const std::map &, const std::map &); 89 | 90 | #ifdef USE_SSL 91 | SSL_CTX *find_ctx(const std::string &); 92 | 93 | void clear(); 94 | #endif 95 | 96 | 97 | }; 98 | 99 | #endif 100 | 101 | -------------------------------------------------------------------------------- /HINTS: -------------------------------------------------------------------------------- 1 | 2 | Some things that you can tune to have optimum performance 3 | in general: 4 | 5 | - Increase value inside /proc/sys/net/core/somaxconn to have larger 6 | connection backlog queue (try 10000, this is what lhttpd will use) 7 | On FreeBSD thats 'sysctl -w kern.ipc.somaxconn=10000' 8 | 9 | - have the logfile's on a different HDD than the web-root 10 | 11 | - If firewall policy allows, mark HTTP traffic as not-conntracked: 12 | iptables -t raw -A PREROUTING -p tcp --dport 80 -j NOTRACK 13 | iptables -t raw -A OUTPUT -p tcp --sport 80 -j NOTRACK 14 | 15 | or in the new shape: 16 | 17 | iptables -t raw -A PREROUTING -p tcp --dport 80 -j CT --notrack 18 | iptables -t raw -A OUTPUT -p tcp --sport 80 -j CT --notrack 19 | 20 | This really buys you connection performance. 21 | 22 | - Whenever possible use the "mmap" log provider, it really rocks. 23 | (it requires the log-files to be MB aligned, so you may need to remove 24 | them first if you previously didn't use the mmap provider) 25 | 26 | - split large directories (dirs containing like 100k files) into smaller ones 27 | if you use auto-indexing, as most web browsers cannot render large tables fastly 28 | 29 | - If you have large bandwidth and a lot of connections, consider to increase your 30 | TCP send buffers via /proc/sys/net/ipv4/tcp_wmem (min, default, max) 31 | 32 | - for a large BDP, consider using tcp-westwood or other congestion avoidance algorithms 33 | (modprobe tcp_weswood and tune /proc/sys/net/ipv4/tcp_*) 34 | 35 | - You can experiment with lophttpd's send-size scheduling algorithms by 36 | passing "-s algo" switch. This allows to choose between different scheduling 37 | strategies if a lot of clients are handled simulteanously: 38 | 39 | o none (default) 40 | Usually there is no need to change anything. If you have good uplink, you 41 | can serve 25k or more clients on a single core on a commodity PC. Todays 42 | CPU and NIC speed dont really limit the amount of clients anymore. However 43 | if you experiance connection drops for really large amount of clients, you 44 | can try the algorithms below. Keep in mind that most drops are not caused by 45 | lophttpd or the OS, but by overloaded network hardware like switches or routers 46 | which cannot handle the constant high-load. 47 | 48 | o suspend 49 | This will remove the client from the POLLOUT list for the time it still 50 | has data inside the TCP send buffer from the last send operation 51 | 52 | o minimize 53 | This will decrease the chunksize of the next segment to a minimum, so that 54 | the TCP send-queue fills slower. Only happens if there is still data 55 | inside the TCP send buffer (as above). 56 | 57 | o static (deprecated) 58 | This computes the size of the next data segment with some formula which depends 59 | on how many clients are actually handled. Does not depend on whether there is 60 | still data in the TCP send-buffer (hence 'static'). Because of the dumb 'static' 61 | computation, this does not really adjust itself and is for testing only. 62 | 63 | 64 | - The default number of maximum allowed parallel connections is set to 10,000 (per core). 65 | If you experience some DoS which tries to exceed that, you can always increase it via -N, 66 | so that it would be hard to reach from DSL up-links. However if just the attackers 67 | up-link is large enough, he can DoS any web server. Thats known and should be prevented 68 | on the border router. If you have smaller files to deliver (some KB) its hard to DoS you, 69 | if you deliver MB sized files, its easier, as even legit connections last longer and 70 | keep the -N parameter high. 71 | In such cases you should consider traffic shaping for dedicated IP networks. 72 | Netfilter is your friend. 73 | 74 | -------------------------------------------------------------------------------- /src/sandbox-linux.cc: -------------------------------------------------------------------------------- 1 | #ifdef USE_SANDBOX 2 | 3 | #ifdef USE_SSL_PRIVSEP 4 | #error "Do not mix SSL PRIVSEP and SANDBOXING" 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "config.h" 16 | 17 | using namespace std; 18 | 19 | /* structures and filter with help from OpenSSH 6.0 code and "fancy seccomp-bpf.h" */ 20 | 21 | 22 | #ifndef SECCOMP_MODE_FILTER 23 | #define SECCOMP_MODE_FILTER 2 /* uses user-supplied filter. */ 24 | #define SECCOMP_RET_KILL 0x00000000U /* kill the task immediately */ 25 | #define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */ 26 | #define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */ 27 | #define SECCOMP_RET_ERRNO 0x00050000U /* returns an errno */ 28 | 29 | 30 | struct seccomp_data { 31 | int nr; 32 | uint32_t arch; 33 | uint64_t instruction_pointer; 34 | uint64_t args[6]; 35 | }; 36 | 37 | #endif 38 | 39 | #if defined(__i386__) 40 | #define REG_SYSCALL REG_EAX 41 | #define SECCOMP_AUDIT_ARCH AUDIT_ARCH_I386 42 | #elif defined(__x86_64__) 43 | #define REG_SYSCALL REG_RAX 44 | #define SECCOMP_AUDIT_ARCH AUDIT_ARCH_X86_64 45 | #else 46 | #warning "Platform does not support seccomp filter yet" 47 | #define REG_SYSCALL 0 48 | #define SECCOMP_AUDIT_ARCH 0 49 | #endif 50 | 51 | #define SC_DENY(_nr, _errno) \ 52 | BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_ ## _nr, 0, 1), \ 53 | BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ERRNO|(_errno)) 54 | #define SC_ALLOW(_nr) \ 55 | BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_ ## _nr, 0, 1), \ 56 | BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW) 57 | 58 | 59 | #ifndef PR_SET_NO_NEW_PRIVS 60 | #define PR_SET_NO_NEW_PRIVS 38 61 | #endif 62 | 63 | 64 | static const struct sock_filter prefix[] = { 65 | /* validate arch */ 66 | BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, arch)), 67 | BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, SECCOMP_AUDIT_ARCH, 1, 0), 68 | BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL), 69 | 70 | /* load syscall nr */ 71 | BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)), 72 | 73 | SC_ALLOW(gettimeofday), 74 | SC_ALLOW(read), 75 | SC_ALLOW(write), 76 | SC_ALLOW(pread64), 77 | SC_ALLOW(ioctl), 78 | #if defined(__i386__) 79 | SC_ALLOW(socketcall), 80 | SC_ALLOW(stat64), 81 | SC_ALLOW(fstat64), 82 | SC_ALLOW(lstat64), 83 | #elif defined(__x86_64__) 84 | SC_ALLOW(accept4), 85 | SC_ALLOW(recvfrom), 86 | SC_ALLOW(shutdown), 87 | SC_ALLOW(stat), 88 | SC_ALLOW(fstat), 89 | SC_ALLOW(lstat), 90 | #endif 91 | SC_ALLOW(fcntl), 92 | SC_ALLOW(getpid), 93 | SC_ALLOW(brk), 94 | SC_ALLOW(close), 95 | SC_ALLOW(sendfile), 96 | SC_ALLOW(poll), 97 | SC_ALLOW(geteuid), 98 | SC_ALLOW(rt_sigreturn), 99 | SC_ALLOW(open), 100 | SC_ALLOW(openat), 101 | SC_ALLOW(getdents), 102 | SC_ALLOW(exit_group) 103 | }; 104 | 105 | static const struct sock_filter suffix[] = { 106 | BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL) 107 | }; 108 | 109 | 110 | static const struct sock_filter f1[] = { 111 | SC_ALLOW(mmap), 112 | SC_ALLOW(munmap), 113 | SC_ALLOW(ftruncate) 114 | }; 115 | 116 | 117 | int sandbox() 118 | { 119 | int r; 120 | 121 | if ((r = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) < 0) 122 | return -1; 123 | 124 | struct sock_fprog filter; 125 | uint16_t flen = sizeof(prefix)/sizeof(prefix[0]) + 1, idx = 0; 126 | if (httpd_config::log_provider == string("mmap")) 127 | flen += sizeof(f1)/sizeof(f1[0]); 128 | 129 | filter.len = flen; 130 | filter.filter = new sock_filter[flen]; 131 | 132 | memcpy(filter.filter, prefix, sizeof(prefix)); 133 | idx += sizeof(prefix)/sizeof(prefix[0]); 134 | if (httpd_config::log_provider == string("mmap")) { 135 | memcpy(&filter.filter[idx], f1, sizeof(f1)); 136 | idx += sizeof(f1)/sizeof(f1[0]); 137 | } 138 | 139 | memcpy(&filter.filter[idx], suffix, sizeof(suffix)); 140 | 141 | r = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &filter); 142 | delete [] filter.filter; 143 | 144 | if (r < 0) 145 | return -1; 146 | return 0; 147 | } 148 | 149 | #else 150 | 151 | int sandbox() 152 | { 153 | return 0; 154 | } 155 | 156 | #endif 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /src/flavor-osx.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include "socket.h" 46 | #include "flavor.h" 47 | #include "lonely.h" 48 | 49 | 50 | namespace flavor { 51 | 52 | using namespace ns_socket; 53 | using namespace std; 54 | 55 | 56 | int accept(int fd, struct sockaddr *saddr, socklen_t *slen, int flags) 57 | { 58 | int afd = 0; 59 | 60 | if ((afd = accept(fd, saddr, slen)) < 0) 61 | return -1; 62 | if (flags == NONBLOCK) { 63 | // no error check 64 | fcntl(afd, F_SETFL, O_RDWR|O_NONBLOCK); 65 | } 66 | return afd; 67 | } 68 | 69 | 70 | bool servable_device(const struct stat &st) 71 | { 72 | return S_ISBLK(st.st_mode); 73 | } 74 | 75 | 76 | bool servable_file(const struct stat &st) 77 | { 78 | // no S_ISLNK() since stat() was used 79 | return S_ISBLK(st.st_mode) || S_ISREG(st.st_mode) || S_ISDIR(st.st_mode); 80 | } 81 | 82 | 83 | int device_size(const std::string &path, off_t &size) 84 | { 85 | int fd = ::open(path.c_str(), O_RDONLY|O_NOCTTY); 86 | if (fd < 0) 87 | return -1; 88 | 89 | int r = 0; 90 | if (ioctl(fd, DKIOCGETBLOCKCOUNT, &size) < 0) 91 | r = -1; 92 | close(fd); 93 | return r; 94 | } 95 | 96 | 97 | int in_send_queue(int fd) 98 | { 99 | return 0; 100 | } 101 | 102 | 103 | ssize_t sendfile(int peer, int fd, off_t *offset, size_t n, off_t &left, off_t &copied, int ftype) 104 | { 105 | if (n > MAX_SEND_SIZE) 106 | return -1; 107 | 108 | ssize_t r = 0, l = 0; 109 | 110 | // proc and sys files 111 | if (ftype == FILE_PROC) { 112 | char buf[MAX_SEND_SIZE], siz[32]; 113 | r = pread(fd, buf, n, *offset); 114 | if (r < 0) { 115 | if (errno == EAGAIN) 116 | errno = EBADF; 117 | return -1; 118 | } else if (r > 0) { 119 | l = snprintf(siz, sizeof(siz), "%x\r\n", (int)r); 120 | if (writen(peer, siz, l) != l) 121 | return -1; 122 | if (writen(peer, buf, r) != r) 123 | return -1; 124 | if (writen(peer, "\r\n", 2) != 2) 125 | return -1; 126 | *offset += r; 127 | copied += r; 128 | } else { 129 | if (writen(peer, "0\r\n\r\n", 5) != 5) 130 | return -1; 131 | left = 0; 132 | r = 5; 133 | } 134 | return r; 135 | } 136 | 137 | off_t count = n; 138 | if (::sendfile(fd, peer, *offset, &count, NULL, 0) != 0) 139 | return -1; 140 | 141 | left -= count; 142 | copied += count; 143 | *offset += count; 144 | return r; 145 | } 146 | 147 | 148 | int sandbox() 149 | { 150 | return 0; 151 | } 152 | 153 | 154 | } // namespace flavor 155 | 156 | -------------------------------------------------------------------------------- /src/flavor-linux.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2014 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #include "socket.h" 46 | #include "flavor.h" 47 | #include "lonely.h" 48 | 49 | #include 50 | #include 51 | 52 | 53 | namespace flavor { 54 | 55 | using namespace ns_socket; 56 | using namespace std; 57 | 58 | 59 | int accept(int fd, struct sockaddr *saddr, socklen_t *slen, int flags) 60 | { 61 | return accept4(fd, saddr, slen, flags == NONBLOCK ? SOCK_NONBLOCK : 0); 62 | } 63 | 64 | 65 | bool servable_device(const struct stat &st) 66 | { 67 | return S_ISBLK(st.st_mode); 68 | } 69 | 70 | 71 | bool servable_file(const struct stat &st) 72 | { 73 | // no S_ISLNK() since stat() was used 74 | return S_ISBLK(st.st_mode) || S_ISREG(st.st_mode) || S_ISDIR(st.st_mode); 75 | } 76 | 77 | 78 | int device_size(const std::string &path, off_t &size) 79 | { 80 | int fd = ::open(path.c_str(), O_RDONLY|O_NOCTTY); 81 | if (fd < 0) 82 | return -1; 83 | 84 | int r = 0, saved_errno = 0; 85 | if (ioctl(fd, BLKGETSIZE64, &size) < 0) { 86 | r = -1; 87 | saved_errno = errno; 88 | } 89 | close(fd); 90 | errno = saved_errno; 91 | return r; 92 | } 93 | 94 | 95 | int in_send_queue(int fd) 96 | { 97 | int n = 0; 98 | ioctl(fd, SIOCOUTQ, &n); 99 | return n; 100 | } 101 | 102 | 103 | ssize_t sendfile(int peer, int fd, off_t *offset, size_t n, off_t &left, off_t &copied, int ftype) 104 | { 105 | if (n > MAX_SEND_SIZE) 106 | return -1; 107 | 108 | ssize_t r = 0, l = 0; 109 | 110 | // proc and sys files 111 | if (ftype == FILE_PROC) { 112 | // OK, cannot be larger than MAX_SEND_SIZE 113 | char buf[MAX_SEND_SIZE], siz[32]; 114 | r = pread(fd, buf, n, *offset); 115 | if (r < 0) { 116 | if (errno == EAGAIN) 117 | errno = EBADF; 118 | return -1; 119 | } else if (r > 0) { 120 | l = snprintf(siz, sizeof(siz), "%x\r\n", (int)r); 121 | if (writen(peer, siz, l) != l) 122 | return -1; 123 | if (writen(peer, buf, r) != r) 124 | return -1; 125 | if (writen(peer, "\r\n", 2) != 2) 126 | return -1; 127 | *offset += r; 128 | copied += r; 129 | } else { 130 | if (writen(peer, "0\r\n\r\n", 5) != 5) 131 | return -1; 132 | left = 0; 133 | r = 5; 134 | } 135 | return r; 136 | } 137 | 138 | // Linux can, unlike BSD, use sendfile() on device files, so 139 | // the last parameter is ignored 140 | 141 | if ((r = ::sendfile(peer, fd, offset, n)) <= 0) 142 | return -1; 143 | left -= r; 144 | copied += r; 145 | return r; 146 | } 147 | 148 | #include "sandbox-linux.cc" 149 | 150 | } // namespace flavor 151 | 152 | -------------------------------------------------------------------------------- /src/config.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "config.h" 10 | 11 | using namespace std; 12 | 13 | 14 | namespace httpd_config 15 | { 16 | string root = "/srv/www/htdocs", base = "/"; 17 | string upload = ""; 18 | map kfile, cfile; 19 | bool gen_index = 0, virtual_hosts = 0, is_chrooted = 0, quiet = 0; 20 | bool rand_upload = 0, no_error_kill = 0, rand_upload_quiet = 0, use_ssl = 0, tfo = 0; 21 | string user = "wwwrun", logfile = "/var/log/lophttpd", log_provider = "file"; 22 | uid_t user_uid = 99, user_gid = 99; 23 | string host = "0.0.0.0", port = "80"; 24 | int cores = 1; 25 | 26 | // on multicore there is only one master 27 | int master = 1; 28 | 29 | uint16_t mss = 0; 30 | uint32_t max_connections = 10000; 31 | uint32_t ncache = 10000; 32 | int client_sched = 0; 33 | } 34 | 35 | 36 | namespace rproxy_config { 37 | 38 | string err = ""; 39 | 40 | map > url_map; 41 | map location_map; 42 | string user = "wwwrun", root = "/var/run/empty", 43 | logfile = "/var/log/frontend", host = "0.0.0.0", port = "80", location = "", 44 | logprovider = "file"; 45 | 46 | 47 | int parse(const string &cfile) 48 | { 49 | FILE *f = fopen(cfile.c_str(), "r"); 50 | if (!f) { 51 | err = "rproxy_config::parse::fopen:"; 52 | err += strerror(errno); 53 | return -1; 54 | } 55 | 56 | char buf[1024], host[256], path[256], sport[32], *ptr = NULL; 57 | int r = 0; 58 | struct addrinfo *ai = NULL, hints; 59 | memset(&hints, 0, sizeof(hints)); 60 | hints.ai_socktype = SOCK_STREAM; 61 | while (fgets(buf, sizeof(buf), f)) { 62 | ptr = buf; 63 | while (*ptr == ' ' || *ptr == '\t') 64 | ++ptr; 65 | if (*ptr == '#' || *ptr == '\n') 66 | continue; 67 | 68 | if (strncmp(ptr, "map", 3) == 0) { 69 | ptr += 3; 70 | while (*ptr == ' ' || *ptr == '\t') 71 | ++ptr; 72 | 73 | string opath = ""; 74 | backend b; 75 | 76 | // No '#' token in 'map' allowed due to port specifier 77 | strtok(ptr, " \t"); 78 | opath = ptr; 79 | ptr = strtok(NULL, " \t\n"); 80 | 81 | memset(host, 0, sizeof(host)); 82 | memset(path, 0, sizeof(path)); 83 | if (strchr(ptr + 5, '#')) { 84 | if (sscanf(ptr, "http://%255[^#]#%hu/%255c", host, &b.port, path) == 0) { 85 | err = "rproxy_config::parse::sscanf: invalid 'map' config."; 86 | return -1; 87 | } 88 | } else { 89 | if (sscanf(ptr, "http://%255[^/]/%255c", host, path) == 0) { 90 | } 91 | b.port = 80; 92 | } 93 | 94 | b.host = host; 95 | b.path = "/"; 96 | b.path += path; 97 | 98 | snprintf(sport, sizeof(sport), "%hu", b.port); 99 | if ((r = getaddrinfo(host, sport, &hints, &ai)) < 0) { 100 | err = "rproxy_config::parse::getaddrinfo:"; 101 | err += gai_strerror(r); 102 | return -1; 103 | } 104 | b.ai = *ai; 105 | url_map[opath].push_back(b); 106 | location_map[ptr] = opath; 107 | } else if (strncmp(ptr, "user", 4) == 0) { 108 | ptr += 4; 109 | while (*ptr == ' ' || *ptr == '\t') 110 | ++ptr; 111 | strtok(ptr, " \t\n#"); 112 | user = ptr; 113 | } else if (strncmp(ptr, "chroot", 6) == 0) { 114 | ptr += 6; 115 | while (*ptr == ' ' || *ptr == '\t') 116 | ++ptr; 117 | strtok(ptr, " \t\n#"); 118 | root = ptr; 119 | } else if (strncmp(ptr, "logfile", 7) == 0) { 120 | ptr += 7; 121 | while (*ptr == ' ' || *ptr == '\t') 122 | ++ptr; 123 | strtok(ptr, " \t\n#"); 124 | logfile = ptr; 125 | } else if (strncmp(ptr, "location", 8) == 0) { 126 | ptr += 8; 127 | while (*ptr == ' ' || *ptr == '\t') 128 | ++ptr; 129 | strtok(ptr, " \t\n#"); 130 | location = ptr; 131 | } else if (strncmp(ptr, "notfound", 8) == 0) { 132 | // url, or default action wenn keine 133 | } else if (strncmp(ptr, "deny", 4) == 0) { 134 | // deny GET for regex 135 | } else if (strncmp(ptr, "host", 4) == 0) { 136 | ptr += 4; 137 | while (*ptr == ' ' || *ptr == '\t') 138 | ++ptr; 139 | strtok(ptr, " \t\n#"); 140 | rproxy_config::host = ptr; 141 | } else if (strncmp(ptr, "port", 4) == 0) { 142 | ptr += 4; 143 | while (*ptr == ' ' || *ptr == '\t') 144 | ++ptr; 145 | strtok(ptr, " \t\n#"); 146 | port = ptr; 147 | } else if (strncmp(ptr, "logprovider", 11) == 0) { 148 | ptr += 11; 149 | while (*ptr == ' ' || *ptr == '\t') 150 | ++ptr; 151 | strtok(ptr, " \t\n#"); 152 | logprovider = ptr; 153 | } 154 | } 155 | 156 | fclose(f); 157 | return 0; 158 | } 159 | 160 | 161 | const char *why() 162 | { 163 | return err.c_str(); 164 | } 165 | 166 | 167 | } 168 | 169 | -------------------------------------------------------------------------------- /src/flavor-android.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2014 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #include "socket.h" 46 | #include "flavor.h" 47 | #include "lonely.h" 48 | 49 | #ifndef BLKGETSIZE64 50 | #define BLKGETSIZE64 _IOR(0x12,114,size_t) 51 | #endif 52 | 53 | #include 54 | #include 55 | 56 | 57 | namespace flavor { 58 | 59 | using namespace ns_socket; 60 | 61 | int accept(int fd, struct sockaddr *saddr, socklen_t *slen, int flags) 62 | { 63 | int afd = 0; 64 | 65 | if ((afd = accept(fd, saddr, slen)) < 0) 66 | return -1; 67 | if (flags == NONBLOCK) { 68 | // no error check 69 | fcntl(afd, F_SETFL, O_RDWR|O_NONBLOCK); 70 | } 71 | return afd; 72 | } 73 | 74 | bool servable_device(const struct stat &st) 75 | { 76 | return S_ISBLK(st.st_mode); 77 | } 78 | 79 | 80 | bool servable_file(const struct stat &st) 81 | { 82 | // no S_ISLNK() since stat() was used 83 | return S_ISBLK(st.st_mode) || S_ISREG(st.st_mode) || S_ISDIR(st.st_mode); 84 | } 85 | 86 | 87 | int device_size(const std::string &path, off_t &size) 88 | { 89 | int fd = ::open(path.c_str(), O_RDONLY|O_NOCTTY); 90 | if (fd < 0) 91 | return -1; 92 | 93 | int r = 0, saved_errno = 0; 94 | if (ioctl(fd, BLKGETSIZE64, &size) < 0) { 95 | r = -1; 96 | saved_errno = errno; 97 | } 98 | close(fd); 99 | errno = saved_errno; 100 | return r; 101 | } 102 | 103 | 104 | int in_send_queue(int fd) 105 | { 106 | int n = 0; 107 | ioctl(fd, SIOCOUTQ, &n); 108 | return n; 109 | } 110 | 111 | 112 | ssize_t sendfile(int peer, int fd, off_t *offset, size_t n, off_t &left, off_t &copied, int ftype) 113 | { 114 | if (n > MAX_SEND_SIZE) 115 | return -1; 116 | 117 | ssize_t r = 0, l = 0; 118 | 119 | // proc and sys files 120 | if (ftype == FILE_PROC) { 121 | // n cannot be larger than MAX_SEND_SIZE 122 | char buf[MAX_SEND_SIZE], siz[32]; 123 | r = pread(fd, buf, n, *offset); 124 | if (r < 0) { 125 | if (errno == EAGAIN) 126 | errno = EBADF; 127 | return -1; 128 | } else if (r > 0) { 129 | l = snprintf(siz, sizeof(siz), "%x\r\n", (int)r); 130 | if (writen(peer, siz, l) != l) 131 | return -1; 132 | if (writen(peer, buf, r) != r) 133 | return -1; 134 | if (writen(peer, "\r\n", 2) != 2) 135 | return -1; 136 | *offset += r; 137 | copied += r; 138 | } else { 139 | if (writen(peer, "0\r\n\r\n", 5) != 5) 140 | return -1; 141 | left = 0; 142 | r = 5; 143 | } 144 | return r; 145 | } 146 | 147 | 148 | // Linux can, unlike BSD, use sendfile() on device files, so 149 | // the last parameter is ignored 150 | if ((r = ::sendfile(peer, fd, offset, n)) <= 0) 151 | return -1; 152 | left -= r; 153 | copied += r; 154 | return r; 155 | } 156 | 157 | 158 | int sandbox() 159 | { 160 | return 0; 161 | } 162 | 163 | 164 | } 165 | 166 | -------------------------------------------------------------------------------- /src/flavor-bsd.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2014 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #include "socket.h" 45 | #include "flavor.h" 46 | #include "lonely.h" 47 | 48 | 49 | namespace flavor { 50 | 51 | using namespace ns_socket; 52 | 53 | int accept(int fd, struct sockaddr *saddr, socklen_t *slen, int flags) 54 | { 55 | int afd = 0; 56 | 57 | if ((afd = accept(fd, saddr, slen)) < 0) 58 | return -1; 59 | if (flags == NONBLOCK) { 60 | // no error check 61 | fcntl(afd, F_SETFL, O_RDWR|O_NONBLOCK); 62 | } 63 | return afd; 64 | } 65 | 66 | 67 | bool servable_device(const struct stat &st) 68 | { 69 | return S_ISCHR(st.st_mode); 70 | } 71 | 72 | 73 | bool servable_file(const struct stat &st) 74 | { 75 | // no S_ISLNK() since stat() was used 76 | return S_ISCHR(st.st_mode) || S_ISREG(st.st_mode) || S_ISDIR(st.st_mode); 77 | } 78 | 79 | 80 | int device_size(const std::string &path, off_t &size) 81 | { 82 | int fd = ::open(path.c_str(), O_RDONLY|O_NOCTTY); 83 | if (fd < 0) 84 | return -1; 85 | 86 | int r = 0, saved_errno = 0; 87 | if (ioctl(fd, DIOCGMEDIASIZE, &size) < 0) { 88 | r = -1; 89 | saved_errno = errno; 90 | } 91 | close(fd); 92 | errno = saved_errno; 93 | return r; 94 | } 95 | 96 | 97 | int in_send_queue(int peer) 98 | { 99 | int n = 0; 100 | ioctl(peer, FIONWRITE, &n); 101 | return n; 102 | } 103 | 104 | 105 | ssize_t sendfile(int peer, int fd, off_t *offset, size_t n, off_t &left, off_t &copied, int ftype) 106 | { 107 | if (n > MAX_SEND_SIZE) 108 | return -1; 109 | 110 | ssize_t r = 0, l = 0; 111 | 112 | // proc files 113 | if (ftype == FILE_PROC) { 114 | // n cannot be larger than MAX_SEND_SIZE 115 | char buf[MAX_SEND_SIZE], siz[32]; 116 | r = pread(fd, buf, n, *offset); 117 | if (r < 0) { 118 | if (errno == EAGAIN) 119 | errno = EBADF; 120 | return -1; 121 | } else if (r > 0) { 122 | l = snprintf(siz, sizeof(siz), "%x\r\n", (int)r); 123 | if (writen(peer, siz, l) != l) 124 | return -1; 125 | if (writen(peer, buf, r) != r) 126 | return -1; 127 | if (writen(peer, "\r\n", 2) != 2) 128 | return -1; 129 | *offset += r; 130 | copied += r; 131 | } else { 132 | if (writen(peer, "0\r\n\r\n", 5) != 5) 133 | return -1; 134 | left = 0; 135 | r = 5; 136 | } 137 | return r; 138 | } 139 | 140 | #ifdef __FreeBSD__ 141 | off_t sbytes = 0; 142 | 143 | if (ftype != FILE_DEVICE) { 144 | r = ::sendfile(fd, peer, *offset, n, NULL, &sbytes, 0); 145 | if (sbytes > 0) { 146 | *offset += sbytes; 147 | left -= sbytes; 148 | copied += sbytes; 149 | } 150 | if (r == 0) 151 | r = (ssize_t)sbytes; 152 | // On FreeBSD, device files do not support sendfile(), NetBSD and OpenBSD 153 | // dont have sendfile() at all 154 | } else { 155 | #endif 156 | // n cannot be larger than MAX_SEND_SIZE 157 | char buf[MAX_SEND_SIZE] = {0}; 158 | r = pread(fd, buf, n, *offset); 159 | if (r > 0) { 160 | // write(), not writen() 161 | r = write(peer, buf, r); 162 | if (r > 0) { 163 | *offset += r; 164 | left -= r; 165 | copied += r; 166 | } 167 | } 168 | #ifdef __FreeBSD__ 169 | } 170 | #endif 171 | 172 | 173 | if (r <= 0) 174 | r = -1; 175 | 176 | return r; 177 | } 178 | 179 | 180 | int sandbox() 181 | { 182 | return 0; 183 | } 184 | 185 | 186 | } // namespace flavor 187 | 188 | 189 | -------------------------------------------------------------------------------- /src/client.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2014 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | // All the client state machine declarations 34 | 35 | #ifndef lophttpd_client_h 36 | #define lophttpd_client_h 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include "config.h" 44 | 45 | #ifdef USE_SSL 46 | 47 | #include "ssl.h" 48 | 49 | extern "C" { 50 | #include 51 | } 52 | #endif 53 | 54 | 55 | typedef enum { 56 | STATE_CONNECTING = 0, 57 | STATE_ACCEPTING, 58 | STATE_DECIDING, 59 | STATE_HANDSHAKING, 60 | STATE_CONNECTED, 61 | STATE_DOWNLOADING, 62 | STATE_DOWNLOADING_FULL, 63 | STATE_UPLOADING, 64 | STATE_CLOSING, 65 | STATE_NONE, 66 | STATE_ERROR 67 | } status_t; 68 | 69 | 70 | typedef enum { 71 | FILE_NONE = 0x0000, 72 | FILE_REGULAR = 0x1000, 73 | FILE_SPECIAL = 0x2000, 74 | FILE_DEVICE = 0x3000, 75 | FILE_PROC = 0x4000, 76 | FILE_GINDEX = 0x5000 77 | } file_t; 78 | 79 | 80 | class http_client { 81 | private: 82 | status_t d_state; 83 | bool ssl_enabled; 84 | 85 | public: 86 | int file_fd, peer_fd; 87 | time_t alive_time, header_time; 88 | bool keep_alive; 89 | off_t offset, copied, left; 90 | dev_t dev; 91 | ino_t ino; 92 | std::string path, from_ip, first_line; 93 | int ct, in_queue; 94 | file_t ftype; 95 | size_t blen; 96 | 97 | char req_buf[2048]; 98 | size_t req_idx; 99 | 100 | #ifdef USE_SSL 101 | SSL *ssl; 102 | #endif 103 | 104 | http_client() 105 | : d_state(STATE_ERROR), ssl_enabled(0), file_fd(-1), peer_fd(-1), alive_time(0), 106 | header_time(0), keep_alive(0), offset(0), copied(0), left(0), dev(0), 107 | ino(0), path(""), from_ip(""), first_line(""), ct(0), in_queue(0), ftype(FILE_REGULAR), blen(0), 108 | req_idx(0) 109 | { 110 | 111 | #ifdef USE_SSL 112 | ssl = NULL; 113 | #endif 114 | memset(req_buf, 0, sizeof(req_buf)); 115 | } 116 | 117 | ~http_client() {}; 118 | 119 | void cleanup(); 120 | 121 | ssize_t sendfile(size_t); 122 | 123 | ssize_t send(const char *, size_t); 124 | 125 | ssize_t recv(void *, size_t); 126 | 127 | ssize_t peek_req(); 128 | 129 | status_t state() const { return d_state; }; 130 | 131 | void transition(status_t s) { d_state = s; }; 132 | 133 | bool is_ssl() { return ssl_enabled; }; 134 | 135 | #ifdef USE_SSL 136 | 137 | int ssl_accept(ssl_container *); 138 | 139 | static int new_session(SSL *, SSL_SESSION *); 140 | 141 | static void remove_session(SSL_CTX *, SSL_SESSION *); 142 | 143 | static SSL_SESSION *get_session(SSL *, unsigned char *, int, int *); 144 | 145 | int match_sni(const std::string &); 146 | 147 | #endif 148 | 149 | }; 150 | 151 | 152 | // distinguish between client and server side to find out 153 | // about new requests on a keep-alive connection 154 | typedef enum { 155 | HTTP_NONE = 0, 156 | HTTP_CLIENT, 157 | HTTP_SERVER 158 | } http_instance_t; 159 | 160 | 161 | class rproxy_client { 162 | private: 163 | status_t d_state; 164 | public: 165 | int fd, file_fd, peer_fd, keep_alive; 166 | time_t alive_time, header_time; 167 | off_t offset; 168 | struct rproxy_config::backend node; 169 | std::string opath, from_ip; 170 | char buf[4096]; 171 | size_t blen; 172 | uint64_t chunk_len; 173 | bool header, chunked; 174 | 175 | http_instance_t type; 176 | 177 | rproxy_client() 178 | : d_state(STATE_ERROR), fd(-1), file_fd(-1), peer_fd(-1), keep_alive(0), alive_time(0), header_time(0), 179 | opath(""), from_ip(""), blen(0), 180 | chunk_len(0), header(1), chunked(0), type(HTTP_NONE) {}; 181 | 182 | ~rproxy_client() {}; 183 | 184 | void cleanup(); 185 | 186 | status_t state() const { return d_state; }; 187 | 188 | void transition(status_t s) { d_state = s; }; 189 | }; 190 | 191 | 192 | #endif 193 | 194 | -------------------------------------------------------------------------------- /src/frontend-main.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2013 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | //#include 46 | //#include 47 | #include 48 | #include 49 | #include "config.h" 50 | #include "lonely.h" 51 | #include "rproxy.h" 52 | #include "misc.h" 53 | #include "multicore.h" 54 | 55 | 56 | using namespace std; 57 | 58 | 59 | void die(const char *s, bool please_die = 1) 60 | { 61 | perror(s); 62 | if (please_die) 63 | exit(errno); 64 | } 65 | 66 | 67 | void close_fds() 68 | { 69 | struct rlimit rl; 70 | 71 | if (getrlimit(RLIMIT_NOFILE, &rl) < 0) 72 | die("getrlimit"); 73 | for (unsigned int i = 3; i <= rl.rlim_max; ++i) 74 | close(i); 75 | close(0); 76 | open("/dev/null", O_RDWR); 77 | dup2(0, 1); 78 | } 79 | 80 | 81 | static rproxy *proxy = NULL; 82 | 83 | void sigusr1(int x) 84 | { 85 | if (!proxy) 86 | return; 87 | } 88 | 89 | 90 | int main(int argc, char **argv) 91 | { 92 | if (getuid() != 0) { 93 | cerr<<"\a!!! WARNING: !!! Must be called as root in order to chroot() and drop privs properly!\n"; 94 | cerr<<"Continuing in UNSAFE mode!\n\n"; 95 | } 96 | 97 | uid_t euid = geteuid(); 98 | 99 | if (argc != 2) { 100 | cerr<<"Usage: frontend \n"; 101 | return 1; 102 | } 103 | 104 | if (rproxy_config::parse(argv[1]) < 0) { 105 | cerr<open_log(rproxy_config::logfile, rproxy_config::logprovider, 0) < 0) { 116 | cerr<<"Opening logfile: "<why()<init(rproxy_config::host, rproxy_config::port) < 0) { 121 | proxy->log(proxy->why()); 122 | exit(-1); 123 | } 124 | 125 | struct passwd *pw = getpwnam(rproxy_config::user.c_str()); 126 | if (!pw) { 127 | cerr<<"Fatal: Unknown user '"<pw_gid) < 0) 138 | die("initgroups", euid == 0); 139 | 140 | chdir("/"); 141 | if (chroot(rproxy_config::root.c_str()) < 0) 142 | die("chroot", euid == 0); 143 | 144 | if (setgid(pw->pw_gid) < 0) 145 | die("setgid", euid == 0); 146 | if (setuid(pw->pw_uid) < 0) 147 | die("setuid", euid == 0); 148 | 149 | // Commented because not needed. Only for IP_TRANSPARENT support 150 | // if it ever comes 151 | #if 0 152 | cap_t my_caps; 153 | cap_value_t cv[2] = {CAP_NET_ADMIN, CAP_NET_BIND_SERVICE}; 154 | 155 | if ((my_caps = cap_init()) == NULL) 156 | die("cap_init"); 157 | if (cap_set_flag(my_caps, CAP_EFFECTIVE, 2, cv, CAP_SET) < 0) 158 | die("cap_set_flag"); 159 | if (cap_set_flag(my_caps, CAP_PERMITTED, 2, cv, CAP_SET) < 0) 160 | die("cap_set_flag"); 161 | if (cap_set_proc(my_caps) < 0) 162 | die("cap_set_proc"); 163 | cap_free(my_caps); 164 | #endif 165 | 166 | if (chdir("/") < 0) 167 | die("chdir"); 168 | 169 | struct sigaction sa; 170 | memset(&sa, 0, sizeof(sa)); 171 | sa.sa_handler = sigusr1; 172 | sa.sa_flags = SA_RESTART; 173 | if (sigaction(SIGUSR1, &sa, NULL) < 0) 174 | die("sigaction"); 175 | 176 | sa.sa_handler = SIG_IGN; 177 | if (sigaction(SIGHUP, &sa, NULL) < 0) 178 | die("sigaction"); 179 | 180 | if (sigaction(SIGPIPE, &sa, NULL) < 0) 181 | die("sigaction"); 182 | 183 | dup2(0, 2); 184 | 185 | if (fork() > 0) 186 | exit(0); 187 | 188 | setsid(); 189 | 190 | for (;;) { 191 | if (proxy->loop() < 0) 192 | proxy->log(proxy->why()); 193 | } 194 | 195 | delete proxy; 196 | return 0; 197 | } 198 | 199 | -------------------------------------------------------------------------------- /src/log.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2012 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include "log.h" 45 | 46 | using namespace std; 47 | 48 | 49 | /* 50 | static inline off_t next_aligned_size(off_t size) 51 | { 52 | return (size + 1024*1024 + 0x1000)&~(0x1000-1); 53 | } 54 | */ 55 | 56 | int log_provider::open_log(const string &logfile, const string &method, int core = 0) 57 | { 58 | struct stat st; 59 | int flags = O_RDWR|O_CREAT; 60 | 61 | if (method != "mmap") 62 | flags |= O_APPEND; 63 | 64 | // safe default 65 | do_log = &log_provider::write_log; 66 | 67 | char lfile[1024]; 68 | snprintf(lfile, sizeof(lfile), "%s.%d", logfile.c_str(), core); 69 | log_fd = open(lfile, flags, 0600); 70 | if (log_fd < 0) { 71 | err = "log_provider::open_log::open:"; 72 | err += strerror(errno); 73 | return -1; 74 | } 75 | 76 | log_area = (void *)-1; 77 | log_index = 0; 78 | log_size = 1<<20; 79 | 80 | if (method == "mmap") { 81 | if (fstat(log_fd, &st) < 0) { 82 | err = "log_provider::open_log::fstat:"; 83 | err += strerror(errno); 84 | return -1; 85 | } 86 | if (ftruncate(log_fd, st.st_size + log_size) < 0) { 87 | err = "log_provider::open_log::ftruncate:"; 88 | err += strerror(errno); 89 | return -1; 90 | } 91 | if ((log_area = mmap(NULL, log_size, PROT_READ|PROT_WRITE, MAP_SHARED, 92 | log_fd, st.st_size)) == (void *)-1) { 93 | err = "log_provider::open_log::mmap:"; 94 | err += strerror(errno); 95 | return -1; 96 | } 97 | do_log = &log_provider::mmap_log; 98 | } 99 | 100 | return 0; 101 | } 102 | 103 | 104 | int log_provider::log(const string &msg) 105 | { 106 | return (this->*do_log)(msg); 107 | } 108 | 109 | 110 | int log_provider::write_log(const string &msg) 111 | { 112 | ssize_t r = write(log_fd, msg.c_str(), msg.size()); 113 | return (int)r; 114 | } 115 | 116 | 117 | int log_provider::mmap_log(const string &msg) 118 | { 119 | if (log_area == (void *)-1) 120 | return -1; 121 | 122 | if (log_index + (off_t)msg.size() >= log_size) { 123 | struct stat st; 124 | if (munmap(log_area, log_size) < 0) { 125 | err = "log_provider::mmap_log::munmap:"; 126 | err += strerror(errno); 127 | return -1; 128 | } 129 | if (fstat(log_fd, &st) < 0) { 130 | err = "log_provider::mmap_log::fstat:"; 131 | err += strerror(errno); 132 | return -1; 133 | } 134 | if (ftruncate(log_fd, st.st_size + log_size) < 0) { 135 | err = "log_provider::mmap_log::ftruncate:"; 136 | err += strerror(errno); 137 | return -1; 138 | } 139 | if ((log_area = mmap(NULL, log_size, PROT_READ|PROT_WRITE, MAP_SHARED, 140 | log_fd, st.st_size)) == (void *)-1) { 141 | err = "log_provider::mmap_log::mmap:"; 142 | err += strerror(errno); 143 | return -1; 144 | } 145 | log_index = 0; 146 | } 147 | memcpy((char *)log_area + log_index, msg.c_str(), msg.size()); 148 | log_index += msg.size(); 149 | return 0; 150 | } 151 | 152 | 153 | #ifdef AIO_UNUSED 154 | int log_provider::aio_log(const string &msg) 155 | { 156 | int count = 0; 157 | bool error = 0; 158 | 159 | // finish last requests 160 | if (log_aio.aio_fildes == log_fd) { 161 | do { 162 | ++count; 163 | } while (aio_error(&log_aio) == EINPROGRESS && count < 12); 164 | 165 | if (count == 12) { 166 | if (aio_cancel(log_fd, &log_aio) != AIO_CANCELED) 167 | error = 1; 168 | } else { 169 | if (aio_return(&log_aio) <= 0) 170 | error = 1; 171 | } 172 | } 173 | 174 | // free(NULL) is defined 175 | free((void *)log_aio.aio_buf); 176 | memset(&log_aio, 0, sizeof(log_aio)); 177 | 178 | if (error) 179 | return -1; 180 | 181 | log_aio.aio_fildes = log_fd; 182 | log_aio.aio_buf = strdup(msg.c_str()); 183 | log_aio.aio_nbytes = msg.size(); 184 | aio_write(&log_aio); 185 | return 0; 186 | } 187 | #endif 188 | 189 | 190 | log_provider::~log_provider() 191 | { 192 | close(log_fd); 193 | } 194 | 195 | -------------------------------------------------------------------------------- /src/lonely.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2018 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef lophttpd_lonely_h 34 | #define lophttpd_lonely_h 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include "client.h" 48 | #include "log.h" 49 | #include "ssl.h" 50 | 51 | 52 | typedef enum { 53 | HTTP_ERROR_400 = 0, 54 | HTTP_ERROR_401, 55 | HTTP_ERROR_404, 56 | HTTP_ERROR_405, 57 | HTTP_ERROR_406, 58 | HTTP_ERROR_408, 59 | HTTP_ERROR_411, 60 | HTTP_ERROR_414, 61 | HTTP_ERROR_416, 62 | HTTP_ERROR_500, 63 | HTTP_ERROR_501, 64 | HTTP_ERROR_503, 65 | HTTP_ERROR_END 66 | } http_error_code_t; 67 | 68 | 69 | typedef enum { 70 | HTTP_REQUEST_NONE = 0, 71 | HTTP_REQUEST_OPTIONS = 1, 72 | HTTP_REQUEST_GET, 73 | HTTP_REQUEST_HEAD, 74 | HTTP_REQUEST_POST, 75 | HTTP_REQUEST_PUT, 76 | HTTP_REQUEST_DELETE, 77 | HTTP_REQUEST_TRACE, 78 | HTTP_REQUEST_CONNECT 79 | } http_request_t; 80 | 81 | 82 | typedef enum { 83 | CLIENT_SCHED_NONE = 0, 84 | CLIENT_SCHED_SUSPEND, 85 | CLIENT_SCHED_MINIMIZE, 86 | CLIENT_SCHED_STATIC 87 | } client_sched_t; 88 | 89 | 90 | template 91 | class lonely { 92 | protected: 93 | struct pollfd *pfds; 94 | int first_fd, max_fd, top_fd; 95 | T *peer; 96 | T **fd2peer; 97 | int peer_idx; 98 | uint32_t n_clients, n_suspended; 99 | int af; 100 | log_provider *logger; 101 | 102 | time_t cur_time; 103 | suseconds_t cur_usec; 104 | char gmt_date[64], local_date[64]; 105 | 106 | std::string err; 107 | 108 | bool heavy_load; 109 | size_t so_sndbuf; 110 | 111 | void cleanup(int); 112 | 113 | void shutdown(int); 114 | 115 | void calc_max_fd(); 116 | 117 | public: 118 | lonely() 119 | : first_fd(0), max_fd(0), top_fd(0), peer(NULL), fd2peer(NULL), peer_idx(-1), 120 | n_clients(0), n_suspended(0), logger(NULL), 121 | cur_time(0), cur_usec(0), err(""), heavy_load(0), so_sndbuf(4096) 122 | { 123 | } 124 | 125 | virtual ~lonely() { delete [] pfds; delete logger; } 126 | 127 | int init(const std::string &, const std::string &); 128 | 129 | int open_log(const std::string &, const std::string &, int core = 0); 130 | 131 | void log(const std::string &); 132 | 133 | virtual int loop() = 0; 134 | 135 | const char *why(); 136 | 137 | }; 138 | 139 | 140 | enum { 141 | TIMEOUT_ALIVE = 30, 142 | TIMEOUT_CLOSING = 5, 143 | TIMEOUT_HEADER = 3 144 | }; 145 | 146 | 147 | enum { 148 | MANY_RECEIVERS = 1000, 149 | MIN_SEND_SIZE = 64, 150 | DEFAULT_SEND_SIZE = 1024, 151 | MAX_SEND_SIZE = 4096 152 | }; 153 | 154 | 155 | struct inode { 156 | dev_t dev; 157 | ino_t ino; 158 | }; 159 | 160 | 161 | 162 | class lonely_http : public lonely { 163 | private: 164 | struct stat cur_stat; 165 | off_t cur_start_range; 166 | off_t cur_end_range; 167 | bool cur_range_requested, forced_send_size; 168 | http_request_t cur_request; 169 | 170 | char hbuf[1024]; // header construction scratch store 171 | 172 | uint16_t min_send, n_send, max_send; 173 | 174 | std::map file_cache; 175 | std::map err_cache; 176 | 177 | // pathname to (stat, content-type) 178 | std::map > stat_cache; 179 | 180 | static const std::string hdr_fmt, chunked_hdr_fmt, part_hdr_fmt, put_hdr_fmt; 181 | 182 | ssl_container *sslc; 183 | 184 | int OPTIONS(); 185 | 186 | int GET(); 187 | 188 | int GETPOST(); 189 | 190 | int HEAD(); 191 | 192 | int POST(); 193 | 194 | int PUT(); 195 | 196 | int DELETE(); 197 | 198 | int TRACE(); 199 | 200 | int CONNECT(); 201 | 202 | int de_escape_path(); 203 | 204 | int send_http_header(); 205 | 206 | int send_error(http_error_code_t, int); 207 | 208 | int stat(); 209 | 210 | int open(); 211 | 212 | int handle_request(); 213 | 214 | int download(); 215 | 216 | int upload(); 217 | 218 | public: 219 | bool vhosts; 220 | 221 | lonely_http(uint16_t s = DEFAULT_SEND_SIZE) 222 | : cur_start_range(0), cur_end_range(0), 223 | cur_range_requested(0), forced_send_size(0), cur_request(HTTP_REQUEST_NONE), 224 | min_send(MIN_SEND_SIZE), n_send(s), max_send(MAX_SEND_SIZE), 225 | sslc(NULL), vhosts(0) 226 | { 227 | if (n_send != DEFAULT_SEND_SIZE) 228 | forced_send_size = 1; 229 | if (n_send > max_send) 230 | n_send = max_send; 231 | if (n_send < min_send) 232 | n_send = min_send; 233 | } 234 | 235 | virtual ~lonely_http() 236 | { 237 | #ifdef USE_SSL 238 | sslc->clear(); 239 | #endif 240 | } 241 | 242 | int setup_ssl(const std::map &, const std::map &); 243 | 244 | int send_genindex(); 245 | 246 | void clear_cache(); 247 | 248 | int loop(); 249 | }; 250 | 251 | 252 | extern std::string http_error_msgs[]; 253 | 254 | #endif 255 | 256 | -------------------------------------------------------------------------------- /src/ssl.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2014 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include "ssl.h" 36 | 37 | #ifdef USE_SSL 38 | extern "C" { 39 | #include 40 | #include 41 | #include 42 | } 43 | 44 | extern int enable_dh(SSL_CTX *); 45 | 46 | #include 47 | 48 | #endif 49 | 50 | using namespace std; 51 | 52 | #ifdef USE_CIPHERS 53 | string ciphers = USE_CIPHERS; 54 | #else 55 | string ciphers = "!LOW:!EXP:!MD5:!CAMELLIA:!RC4:!MEDIUM:!DES:!ADH:kDHE:RSA:AESGCM:AES256:AES128:SHA256:SHA384:IDEA:@STRENGTH"; 56 | #endif 57 | 58 | 59 | using namespace std; 60 | 61 | 62 | #ifdef USE_SSL 63 | int sni_handler(SSL *ssl, int *ad, void *arg) 64 | { 65 | const char *sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); 66 | 67 | // If no SNI, the default CTX is already set 68 | if (!sni) 69 | return SSL_TLSEXT_ERR_OK; 70 | string name = sni; 71 | ssl_container *sslc = reinterpret_cast(arg); 72 | if (!SSL_set_SSL_CTX(ssl, sslc->find_ctx(name))) 73 | return SSL_TLSEXT_ERR_NOACK; 74 | 75 | return SSL_TLSEXT_ERR_OK; 76 | } 77 | 78 | #endif 79 | 80 | 81 | int ssl_container::init(const map &certs, const map &keys) 82 | { 83 | 84 | #ifdef USE_SSL 85 | SSL_library_init(); 86 | SSL_load_error_strings(); 87 | OpenSSL_add_all_algorithms(); 88 | OpenSSL_add_all_digests(); 89 | 90 | #if OPENSSL_VERSION_NUMBER >= 0x10100000 91 | if ((ssl_method = TLS_server_method()) == NULL) { 92 | #else 93 | if ((ssl_method = SSLv23_server_method()) == NULL) { 94 | #endif 95 | err = "ssl_container::init::server_method:"; 96 | err += ERR_error_string(ERR_get_error(), NULL); 97 | return -1; 98 | } 99 | 100 | if (certs.count("") == 0) { 101 | err = "ssl_container::init: No default certificate given."; 102 | return -1; 103 | } 104 | 105 | string cpath = "", kpath = "", host = ""; 106 | SSL_CTX *ssl_ctx = NULL; 107 | 108 | long op = SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1; 109 | op |= (SSL_OP_SINGLE_DH_USE|SSL_OP_SINGLE_ECDH_USE); 110 | 111 | for (map::const_iterator it = certs.begin(); it != certs.end(); ++it) { 112 | 113 | if (keys.count(it->first) == 0) { 114 | err = "ssl_container::init: Missing keyfile for host '"; 115 | err += it->first; 116 | err += "'"; 117 | return -1; 118 | } 119 | host = it->first; 120 | cpath = it->second; 121 | kpath = keys.find(host)->second; 122 | 123 | if ((ssl_ctx = SSL_CTX_new(ssl_method)) == NULL) { 124 | err = "ssl_container::init::SSL_CTX_new:"; 125 | err += ERR_error_string(ERR_get_error(), NULL); 126 | return -1; 127 | } 128 | 129 | if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cpath.c_str()) != 1) { 130 | err = "ssl_container::init::SSL_CTX_use_certificate_chain_file:"; 131 | err += ERR_error_string(ERR_get_error(), NULL); 132 | return -1; 133 | } 134 | if (SSL_CTX_use_PrivateKey_file(ssl_ctx, kpath.c_str(), SSL_FILETYPE_PEM) != 1) { 135 | err = "ssl_container::init::SSL_CTX_use_PrivateKey_file:"; 136 | err += ERR_error_string(ERR_get_error(), NULL); 137 | return -1; 138 | } 139 | if (SSL_CTX_check_private_key(ssl_ctx) != 1) { 140 | err = "ssl_container::init::SSL_CTX_check_private_key:"; 141 | err += ERR_error_string(ERR_get_error(), NULL); 142 | return -1; 143 | } 144 | 145 | if (SSL_CTX_set_session_id_context(ssl_ctx, (const unsigned char *)"lophttpd", 8) != 1) { 146 | err = "ssl_container::init::SSL_CTX_set_session_id_context:"; 147 | err += ERR_error_string(ERR_get_error(), NULL); 148 | return -1; 149 | } 150 | 151 | if ((unsigned long)(SSL_CTX_set_options(ssl_ctx, op) & op) != (unsigned long)op) { 152 | err = "ssl_container::init::SSL_CTX_set_options:"; 153 | err += ERR_error_string(ERR_get_error(), NULL); 154 | return -1; 155 | } 156 | 157 | SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE); 158 | 159 | SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER); 160 | 161 | // check for DHE and enable it if there are parameters 162 | string::size_type dhe = ciphers.find("kDHE"); 163 | if (dhe != string::npos) { 164 | if (enable_dh(ssl_ctx) != 1) 165 | ciphers.erase(dhe, 4); 166 | } 167 | 168 | if (SSL_CTX_set_cipher_list(ssl_ctx, ciphers.c_str()) != 1) { 169 | err = "ssl_container::init::SSL_CTX_set_cipher_list:"; 170 | err += ERR_error_string(ERR_get_error(), NULL); 171 | err += "(Try default cipher list in Makefile)"; 172 | return -1; 173 | } 174 | 175 | host2ctx[host] = ssl_ctx; 176 | } 177 | 178 | SSL_CTX_set_tlsext_servername_callback(host2ctx[""], sni_handler); 179 | SSL_CTX_set_tlsext_servername_arg(host2ctx[""], this); 180 | #endif 181 | return 0; 182 | } 183 | 184 | #ifdef USE_SSL 185 | 186 | SSL_CTX *ssl_container::find_ctx(const string &host) 187 | { 188 | map::iterator it = host2ctx.find(host); 189 | if (it != host2ctx.end()) 190 | return it->second; 191 | 192 | // must exist 193 | return host2ctx[""]; 194 | } 195 | 196 | 197 | 198 | void ssl_container::clear() 199 | { 200 | for (map::iterator it = host2ctx.begin(); it != host2ctx.end(); ++it) 201 | SSL_CTX_free(it->second); 202 | host2ctx.clear(); 203 | } 204 | 205 | #endif 206 | 207 | -------------------------------------------------------------------------------- /src/socket.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2004-2012 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include "socket.h" 47 | 48 | namespace ns_socket { 49 | 50 | using namespace std; 51 | 52 | string error; 53 | 54 | const char *why() 55 | { 56 | return error.c_str(); 57 | } 58 | 59 | // disable Mr. Nagle's algorithm 60 | int nodelay(int sock) 61 | { 62 | int one = 1; 63 | socklen_t len = sizeof(one); 64 | 65 | if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &one, len) < 0) { 66 | error = "ns_socket::nodelay::setsockopt: "; 67 | error += strerror(errno); 68 | return -1; 69 | } 70 | 71 | return 0; 72 | } 73 | 74 | // make socket ready for port-reuse 75 | int reuse(int sock) 76 | { 77 | int one = 1; 78 | socklen_t len = sizeof(one); 79 | 80 | if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, len) < 0) { 81 | error = "ns_socket::reuse::setsockopt: "; 82 | error += strerror(errno); 83 | return -1; 84 | } 85 | 86 | #ifdef SO_REUSEPORT 87 | one = 1; 88 | setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &one, len); 89 | #endif 90 | 91 | return 0; 92 | } 93 | 94 | 95 | int bind_local(int sock, const struct sockaddr *s, socklen_t slen, bool do_listen, bool fastopen) 96 | { 97 | if (reuse(sock) < 0) 98 | return -1; 99 | 100 | if (bind(sock, s, slen) < 0) { 101 | error = "ns_socket::bind_local::bind: "; 102 | error += strerror(errno); 103 | return -1; 104 | } 105 | 106 | #ifdef TCP_FASTOPEN 107 | if (fastopen) { 108 | int somax = 128; // SOMAXCONN 109 | setsockopt(sock, SOL_TCP, TCP_FASTOPEN, &somax, sizeof(somax)); // ignore error 110 | } 111 | #endif 112 | 113 | if (do_listen) { 114 | if (listen(sock, 100000) < 0) { 115 | if (listen(sock, 10000) < 0) { 116 | if (listen(sock, SOMAXCONN) < 0) { 117 | error = "ns_socket::bind_local::listen: "; 118 | error += strerror(errno); 119 | return -1; 120 | } 121 | } 122 | } 123 | } 124 | 125 | return 0; 126 | } 127 | 128 | 129 | int bind_local(int sock, uint16_t port, int af, bool do_listen, int tries) 130 | { 131 | sockaddr_in sin4; 132 | sockaddr_in6 sin6; 133 | sockaddr *sin = (sockaddr *)&sin4; 134 | socklen_t slen = sizeof(sin4); 135 | 136 | if (af == AF_INET6) { 137 | sin = (sockaddr *)&sin6; 138 | slen = sizeof(sin6); 139 | } 140 | 141 | // XXX: static since connect will ne non-blocking, thus bind() will never fail 142 | static int i = 0; 143 | 144 | memset(&sin4, 0, sizeof(sin4)); 145 | memset(&sin6, 0, sizeof(sin6)); 146 | 147 | sin4.sin_family = sin6.sin6_family = af; 148 | 149 | if (reuse(sock) < 0) 150 | return -1; 151 | 152 | for (; i < tries; ++i) { 153 | sin4.sin_port = sin6.sin6_port = htons(port + i); 154 | if (bind(sock, sin, slen) < 0 && 155 | (errno != EADDRINUSE || i == tries - 1)) { 156 | error = "ns_socket::bind_local::bind: "; 157 | error += strerror(errno); 158 | return -1; 159 | } else { 160 | ++i; 161 | break; 162 | } 163 | } 164 | 165 | if (do_listen) { 166 | if (listen(sock, SOMAXCONN) < 0) { 167 | error = "ns_socket::bind_local::listen: "; 168 | error += strerror(errno); 169 | return -1; 170 | } 171 | } 172 | return 0; 173 | } 174 | 175 | 176 | int tcp_connect_nb(const struct addrinfo &ai, uint16_t local_port) 177 | { 178 | int sock = socket(ai.ai_family, ai.ai_socktype, 0); 179 | if (sock < 0) { 180 | error = "ns_socket::tcp_connect_nb::socket:"; 181 | error += strerror(errno); 182 | return -1; 183 | } 184 | 185 | // not needed until FREEBIND 186 | //int one = 1; 187 | //setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one ,sizeof(one)); 188 | if (local_port > 0) { 189 | if (bind_local(sock, local_port, ai.ai_family, 0, 1000) < 0) 190 | return -1; 191 | } 192 | 193 | if (fcntl(sock, F_SETFL, O_RDWR|O_NONBLOCK) < 0) { 194 | error = "ns_socket::tcp_connect_nb::fcntl:"; 195 | error += strerror(errno); 196 | return -1; 197 | } 198 | 199 | if (connect(sock, ai.ai_addr, ai.ai_addrlen) < 0 && 200 | errno != EINPROGRESS) { 201 | error = "ns_socket::tcp_connect_nb::fcntl:"; 202 | error += strerror(errno); 203 | return -1; 204 | } 205 | 206 | return sock; 207 | } 208 | 209 | 210 | int finish_connecting(int fd) 211 | { 212 | int e = 0; 213 | socklen_t len = sizeof(e); 214 | if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &e, &len) < 0 || e < 0) { 215 | error = "ns_socket::finish_connecting::getsockopt:"; 216 | error += strerror(errno); 217 | return -1; 218 | } 219 | return nodelay(fd); 220 | } 221 | 222 | 223 | int readn(int fd, void *buf, size_t len) 224 | { 225 | int o = 0, n; 226 | char *ptr = (char*)buf; 227 | 228 | while (len > 0) { 229 | if ((n = read(fd, ptr+o, len)) <= 0) 230 | return n; 231 | len -= n; 232 | o += n; 233 | } 234 | return o; 235 | } 236 | 237 | 238 | int writen(int fd, const void *buf, size_t len) 239 | { 240 | int o = 0, n; 241 | char *ptr = (char*)buf; 242 | 243 | while (len > 0) { 244 | if ((n = write(fd, ptr+o, len)) <= 0) { 245 | if (errno == EAGAIN || errno == EWOULDBLOCK) 246 | return o; 247 | return n; 248 | } 249 | len -= n; 250 | o += n; 251 | } 252 | return o; 253 | } 254 | 255 | } // namespace 256 | 257 | -------------------------------------------------------------------------------- /src/client.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2014 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "flavor.h" 41 | #include "socket.h" 42 | #include "client.h" 43 | #include "lonely.h" 44 | 45 | #ifdef USE_SSL 46 | extern "C" { 47 | #include 48 | } 49 | 50 | #endif 51 | 52 | 53 | using namespace std; 54 | using namespace ns_socket; 55 | 56 | 57 | // might be called twice, so no double-free's 58 | void http_client::cleanup() 59 | { 60 | if (d_state == STATE_UPLOADING) 61 | close(file_fd); 62 | file_fd = -1; 63 | peer_fd = -1; 64 | copied = left = 0; 65 | offset = 0; 66 | keep_alive = 0; 67 | alive_time = header_time = 0; 68 | dev = ino = 0; 69 | ct = in_queue = 0; 70 | ftype = FILE_NONE; 71 | d_state = STATE_NONE; 72 | path.clear(); from_ip.clear(); first_line.clear(); 73 | blen = 0; 74 | ssl_enabled = 0; 75 | 76 | memset(req_buf, 0, sizeof(req_buf)); 77 | req_idx = 0; 78 | 79 | #ifdef USE_SSL 80 | if (ssl) 81 | SSL_free(ssl); 82 | ssl = NULL; 83 | // do not free ssl_ctx, its only borrowed 84 | #endif 85 | 86 | } 87 | 88 | 89 | ssize_t http_client::send(const char *buf, size_t n) 90 | { 91 | 92 | #ifdef USE_SSL 93 | int r = 0; 94 | if (ssl_enabled) { 95 | r = SSL_write(ssl, buf, n); 96 | switch (SSL_get_error(ssl, r)) { 97 | case SSL_ERROR_NONE: 98 | break; 99 | default: 100 | r = -1; 101 | } 102 | 103 | return r; 104 | } 105 | #endif 106 | 107 | return writen(peer_fd, buf, n); 108 | } 109 | 110 | 111 | ssize_t http_client::sendfile(size_t n) 112 | { 113 | if (n > MAX_SEND_SIZE) 114 | return -1; 115 | 116 | #ifdef USE_SSL 117 | if (ssl_enabled) { 118 | ssize_t r = 0, l = 0; 119 | 120 | // OK, n cannot be larger than MAX_SEND_SIZE 121 | char buf[MAX_SEND_SIZE], siz[32]; 122 | 123 | r = pread(file_fd, buf, n, offset); 124 | 125 | if (ftype == FILE_PROC) { 126 | if (r < 0) { 127 | if (errno == EAGAIN) 128 | errno = EBADF; 129 | return -1; 130 | } else if (r > 0) { 131 | l = snprintf(siz, sizeof(siz), "%x\r\n", (int)r); 132 | if (SSL_write(ssl, siz, l) != l) 133 | return -1; 134 | if (SSL_write(ssl, buf, r) != r) 135 | return -1; 136 | if (SSL_write(ssl, "\r\n", 2) != 2) 137 | return -1; 138 | offset += r; 139 | copied += r; 140 | } else { 141 | if (SSL_write(ssl, "0\r\n\r\n", 5) != 5) 142 | return -1; 143 | left = 0; 144 | } 145 | return r; 146 | } 147 | 148 | if (r <= 0) { 149 | if (errno == EAGAIN) 150 | errno = EBADF; 151 | return -1; 152 | } 153 | 154 | r = SSL_write(ssl, buf, r); 155 | 156 | switch (SSL_get_error(ssl, r)) { 157 | case SSL_ERROR_NONE: 158 | break; 159 | case SSL_ERROR_WANT_WRITE: 160 | case SSL_ERROR_WANT_READ: 161 | r = 0; 162 | break; 163 | default: 164 | return -1; 165 | } 166 | 167 | offset += r; 168 | left -= r; 169 | copied += r; 170 | return r; 171 | } 172 | #endif 173 | 174 | return flavor::sendfile(peer_fd, file_fd, &offset, // updated by sendfile 175 | n, 176 | left, // updated by sendfile 177 | copied, // updated by sendfile 178 | ftype); 179 | } 180 | 181 | 182 | ssize_t http_client::recv(void *buf, size_t n) 183 | { 184 | 185 | #ifdef USE_SSL 186 | int r = 0; 187 | if (ssl_enabled) { 188 | r = SSL_read(ssl, buf, n); 189 | switch (SSL_get_error(ssl, r)) { 190 | case SSL_ERROR_NONE: 191 | break; 192 | case SSL_ERROR_WANT_WRITE: 193 | case SSL_ERROR_WANT_READ: 194 | r = 0; 195 | break; 196 | default: 197 | r = -1; 198 | } 199 | 200 | return r; 201 | } 202 | #endif 203 | 204 | return ::recv(peer_fd, buf, n, MSG_DONTWAIT); 205 | } 206 | 207 | 208 | ssize_t http_client::peek_req() 209 | { 210 | if (req_idx == 0) 211 | memset(req_buf, 0, sizeof(req_buf)); 212 | 213 | #ifdef USE_SSL 214 | int r = 0; 215 | if (ssl_enabled) { 216 | if (req_idx >= sizeof(req_buf)) 217 | return -1; 218 | 219 | r = SSL_peek(ssl, req_buf + req_idx, sizeof(req_buf) - req_idx); 220 | 221 | // workaround for stupid browsers (chrome) send a single byte 222 | // G,E,T... and expect it to be taken from buffer immediately before sending 223 | // rest of request! 224 | if (r == 1) { 225 | char skip = 0; 226 | SSL_read(ssl, &skip, 1); 227 | ++req_idx; 228 | return 1; 229 | } 230 | 231 | switch (SSL_get_error(ssl, r)) { 232 | case SSL_ERROR_NONE: 233 | break; 234 | case SSL_ERROR_WANT_WRITE: 235 | case SSL_ERROR_WANT_READ: 236 | r = 0; 237 | break; 238 | default: 239 | r = -1; 240 | } 241 | 242 | return r; 243 | } 244 | #endif 245 | 246 | return ::recv(peer_fd, req_buf, sizeof(req_buf) - 1, MSG_PEEK); 247 | } 248 | 249 | 250 | #ifdef USE_SSL 251 | 252 | int http_client::new_session(SSL *ssl, SSL_SESSION *sess) 253 | { 254 | return 1; 255 | } 256 | 257 | 258 | void http_client::remove_session(SSL_CTX *ctx, SSL_SESSION *sess) 259 | { 260 | return; 261 | } 262 | 263 | 264 | SSL_SESSION *http_client::get_session(SSL *ssl, unsigned char *id, int len, int *ref) 265 | { 266 | return NULL; 267 | } 268 | 269 | 270 | int http_client::match_sni(const string &host) 271 | { 272 | if (!ssl_enabled || !ssl || httpd_config::cfile.size() < 2) 273 | return 0; 274 | 275 | const char *sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); 276 | 277 | if (!sni) 278 | return 0; 279 | 280 | return strcasecmp(sni, host.c_str()) == 0 ? 1 : -1; 281 | } 282 | 283 | 284 | 285 | int http_client::ssl_accept(ssl_container *sslc) 286 | { 287 | int r = 0; 288 | 289 | // may be re-entered if no complete handshake has been seen yet 290 | if (!ssl) { 291 | if ((ssl = SSL_new(sslc->find_ctx(""))) == NULL) 292 | return -1; 293 | if (SSL_set_fd(ssl, peer_fd) != 1) 294 | return -1; 295 | } 296 | 297 | r = SSL_accept(ssl); 298 | 299 | switch (SSL_get_error(ssl, r)) { 300 | case SSL_ERROR_NONE: 301 | d_state = STATE_CONNECTED; 302 | ssl_enabled = 1; 303 | keep_alive = 1; 304 | return 1; 305 | 306 | // no complete handshake yet? Try later 307 | case SSL_ERROR_WANT_READ: 308 | case SSL_ERROR_WANT_WRITE: 309 | return 0; 310 | default: 311 | return -1; 312 | } 313 | 314 | return 0; 315 | } 316 | #endif 317 | 318 | 319 | void rproxy_client::cleanup() 320 | { 321 | file_fd = fd = peer_fd = -1; 322 | d_state = STATE_NONE; 323 | type = HTTP_NONE; 324 | node.host.clear(); node.path.clear(); opath.clear(); from_ip.clear(); 325 | blen = chunk_len = 0; 326 | header = 1; 327 | chunked = 0; 328 | alive_time = header_time = 0; 329 | } 330 | 331 | -------------------------------------------------------------------------------- /src/main.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2014 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include "config.h" 48 | #include "lonely.h" 49 | #include "flavor.h" 50 | #include "misc.h" 51 | #include "multicore.h" 52 | 53 | using namespace std; 54 | 55 | 56 | void die(const char *s, bool please_die = 1) 57 | { 58 | perror(s); 59 | if (please_die) 60 | exit(errno); 61 | } 62 | 63 | 64 | void help(const char *p) 65 | { 66 | cerr<<"Usage: "< 'http://...' tag, if operating behind a proxy\n" 86 | <<"\t\t -q : quiet mode; don't generate any logs or index.html files\n" 87 | <<"\t\t -S : sendfile() chunksize (no need to change), default: "<clear_cache(); 130 | } 131 | 132 | 133 | int main(int argc, char **argv) 134 | { 135 | int c = 0; 136 | bool port_was_given = 0; 137 | 138 | cout<<"\nlophttpd -- lots of performance httpd (C) 2008-2018 Sebastian Krahmer\n\n"; 139 | 140 | if (getuid() != 0) { 141 | cerr<<"\a!!! WARNING: !!! Must be called as root in order to chroot() and drop privs properly!\n"; 142 | cerr<<"Continuing in UNSAFE mode!\n\n"; 143 | } 144 | 145 | string::size_type idx = string::npos; 146 | 147 | while ((c = getopt(argc, argv, "iHhR:p:l:L:u:n:S:I:6B:qU:rEQN:C:K:e:s:F")) != -1) { 148 | switch (c) { 149 | case '6': 150 | if (httpd_config::host == "0.0.0.0") 151 | httpd_config::host = "::"; 152 | break; 153 | case 'i': 154 | httpd_config::gen_index = 1; 155 | break; 156 | case 'R': 157 | httpd_config::root = optarg; 158 | break; 159 | case 'H': 160 | httpd_config::virtual_hosts = 1; 161 | break; 162 | case 'u': 163 | httpd_config::user = optarg; 164 | break; 165 | case 'I': 166 | httpd_config::host = optarg; 167 | break; 168 | case 'p': 169 | httpd_config::port = optarg; 170 | port_was_given = 1; 171 | break; 172 | case 'l': 173 | httpd_config::logfile = optarg; 174 | break; 175 | case 'L': 176 | httpd_config::log_provider = optarg; 177 | break; 178 | case 'n': 179 | httpd_config::cores = strtoul(optarg, NULL, 10); 180 | break; 181 | case 'S': 182 | httpd_config::mss = strtoul(optarg, NULL, 10); 183 | break; 184 | case 'B': 185 | httpd_config::base = optarg; 186 | break; 187 | case 'q': 188 | httpd_config::quiet = 1; 189 | break; 190 | case 'U': 191 | httpd_config::upload = optarg; 192 | break; 193 | case 'r': 194 | httpd_config::rand_upload = 1; 195 | break; 196 | case 'Q': 197 | httpd_config::rand_upload = 1; 198 | httpd_config::rand_upload_quiet = 1; 199 | break; 200 | case 'E': 201 | httpd_config::no_error_kill = 1; 202 | break; 203 | case 'e': 204 | httpd_config::ncache = strtoul(optarg, NULL, 10); 205 | break; 206 | case 'N': 207 | httpd_config::max_connections = strtoul(optarg, NULL, 10); 208 | break; 209 | case 'K': 210 | // host:keypath for vHosts+SNI 211 | idx = string(optarg).find(":"); 212 | if (idx == string::npos || idx < 1) 213 | httpd_config::kfile[""] = optarg; 214 | else 215 | httpd_config::kfile[string(optarg, 0, idx)] = string(optarg, idx + 1, string::npos); 216 | break; 217 | case 'C': 218 | // host:certpath for vHosts+SNI 219 | idx = string(optarg).find(":"); 220 | if (idx == string::npos || idx < 1) 221 | httpd_config::cfile[""] = optarg; 222 | else 223 | httpd_config::cfile[string(optarg, 0, idx)] = string(optarg, idx + 1, string::npos); 224 | break; 225 | case 's': 226 | if (strcmp(optarg, "none") == 0) 227 | httpd_config::client_sched = CLIENT_SCHED_NONE; 228 | if (strcmp(optarg, "static") == 0) 229 | httpd_config::client_sched = CLIENT_SCHED_STATIC; 230 | else if (strcmp(optarg, "suspend") == 0) 231 | httpd_config::client_sched = CLIENT_SCHED_SUSPEND; 232 | else if (strcmp(optarg, "minimize") == 0) 233 | httpd_config::client_sched = CLIENT_SCHED_MINIMIZE; 234 | else 235 | help(*argv); 236 | break; 237 | case 'F': 238 | // enable TCP Fast Open on listening socket 239 | httpd_config::tfo = 1; 240 | break; 241 | case 'h': 242 | default: 243 | help(*argv); 244 | } 245 | } 246 | 247 | cout<<"Using webroot '"<pw_uid; 275 | httpd_config::user_gid = pw->pw_gid; 276 | 277 | if (httpd_config::cfile.size() && httpd_config::kfile.size()) { 278 | if (!port_was_given) 279 | httpd_config::port = "443"; 280 | httpd_config::use_ssl = 1; 281 | if (httpd->setup_ssl(httpd_config::cfile, httpd_config::kfile) < 0) { 282 | cerr<<"Unable to initialize SSL, exiting:\n"; 283 | cerr<why()<init(httpd_config::host, httpd_config::port) < 0) { 289 | cerr<why()<open_log(httpd_config::logfile, httpd_config::log_provider, misc::my_core) < 0) { 300 | cerr<<"ERROR: opening logfile: "<why()<vhosts = 1; 331 | 332 | struct sigaction sa; 333 | memset(&sa, 0, sizeof(sa)); 334 | sa.sa_handler = sigusr1; 335 | sa.sa_flags = SA_RESTART; 336 | if (sigaction(SIGUSR1, &sa, NULL) < 0) 337 | die("sigaction"); 338 | 339 | sa.sa_handler = SIG_IGN; 340 | if (sigaction(SIGHUP, &sa, NULL) < 0) 341 | die("sigaction"); 342 | 343 | if (sigaction(SIGPIPE, &sa, NULL) < 0) 344 | die("sigaction"); 345 | 346 | dup2(0, 2); 347 | 348 | if (httpd_config::master) { 349 | if (fork() > 0) 350 | exit(0); 351 | setsid(); 352 | } 353 | 354 | if (flavor::sandbox() < 0) { 355 | httpd->log("Exit: error setting up sandbox"); 356 | exit(1); 357 | } 358 | 359 | httpd->loop(); 360 | 361 | delete httpd; 362 | return 0; 363 | } 364 | 365 | -------------------------------------------------------------------------------- /src/misc.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2014 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include "config.h" 49 | #include "misc.h" 50 | #include "flavor.h" 51 | 52 | 53 | // Android has got not ftw! So we need to write our own 54 | 55 | namespace misc { 56 | 57 | using namespace std; 58 | 59 | enum { 60 | FTW_D = 1, 61 | FTW_F = 2, 62 | FTW_L = 4 63 | }; 64 | 65 | struct ctypes content_types[] = { 66 | // This one must be at index 0 and 1, since we keep a cache of 67 | // file <-> content-type with an index to this table 68 | {".data", "application/data"}, 69 | {".html", "text/html"}, 70 | 71 | {".apk", "application/vnd.android.package-archive"}, 72 | {".avi", "video/x-msvideo"}, 73 | {".bmp", "image/bmp"}, 74 | {".bib", "text/x-bibtex"}, 75 | {".c", "text/x-csrc"}, 76 | {".cc", "text/x-c++src"}, 77 | {".cpp", "text/x-c++src"}, 78 | {".cxx", "text/x-c++src"}, 79 | {".css", "text/css"}, 80 | {".dtd", "text/x-dtd"}, 81 | {".dvi", "application/x-dvi"}, 82 | {".fig", "image/x-xfig"}, 83 | {".flv", "application/flash-video"}, 84 | {".gif", "image/gif"}, 85 | {".gz", "application/gzip"}, 86 | {".h", "text/x-chdr"}, 87 | {".hh", "text/x-chdr"}, 88 | {".htm", "text/html"}, 89 | {".ico", "image/x-ico"}, 90 | {".iso", "application/x-cd-image"}, 91 | {".java", "text/x-java"}, 92 | {".jpg", "image/jpg"}, 93 | {".js", "application/x-javascript"}, 94 | {".mp3", "audio/mpeg"}, 95 | {".mpeg", "video/mpeg"}, 96 | {".mpg", "video/mpeg"}, 97 | {".ogg", "application/ogg"}, 98 | {".pac", "application/x-ns-proxy-autoconfig"}, 99 | {".pdf", "application/pdf"}, 100 | {".pgn", "application/x-chess-pgn"}, 101 | {".pls", "audio/x-scpls"}, 102 | {".png", "image/png"}, 103 | {".ps", "application/postscript"}, 104 | {".ps.gz", "application/x-gzpostscript"}, 105 | {".rar", "application/x-rar-compressed"}, 106 | {".rdf", "text/rdf"}, 107 | {".rss", "text/rss"}, 108 | {".sgm", "text/sgml"}, 109 | {".sgml", "text/sgml"}, 110 | {".svg", "image/svg+xml"}, 111 | {".tar", "application/x-tar"}, 112 | {".tar.Z", "application/x-tarz"}, 113 | {".tgz", "application/gzip"}, 114 | {".tiff", "image/tiff"}, 115 | {".txt", "text/plain"}, 116 | {".wav", "audio/x-wav"}, 117 | {".wmv", "video/x-ms-wm"}, 118 | {".xbm", "image/x-xbitmap"}, 119 | {".x509", "application/x-x509-ca-cert"}, 120 | {".xml", "text/xml"}, 121 | {".zip", "application/zip"}, 122 | {".zoo", "application/x-zoo"}, 123 | {"", ""} 124 | }; 125 | 126 | map dir2index; 127 | string err = ""; 128 | 129 | 130 | int find_ctype(const string &p) 131 | { 132 | int i = 0; 133 | 134 | for (i = 0; !content_types[i].extension.empty(); ++i) { 135 | if (p.size() <= content_types[i].extension.size()) 136 | continue; 137 | if (strcasestr(p.c_str()+p.size() - content_types[i].extension.size(), 138 | content_types[i].extension.c_str())) 139 | break; 140 | } 141 | if (content_types[i].c_type.empty()) 142 | i = 0; 143 | return i; 144 | } 145 | 146 | 147 | // if generated indexes exceed this limit, they 148 | // are written as index.html to disk 149 | const unsigned int index_max_size = 1<<24; 150 | 151 | 152 | int ftw_helper(const char *fpath, const struct stat *st, int typeflag) 153 | { 154 | string pathname = fpath, parent = ""; 155 | 156 | if (pathname.find("//") != string::npos || pathname.find("/../") != string::npos || 157 | pathname.find("/./") != string::npos) 158 | return -1; 159 | 160 | string::size_type base = pathname.find_last_of("/"); 161 | string basename = ""; 162 | char spaces[40]; 163 | 164 | if (base == string::npos) 165 | return 0; 166 | 167 | if (!S_ISDIR(st->st_mode) && !S_ISREG(st->st_mode) && !S_ISBLK(st->st_mode) && 168 | !S_ISLNK(st->st_mode) && !S_ISCHR(st->st_mode)) 169 | return 0; 170 | 171 | if (base == 0) { 172 | parent = "/"; 173 | basename = pathname.substr(1); 174 | } else { 175 | basename = pathname.substr(base + 1); 176 | parent = pathname.substr(0, base); 177 | } 178 | 179 | memset(spaces, ' ', sizeof(spaces) - 1); 180 | spaces[sizeof(spaces) - 1] = 0; 181 | 182 | char *mod_time = ctime((const time_t *)&st->st_mtime), *nl = NULL; 183 | if ((nl = strchr(mod_time, '\n')) != NULL) 184 | *nl = 0; 185 | 186 | if (typeflag & FTW_D) { 187 | string html = "\n"; 188 | if (httpd_config::base.size() > 0) { 189 | html += "\n"; 192 | } 193 | html += "Index of "; 194 | html += fpath; 195 | html += "\n"; 196 | html += "\n

Index of "; 197 | html += fpath; 198 | html += "

";
199 | 		html += "Name                                           Last modified                Size    Content\n
\n"; 200 | html += "[DIR ] tag, which only works for 205 | // relative URLs 206 | if (parent == "/") 207 | html += httpd_config::base; 208 | else 209 | html += parent.substr(1); 210 | html += "\">Parent Directory\n\n"; 211 | 212 | if (dir2index.find(fpath) == dir2index.end()) 213 | dir2index[fpath] = html; 214 | 215 | if (dir2index.find(parent) != dir2index.end()) 216 | html = dir2index[parent]; 217 | if (basename == "") 218 | return 0; 219 | 220 | html += "[DIR ]"; 221 | html += " "; 224 | html += basename; 225 | html += ""; 226 | if (basename.size() < sizeof(spaces)) 227 | html += string(spaces, sizeof(spaces) - basename.size()); 228 | else 229 | html += " "; 230 | 231 | html += mod_time; 232 | html += string(spaces, 13); // size 233 | html += "(directory)\n"; 234 | 235 | dir2index[parent] = html; 236 | } else if (typeflag & FTW_L) { 237 | char lnk[1024], rlnk[4096]; 238 | memset(lnk, 0, sizeof(lnk)); 239 | memset(rlnk, 0, sizeof(rlnk)); 240 | if (readlink(fpath, lnk, sizeof(lnk)) < 0) 241 | return -1; 242 | if (!realpath(lnk, rlnk)) 243 | return -1; 244 | string &html = dir2index[parent]; 245 | html += "[FILE]"; 246 | html += " "; 249 | html += basename; 250 | html += ""; 251 | if (basename.size() < sizeof(spaces)) 252 | html += string(spaces, sizeof(spaces) - basename.size()); 253 | else 254 | html += " "; 255 | html += mod_time; 256 | html += string(spaces, 13); // size 257 | html += "(symlink)\n"; 258 | } else { 259 | string &html = dir2index[parent]; 260 | html += "[FILE]"; 261 | html += " "; 264 | html += basename; 265 | html += ""; 266 | if (basename.size() < sizeof(spaces)) 267 | html += string(spaces, sizeof(spaces) - basename.size()); 268 | else 269 | html += " "; 270 | html += mod_time; 271 | char sbuf[128]; 272 | // st is const 273 | off_t size = st->st_size; 274 | if (!size && flavor::servable_device(*st)) { 275 | flavor::device_size(fpath, size); 276 | } 277 | if (size > 1024*1024*1024) 278 | sprintf(sbuf, " %8.2fGB ", ((double)size)/(1024*1024*1024)); 279 | else if (size > 1024*1024) 280 | sprintf(sbuf, " %8.2fMB ", ((double)size)/(1024*1024)); 281 | else if (size > 1024) 282 | sprintf(sbuf, " %8.2fKB ", ((double)size)/1024); 283 | else 284 | sprintf(sbuf, " %8zdB ", (size_t)size); 285 | 286 | html += sbuf; 287 | int i = find_ctype(basename); 288 | html += content_types[i].c_type; 289 | html += "\n"; 290 | } 291 | return 0; 292 | } 293 | 294 | 295 | 296 | int ftw_once(const char *dir, int (*fn) (const char *fpath, const struct stat *sb, int typeflag), int nopenfd) 297 | { 298 | DIR *dfd = nullptr; 299 | string pathname = ""; 300 | struct dirent *de = nullptr; 301 | struct stat lst; 302 | 303 | if ((dfd = opendir(dir)) == nullptr) 304 | return -1; 305 | 306 | for (;;) { 307 | if ((de = readdir(dfd)) == nullptr) 308 | break; 309 | if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) 310 | continue; 311 | pathname = dir; 312 | if (pathname[pathname.size() - 1] != '/') 313 | pathname += "/"; 314 | pathname += de->d_name; 315 | 316 | if (lstat(pathname.c_str(), &lst) < 0) 317 | continue; 318 | // dont follow symlinks into directories 319 | if (S_ISDIR(lst.st_mode)) { 320 | fn(pathname.c_str(), &lst, FTW_D); 321 | ftw_once(pathname.c_str(), fn, 1); 322 | } else if (S_ISLNK(lst.st_mode)) { 323 | fn(pathname.c_str(), &lst, FTW_L); 324 | } else { 325 | fn(pathname.c_str(), &lst, FTW_F); 326 | } 327 | } 328 | closedir(dfd); 329 | return 0; 330 | } 331 | 332 | 333 | // nopenfd is ignored 334 | int ftw(const char *dir, int (*fn) (const char *fpath, const struct stat *sb, int typeflag), int nopenfd) 335 | { 336 | struct stat st; 337 | 338 | // This function is only to also record the real parent dir 339 | // without having it processed inrecursive calls ever and ever 340 | if (stat(dir, &st) < 0) 341 | return -1; 342 | fn(dir, &st, FTW_D); 343 | 344 | return ftw_once(dir, fn, nopenfd); 345 | } 346 | 347 | 348 | void generate_index(const string &path) 349 | { 350 | map::iterator i; 351 | 352 | ftw(path.c_str(), ftw_helper, 1); 353 | 354 | uid_t euid = geteuid(); 355 | 356 | for (i = dir2index.begin(); i != dir2index.end();) { 357 | string &html = i->second; 358 | html += "

lophttpd powered

"; 359 | 360 | // in quiet mode, dont drop index.html files 361 | if (httpd_config::quiet) { 362 | ++i; 363 | continue; 364 | } 365 | 366 | // if running multicore, only master needs to create files 367 | if (httpd_config::master && html.size() > index_max_size) { 368 | string path = i->first; 369 | path += "/index.html"; 370 | int flags = O_RDWR|O_CREAT; 371 | 372 | // if running initial (root), dont smash existing stuff, 373 | // if USR1 is received, e.g. re-indexing, allow to update 374 | if (euid == 0) 375 | flags |= O_EXCL; 376 | else 377 | flags |= O_TRUNC; 378 | int fd = open(path.c_str(), flags, 0644); 379 | if (fd < 0) { 380 | if (errno == EEXIST) 381 | dir2index.erase(i++); 382 | else 383 | ++i; 384 | continue; 385 | } 386 | write(fd, i->second.c_str(), i->second.size()); 387 | 388 | // own to user, so re-generation of index files 389 | // can really happen 390 | if (euid == 0) 391 | fchown(fd, httpd_config::user_uid, httpd_config::user_gid); 392 | close(fd); 393 | 394 | dir2index.erase(i++); 395 | continue; 396 | } else if (!httpd_config::master && html.size() > index_max_size) { 397 | dir2index.erase(i++); 398 | continue; 399 | } 400 | ++i; 401 | } 402 | } 403 | 404 | 405 | const char *why() 406 | { 407 | return err.c_str(); 408 | } 409 | 410 | 411 | } // namespace misc 412 | 413 | -------------------------------------------------------------------------------- /src/tests/webstress.cc: -------------------------------------------------------------------------------- 1 | /* webstress webserver download bandwidth tesing tool 2 | * (C) 2011-2013 Sebastian Krahmer 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | 24 | 25 | using namespace std; 26 | 27 | 28 | enum { 29 | HTTP_STATE_CONNECTING = 0, 30 | HTTP_STATE_CONNECTED, 31 | HTTP_STATE_HANDSHAKING, 32 | HTTP_STATE_HANDSHAKED, 33 | HTTP_STATE_TRANSFERING 34 | }; 35 | 36 | 37 | class webstress { 38 | 39 | string host, port, path, err; 40 | bool ssl; 41 | int max_cl, peers, ever, ests, success, hdr_fail, write_fail, read_fail, to_fail, hup_fail, max_fd, max_success; 42 | time_t now; 43 | 44 | pollfd *pfds; 45 | 46 | struct client { 47 | size_t obtained, content_length; 48 | int state; 49 | time_t time, start_time; 50 | SSL *ssl; 51 | }; 52 | 53 | map clients; 54 | 55 | static const int TIMEOUT = 60; 56 | 57 | const SSL_METHOD *ssl_method; 58 | SSL_CTX *ssl_ctx; 59 | 60 | public: 61 | webstress(const string &h, const string &p, const string &f, int max, int max_s = -1) 62 | : host(h), port(p), path(f), err(""), ssl(0), max_cl(max), 63 | peers(0), ever(0), ests(0), success(0), hdr_fail(0), write_fail(0), read_fail(0), 64 | to_fail(0), hup_fail(0), max_fd(0), max_success(max_s), pfds(NULL) 65 | { 66 | if (p == "443" || p == "4343") 67 | ssl = 1; 68 | 69 | ssl_method = NULL; 70 | ssl_ctx = NULL; 71 | } 72 | 73 | ~webstress() 74 | { 75 | } 76 | 77 | void calc_max_fd(); 78 | 79 | void max_clients(int n) 80 | { 81 | max_cl = n; 82 | } 83 | 84 | int ssl_init(); 85 | 86 | int recv(int, char *, size_t, int); 87 | 88 | int loop(); 89 | 90 | int cleanup(int); 91 | 92 | void print_stat(int); 93 | 94 | const char *why() 95 | { 96 | return err.c_str(); 97 | } 98 | 99 | }; 100 | 101 | 102 | int writen(int fd, const void *buf, size_t len) 103 | { 104 | int o = 0, n; 105 | char *ptr = (char*)buf; 106 | 107 | while (len > 0) { 108 | if ((n = write(fd, ptr+o, len)) <= 0) { 109 | if (errno == EAGAIN || errno == EWOULDBLOCK) 110 | return o; 111 | return n; 112 | } 113 | len -= n; 114 | o += n; 115 | } 116 | return o; 117 | } 118 | 119 | 120 | int webstress::recv(int fd, char *buf, size_t blen, int flags) 121 | { 122 | int r = 0; 123 | 124 | if (ssl) { 125 | if (!clients[fd]->ssl) 126 | return -1; 127 | 128 | if (flags == MSG_PEEK) { 129 | r = SSL_peek(clients[fd]->ssl, buf, blen); 130 | switch (SSL_get_error(clients[fd]->ssl, r)) { 131 | case SSL_ERROR_NONE: 132 | break; 133 | case SSL_ERROR_WANT_WRITE: 134 | case SSL_ERROR_WANT_READ: 135 | r = 0; 136 | break; 137 | default: 138 | r = -1; 139 | } 140 | } else { 141 | r = SSL_read(clients[fd]->ssl, buf, blen); 142 | switch (SSL_get_error(clients[fd]->ssl, r)) { 143 | case SSL_ERROR_NONE: 144 | break; 145 | case SSL_ERROR_WANT_READ: 146 | r = 0; 147 | break; 148 | default: 149 | r = -1; 150 | } 151 | } 152 | 153 | return r; 154 | } 155 | 156 | return ::recv(fd, buf, blen, flags); 157 | } 158 | 159 | 160 | int webstress::cleanup(int fd) 161 | { 162 | if (fd < 0) 163 | return -1; 164 | shutdown(fd, SHUT_RDWR); 165 | close(fd); 166 | pfds[fd].fd = -1; 167 | pfds[fd].events = pfds[fd].revents = 0; 168 | 169 | if (clients[fd]->state > HTTP_STATE_CONNECTING) 170 | --ests; 171 | 172 | if (ssl) { 173 | if (clients[fd]->ssl) 174 | SSL_free(clients[fd]->ssl); 175 | } 176 | 177 | delete clients[fd]; 178 | clients[fd] = NULL; 179 | --peers; 180 | 181 | if (fd == max_fd) 182 | --max_fd; 183 | 184 | return 0; 185 | } 186 | 187 | 188 | void webstress::print_stat(int fd) 189 | { 190 | if (now - clients[fd]->start_time == 0) 191 | --clients[fd]->start_time; 192 | 193 | char s = 'X'; 194 | switch (clients[fd]->state) { 195 | case HTTP_STATE_CONNECTING: 196 | s = 'c'; 197 | break; 198 | case HTTP_STATE_CONNECTED: 199 | s = 'C'; 200 | break; 201 | case HTTP_STATE_TRANSFERING: 202 | s = 'T'; 203 | break; 204 | case HTTP_STATE_HANDSHAKING: 205 | s = 'h'; 206 | break; 207 | case HTTP_STATE_HANDSHAKED: 208 | s = 'H'; 209 | } 210 | 211 | printf("(%c)[#=%08u][#S=%05u][#P=%05u][#EST=%05u][rf=%05u][hdrf=%05u][wf=%05u][tf=%05u][hf=%05u][cnt=%08u][%s][%f MB/s]\n", 212 | s, ever, success, peers, ests, 213 | read_fail, hdr_fail, write_fail, to_fail, hup_fail, clients[fd]->obtained, 214 | path.c_str(), 215 | (double)clients[fd]->content_length/(now - clients[fd]->start_time)/(1024*1024)); 216 | 217 | if (max_success > 0 && success > max_success) 218 | exit(0); 219 | } 220 | 221 | void webstress::calc_max_fd() 222 | { 223 | for (int i = max_fd; i >= 0; --i) { 224 | if (pfds[i].fd != -1) { 225 | max_fd = i; 226 | return; 227 | } 228 | } 229 | } 230 | 231 | 232 | int webstress::ssl_init() 233 | { 234 | SSL_library_init(); 235 | SSL_load_error_strings(); 236 | OpenSSL_add_all_algorithms(); 237 | OpenSSL_add_all_digests(); 238 | 239 | #if OPENSSL_VERSION_NUMBER >= 0x10100000 240 | if ((ssl_method = TLS_client_method()) == NULL) { 241 | #else 242 | if ((ssl_method = SSLv23_client_method()) == NULL) { 243 | #endif 244 | fprintf(stderr, "ERR: TLSv1_client_method: %s\n", ERR_error_string(ERR_get_error(), NULL)); 245 | return -1; 246 | } 247 | 248 | if ((ssl_ctx = SSL_CTX_new(ssl_method)) == NULL) { 249 | fprintf(stderr, "ERR: SSL_CTX_new: %s\n", ERR_error_string(ERR_get_error(), NULL)); 250 | return -1; 251 | } 252 | SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); 253 | SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL); 254 | SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE); 255 | return 0; 256 | } 257 | 258 | 259 | int webstress::loop() 260 | { 261 | struct rlimit rl; 262 | 263 | if (geteuid() == 0) { 264 | rl.rlim_cur = (1<<16); 265 | rl.rlim_max = rl.rlim_cur; 266 | if (setrlimit(RLIMIT_NOFILE, &rl) < 0) { 267 | err = "webstress::loop::setrlimit:"; 268 | err += strerror(errno); 269 | return -1; 270 | } 271 | } else { 272 | if (getrlimit(RLIMIT_NOFILE, &rl) < 0) { 273 | err = "webstress::loop::getrlimit:"; 274 | err += strerror(errno); 275 | return -1; 276 | } 277 | } 278 | 279 | if (ssl) { 280 | if (ssl_init() < 0) 281 | return -1; 282 | } 283 | 284 | 285 | struct addrinfo *ai = NULL; 286 | int r = 0; 287 | if ((r = getaddrinfo(host.c_str(), port.c_str(), NULL, &ai)) < 0) { 288 | err = "webstress::loop::getaddrinfo:"; 289 | err += gai_strerror(r); 290 | return -1; 291 | } 292 | 293 | pfds = new (nothrow) pollfd[rl.rlim_cur]; 294 | if (!pfds) { 295 | err = "webstress::loop::OOM"; 296 | return -1; 297 | } 298 | 299 | for (unsigned int i = 0; i < rl.rlim_cur; ++i) 300 | pfds[i].fd = -1; 301 | 302 | char GET[1024], buf[4096]; 303 | 304 | snprintf(GET, sizeof(GET), "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", path.c_str(), 305 | host.c_str()); 306 | size_t GET_len = strlen(GET); 307 | 308 | int sock = 0, one = 1; 309 | for (;;) { 310 | now = time(NULL); 311 | while (peers + 10 < max_cl) { 312 | if ((sock = socket(ai->ai_family, SOCK_STREAM, 0)) < 0) { 313 | err = "webstress::loop::socket:"; 314 | err += strerror(errno); 315 | return -1; 316 | } 317 | fcntl(sock, F_SETFL, O_RDWR|O_NONBLOCK); 318 | setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); 319 | if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0 && 320 | errno != EINPROGRESS) { 321 | err = "webstress::loop::connect:"; 322 | err += strerror(errno); 323 | printf("%s\n", err.c_str()); 324 | break; 325 | } 326 | client *c = new client; 327 | memset(c, 0, sizeof(client)); 328 | c->state = HTTP_STATE_CONNECTING; 329 | c->time = now; 330 | clients[sock] = c; 331 | 332 | pfds[sock].fd = sock; 333 | pfds[sock].events = POLLIN|POLLOUT; 334 | pfds[sock].revents = 0; 335 | ++peers; ++ever; 336 | if (sock > max_fd) 337 | max_fd = sock; 338 | } 339 | 340 | if (poll(pfds, max_fd + 1, 10000) < 0) 341 | continue; 342 | 343 | // starts at most at FD 3 344 | for (int i = 3; i <= max_fd ; ++i) { 345 | now = time(NULL); 346 | 347 | if (!clients[i]) 348 | continue; 349 | if (pfds[i].revents == 0 && now - clients[i]->time > TIMEOUT) { 350 | ++to_fail; 351 | print_stat(i); 352 | cleanup(i); 353 | continue; 354 | } 355 | if (pfds[i].revents == 0) 356 | continue; 357 | 358 | if ((pfds[i].revents & (POLLERR|POLLHUP|POLLNVAL)) != 0) { 359 | ++hup_fail; 360 | print_stat(i); 361 | cleanup(i); 362 | continue; 363 | } 364 | 365 | if (clients[i]->state == HTTP_STATE_CONNECTING) { 366 | int e = 0; 367 | socklen_t elen = sizeof(e); 368 | if (getsockopt(i, SOL_SOCKET, SO_ERROR, &e, &elen) < 0) { 369 | cleanup(i); 370 | continue; 371 | } 372 | if (e != 0) { 373 | ++hup_fail; 374 | print_stat(i); 375 | cleanup(i); 376 | continue; 377 | } 378 | ++ests; 379 | 380 | if (ssl) { 381 | clients[i]->state = HTTP_STATE_HANDSHAKING; 382 | clients[i]->ssl = SSL_new(ssl_ctx); 383 | SSL_set_fd(clients[i]->ssl, i); 384 | pfds[i].events = POLLOUT; 385 | } else { 386 | clients[i]->state = HTTP_STATE_CONNECTED; 387 | 388 | if (writen(i, GET, GET_len) <= 0) { 389 | ++write_fail; 390 | print_stat(i); 391 | cleanup(i); 392 | continue; 393 | } 394 | 395 | pfds[i].events = POLLIN; 396 | } 397 | 398 | pfds[i].revents = 0; 399 | clients[i]->start_time = clients[i]->time = now; 400 | 401 | } else if (clients[i]->state == HTTP_STATE_HANDSHAKING) { 402 | r = SSL_connect(clients[i]->ssl); 403 | switch (SSL_get_error(clients[i]->ssl, r)) { 404 | case SSL_ERROR_NONE: 405 | clients[i]->state = HTTP_STATE_HANDSHAKED; 406 | pfds[i].events = POLLOUT; 407 | clients[i]->time = now; 408 | break; 409 | case SSL_ERROR_WANT_READ: 410 | pfds[i].events |= POLLIN; 411 | break; 412 | case SSL_ERROR_WANT_WRITE: 413 | pfds[i].events |= POLLOUT; 414 | break; 415 | default: 416 | print_stat(i); 417 | cleanup(i); 418 | } 419 | pfds[i].revents = 0; 420 | 421 | } else if (clients[i]->state == HTTP_STATE_HANDSHAKED) { 422 | r = SSL_write(clients[i]->ssl, GET, GET_len); 423 | switch (SSL_get_error(clients[i]->ssl, r)) { 424 | case SSL_ERROR_NONE: 425 | clients[i]->state = HTTP_STATE_CONNECTED; 426 | pfds[i].events = POLLIN; 427 | clients[i]->time = now; 428 | break; 429 | case SSL_ERROR_WANT_WRITE: 430 | pfds[i].events = POLLOUT; 431 | break; 432 | default: 433 | print_stat(i); 434 | cleanup(i); 435 | } 436 | pfds[i].revents = 0; 437 | 438 | // just read header and extract Content-Length if found 439 | } else if (clients[i]->state == HTTP_STATE_CONNECTED) { 440 | memset(buf, 0, sizeof(buf)); 441 | r = this->recv(i, buf, sizeof(buf) - 1, MSG_PEEK); 442 | 443 | if (ssl && r == 0) 444 | continue; 445 | 446 | if (r <= 0) { 447 | ++read_fail; 448 | print_stat(i); 449 | cleanup(i); 450 | continue; 451 | } 452 | 453 | char *ptr = NULL; 454 | if ((ptr = strstr(buf, "\r\n\r\n")) == NULL) { 455 | if (now - clients[i]->time > TIMEOUT) { 456 | ++to_fail; 457 | print_stat(i); 458 | cleanup(i); 459 | } 460 | continue; 461 | } 462 | if (this->recv(i, buf, ptr - buf + 4, 0) <= 0) { 463 | ++hdr_fail; 464 | cleanup(i); 465 | continue; 466 | } 467 | clients[i]->state = HTTP_STATE_TRANSFERING; 468 | clients[i]->time = now; 469 | 470 | char *end_ptr = buf + (ptr - buf + 4); 471 | if ((ptr = strcasestr(buf, "Content-Length:")) != NULL) { 472 | ptr += 15; 473 | for (;ptr < end_ptr; ++ptr) { 474 | if (*ptr != ' ') 475 | break; 476 | } 477 | if (ptr >= end_ptr) { 478 | cleanup(i); 479 | continue; 480 | } 481 | clients[i]->content_length = strtoul(ptr, NULL, 10); 482 | } else 483 | clients[i]->content_length = (size_t)-1; 484 | 485 | clients[i]->obtained = 0; 486 | pfds[i].revents = 0; 487 | pfds[i].events = POLLIN; 488 | 489 | // read content 490 | } else if (clients[i]->state == HTTP_STATE_TRANSFERING) { 491 | errno = 0; 492 | r = this->recv(i, buf, sizeof(buf), 0); 493 | 494 | if (ssl && r == 0) 495 | continue; 496 | 497 | if (r <= 0) { 498 | ++read_fail; 499 | cleanup(i); 500 | continue; 501 | } 502 | 503 | clients[i]->obtained += r; 504 | if (clients[i]->obtained == clients[i]->content_length || r == 0) { 505 | if (clients[i]->obtained == clients[i]->content_length) 506 | ++success; 507 | else 508 | ++to_fail; 509 | print_stat(i); 510 | cleanup(i); 511 | continue; 512 | } 513 | pfds[i].revents = 0; 514 | pfds[i].events = POLLIN; 515 | clients[i]->time = now; 516 | } else { 517 | cleanup(i); 518 | } 519 | } 520 | calc_max_fd(); 521 | 522 | } 523 | return 0; 524 | } 525 | 526 | 527 | int main(int argc, char **argv) 528 | { 529 | 530 | if (argc < 5) { 531 | cerr<<"Usage: ws <#clients> [stop-after]\n"; 532 | return 1; 533 | } 534 | 535 | if (atoi(argv[4]) < 20) { 536 | cerr<<"Minimum of 20 clients"; 537 | return 1; 538 | } 539 | 540 | int max_s = -1; 541 | if (argc == 6) 542 | max_s = atoi(argv[5]); 543 | 544 | webstress ws(argv[1], argv[2], argv[3], atoi(argv[4]), max_s); 545 | 546 | if (ws.loop() < 0) 547 | cerr<[:vhost-port] 114 | 115 | If port is `80` it should be omitted. For example if you 116 | have a vhost `localhost:8080` and your web-root is __/srv/www/htdocs__ 117 | and you want to use auto-indexing, create the subdir 118 | 119 | vhostlocalhost:8080 120 | 121 | inside __/srv/www/htdocs__ and run 122 | 123 | # ./lhttpd -R /srv/www/htdocs -i -H -p 8080 124 | 125 | Users can browse `localhost:8080` then. Whatever they type in the address field 126 | of their browser can be appended to "vhost" and created as a subdir. 127 | _lophttpd_ will serve this as a vhost as long as your DNS resolves 128 | to the IP address where _lophttpd_ is running. 129 | 130 | If you are using vhosts with TLS, you may want to use the SNI feature. 131 | _lophttpd_ is using this automatically if you provide additional certificates 132 | for each vhost, in the form of `vhost:certpath` 133 | 134 | # ./lhttpd -C pub.x509 -K key.pem -C localhost:pub1.x509 -K localhost:key1.pem -H -i 135 | 136 | The __pub.x509__ is the default certificate presented to the client if its not 137 | supporting SNI or requesting hosts that are not served. In case the client indicates 138 | __localhost__ via SNI, it will be presented the certificate from __pub1.x509__ 139 | 140 | 141 | The default log-location is __/var/log/lophttpd__. If you change this, take care 142 | that your log-file is located outside the chroot cage. 143 | 144 | You can run _lophttpd_ as normal user on a port >= 1024, but this is INSECURE! 145 | It does not drop privs/chroot then. The aim is to quickly exchange some 146 | ISO's or tar-balls in a separated, secure LAN between friends. 147 | 148 | If you change something in the web-root while lophttpd is running, 149 | e.g. you use auto-indexing and copy new files to web-root or 150 | delete files from there, you need to tell lophttpd by sending 151 | it a `SIGUSR1` signal. It will drop its open file caches and 152 | generates new indexes. 153 | 154 | 155 | Misc 156 | ---- 157 | 158 | If `lhttpd` experiences that the generated indexes exceed a certain limit, 159 | (default 16MB bytes per dir) it writes the appropriate __index.html__ to disk 160 | inside the directory. It wont overwrite existing files. But creation of 161 | large index files makes sense, otherwise it would need to keep 100's 162 | of MB inside RAM. Since `lhttpd` is walking the web-root as root when 163 | generating the index, take care that users don't create large FS-trees 164 | so that `lhttpd` is forced to generate junk. 165 | Generating indexes for large directories, containing 100k of files, can 166 | take some time. If the `-q` parameter is passed, generated indexes 167 | are NOT written to disk and logs are not written. This is to run in a clean 168 | mode on Android devices without leaving any trace if you do forensics 169 | on the device, since _lophttpd_ allows you to download whole partitions 170 | as block devices. 171 | 172 | Since version 0.82 _lophttpd_ contains an experimental feature to 173 | speed up logging. Since writing 1000's of logs per second can 174 | be a bottleneck, _lophttpd_ introduces different log providers 175 | which you can choose via `-L`. 176 | By default it is `file` which means the normal behavior. You can 177 | also say `mmap` Then it is using a mmap-backed buffer which speeds 178 | up logging. `aio` is also supported which uses the POSIX real-time 179 | `aio_` calls. However this could lead to drops of messages if 180 | under very heavy load. Remember that _lophttpd_ is still single threaded, 181 | even with different log providers. (For aio to be really 182 | single threaded you may want to check out my Linux aio implementation 183 | since glibc is using pthreads under the hood.) 184 | 185 | 186 | Multicore support 187 | ----------------- 188 | 189 | Since version 0.86 _lophttpd_ supports multi-core setups for Linux. 190 | If run without any `-n` switch, _lophttpd_ will run one process per core. 191 | You can turn this off by using `-n 1` or you specify the number 192 | of cores you want lophttpd to use. In general, `-n 1` is a good idea, 193 | since even if under heavy load, you rarely need more than one core 194 | for _lophttpd_. 195 | 196 | Since version 0.88 _lophttpd_ has one log-file per core. If you run on two 197 | cores you get __logfile.0__ and __logfile.1__. This way we avoid file locking. 198 | _lophttpd_ is now also using local-time instead of gmtime in the logs, 199 | so take care to have the right TZ environment variable set when starting 200 | `lhttpd` or it will use UTC. 201 | 202 | If you use multi-core support, every process will have its own cache 203 | for open files and stats. Do not run `lhttpd` on large read-only directories w/o 204 | __index.html__ and auto-indexing since it needs to keep a big cache about 205 | indexing information in memory then (per core!). This does not happen 206 | if the directory is writable since _lophttpd_ will dump content into a 207 | __index.html__ file if above a certain threshold (see above). 208 | 209 | 210 | IPv6 211 | ---- 212 | 213 | Since version > 0.91 __lophttpd__ also supports IPv6, by simply using `-6` 214 | command-line switch. The default bind goes to `::` unless 215 | given `-l` local bind address. 216 | You can either run IPv4 or IPv6 in one instance, not both. But you can run 217 | two lophttpd's: one with IPv4 and one with IPv6 if needed. 218 | __Frontend__ reverse proxy also supports IPv6, both on the frontend and 219 | the backend side. You can even mix IPv4 and IPv6 backend nodes. 220 | 221 | 222 | Proxies 223 | ------- 224 | 225 | If you operate _lophttpd_ behind a proxy, you can use the `-B string` 226 | argument so it automatically generates argument 227 | which should be your proxy URL: http://proxy:port/path/. 228 | The base string must end with '/' character and the index which lhttpd 229 | generates is relative to this base. `-B` makes only sense with `-i`. 230 | 231 | 232 | Special files 233 | ------------- 234 | 235 | _lophttpd_ also serves block device files. You need to run it as 236 | root (`-u root`) or as the user who can open the block dev files. This 237 | is not really for production use, (keep the security implications in mind 238 | if running as root) but its sometimes useful on embedded devices. 239 | 240 | As well as device files, _lophttpd_ can serve __/proc__ or __/sys__ file systems. 241 | !!! Be aware about the security consequences and that you sometimes will 242 | get wrong results (proc/ dirs come and go) !!! 243 | 244 | Please note that serving __/proc__, __/sys__ and device files is a special 245 | operation mode and not optimized for thousands of clients (although 246 | its possible to do that) and that some of these files are blocking 247 | so do not try to serve a block device that is a tape drive without a 248 | tape or so. There is no web server which can do that, not even _lophttpd_. 249 | 250 | If your web clients send a lot of requests to non existing files, 251 | e.g. some buggy browsers always request /favicon.ico, no matter of 252 | the base URL, you may consider using `-E` which does not close 253 | the connection after non-fatal errors. That saves overhead of accepting 254 | new connections and can matter if a lot of crippled requests are expected 255 | from legit clients due to buggy browser/setups. 256 | 257 | 258 | File upload 259 | ----------- 260 | 261 | _Lophttpd_ supports file upload (via __PUT__ method) directly, w/o the need 262 | for external CGI scripts or alike. 263 | 264 | On the machine running _lophttpd_, you have to create a directory 265 | named `upload` inside the web-root (and inside your vHost setup if any), 266 | which must be owned by the user you run `lhttpd` as (e.g. 'wwwrun'). Then 267 | start `lhttpd` like: 268 | 269 | # lhttpd -n 1 -i -U /upload 270 | 271 | On client side: 272 | 273 | $ curl --upload-file /path/to/file http://lophttpd.host:port 274 | 275 | Then, the file is transferred to `$WEBROOT/upload/file`. If you additionally 276 | start `lhttpd` with `-r`, a random token is appended to the file when its stored. 277 | If you put an empty __index.html__ inside __upload__, outside visitors cannot 278 | download the uploaded file, as they do not know the URL. If you want that 279 | nobody, not even the up-loader can see the URL, you can use `-Q` 280 | This effectively creates you an upload-only service where you can store 281 | submitted documents which nobody can download. 282 | 283 | 284 | HTTPS 285 | ----- 286 | 287 | Since version 0.98, _lophttpd_ supports HTTPS (TLSv1+). Just generate 288 | a public/private keypair: 289 | 290 | $ openssl genrsa -out serverkey.pem 4096 291 | $ openssl req -new -x509 -nodes -sha1 -key serverkey.pem -out pubkey.x509 292 | 293 | and append `-C pubkey.x509 -K serverkey.pem` to the _lophttpd_ 294 | commandline. You then either import the X509 certificate into your browser 295 | or let it sign by some CA that is in your browsers CA chain. 296 | 297 | As of the Snowden-leaks it became certain that the public CA infrastructure 298 | is subverted and cannot be trusted if you need real security. So you 299 | should setup your own CA (howto is beyond this README) or import 300 | the generated X509 into your browser. 301 | Additionally _lophttpd_ sets a cipher list that is believed to be 302 | secure (no export ciphers, no weak ciphers and no ECC ciphers - 303 | where most parameters are given by NIST). 304 | 305 | If you also want ephemeral keying (known as Perfect Forward Secrecy [PFS]) 306 | you need to call the `newdh` script before you invoke `make`. 307 | This will generate all the necessary DH parameters. Theres nothing more 308 | to do on the `lhttpd` commandline then, PFS will be automatically used 309 | if available. 310 | 311 | 312 | TCP Fast Open 313 | ------------- 314 | 315 | _Lophttpd_ supports TCP Fast Open. To use it, you have to first enable it 316 | in your OS. On Linux to have client and server support for TFO: 317 | 318 | ``` 319 | # echo 3 > /proc/sys/net/ipv4/tcp_fastopen 320 | ``` 321 | 322 | Then you can start `lhttpd` with the `-F` switch to make use of it. 323 | Inside `tests` directory theres a small test program to check support 324 | of it. In order to find out if a server supports TFO, you'd need to use 325 | `tcpdump` on that connection, as from the program output itself you 326 | won't see any difference for server that don't support it. 327 | 328 | 329 | If you have ideas and or offer performance/testing environment please 330 | drop me an email: sebastian.krahmer [at] gmail [dot] com :-) 331 | 332 | 333 | -------------------------------------------------------------------------------- /src/rproxy.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2014 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include "socket.h" 46 | #include "lonely.h" 47 | #include "config.h" 48 | #include "rproxy.h" 49 | #include "client.h" 50 | #include "flavor.h" 51 | 52 | 53 | using namespace rproxy_config; 54 | using namespace ns_socket; 55 | using namespace std; 56 | 57 | 58 | int rproxy::loop() 59 | { 60 | int i = 0, wn = 0, afd = -1, peer_fd = -1; 61 | char from[64]; 62 | ssize_t r = 0; 63 | size_t n = 0; 64 | struct tm tm; 65 | struct timeval tv; 66 | sockaddr_in sin4; 67 | sockaddr_in6 sin6; 68 | sockaddr *sin = (sockaddr *)&sin4; 69 | socklen_t slen = sizeof(sin4); 70 | 71 | if (af == AF_INET6) { 72 | sin = (sockaddr *)&sin6; 73 | slen = sizeof(sin6); 74 | } 75 | 76 | for (;;) { 77 | if (poll(pfds, max_fd + 1, 1000) < 0) 78 | continue; 79 | 80 | memset(&tv, 0, sizeof(tv)); 81 | memset(&tm, 0, sizeof(tm)); 82 | gettimeofday(&tv, NULL); 83 | 84 | // optimization: only stringify time if at least 1s elapsed 85 | if (cur_time != tv.tv_sec) { 86 | cur_time = tv.tv_sec; 87 | localtime_r(&cur_time, &tm); 88 | strftime(local_date, sizeof(local_date), "%a, %d %b %Y %H:%M:%S GMT%z", &tm); 89 | gmtime_r(&cur_time, &tm); 90 | strftime(gmt_date, sizeof(gmt_date), "%a, %d %b %Y %H:%M:%S GMT", &tm); 91 | } 92 | 93 | // assert: pfds[i].fd == i 94 | for (i = first_fd; i <= max_fd; ++i) { 95 | 96 | if (fd2peer[i] && fd2peer[i]->state() == STATE_CLOSING) { 97 | if (heavy_load || cur_time - fd2peer[i]->alive_time > TIMEOUT_CLOSING) { 98 | cleanup(i); 99 | continue; 100 | } 101 | } 102 | 103 | if (pfds[i].fd == -1) 104 | continue; 105 | 106 | if (!fd2peer[i]) 107 | continue; 108 | 109 | peer_idx = i; 110 | peer = fd2peer[i]; 111 | 112 | // timeout hanging connections (with pending data) but not accepting 113 | // socket 114 | if (cur_time - peer->alive_time >= TIMEOUT_ALIVE && 115 | peer->state() != STATE_ACCEPTING && 116 | (peer->blen > 0 || peer->state() == STATE_DECIDING)) { 117 | 118 | // always call fd and its peer in pairs: cleanup() + cleanup() or 119 | // shutdown() + cleanup(). Otherwise re-used fd's can make troubles. 120 | cleanup(peer->peer_fd); 121 | cleanup(i); 122 | continue; 123 | } 124 | 125 | if ((pfds[i].revents & (POLLERR|POLLHUP|POLLNVAL)) != 0) { 126 | if (peer->blen > 0) { 127 | writen(peer->peer_fd, peer->buf, peer->blen); 128 | peer->blen = 0; 129 | } 130 | shutdown(peer->peer_fd); 131 | cleanup(i); 132 | continue; 133 | } 134 | 135 | if (pfds[i].revents == 0) 136 | continue; 137 | 138 | // new connection ready to accept? 139 | if (peer->state() == STATE_ACCEPTING) { 140 | pfds[i].revents = 0; 141 | for (;;) { 142 | heavy_load = 0; 143 | afd = flavor::accept(i, sin, &slen, flavor::NONBLOCK); 144 | if (afd < 0) { 145 | if (errno == EMFILE || errno == ENFILE) 146 | heavy_load = 1; 147 | break; 148 | } 149 | nodelay(afd); 150 | pfds[afd].fd = afd; 151 | pfds[afd].events = POLLIN; 152 | pfds[afd].revents = 0; 153 | 154 | if (!fd2peer[afd]) { 155 | fd2peer[afd] = new (nothrow) rproxy_client; 156 | 157 | if (!fd2peer[afd]) { 158 | err = "OOM"; 159 | close(afd); 160 | return -1; 161 | } 162 | } 163 | 164 | if (af == AF_INET) { 165 | if (inet_ntop(af, &sin4.sin_addr, from, sizeof(from)) == NULL) 166 | continue; 167 | } else { 168 | if (inet_ntop(af, &sin6.sin6_addr, from, sizeof(from)) == NULL) 169 | continue; 170 | } 171 | 172 | fd2peer[afd]->from_ip = from; 173 | fd2peer[afd]->fd = afd; 174 | fd2peer[afd]->transition(STATE_DECIDING); 175 | fd2peer[afd]->alive_time = cur_time; 176 | 177 | pfds[i].events = POLLOUT|POLLIN; 178 | 179 | if (afd > max_fd) 180 | max_fd = afd; 181 | } 182 | continue; 183 | } else if (peer->state() == STATE_DECIDING) { 184 | // Also in DECIDING state, there might be response from server 185 | // to be sent to client 186 | if (pfds[i].revents & POLLOUT) { 187 | // actually data to send? 188 | if ((n = fd2peer[peer->peer_fd]->blen) > 0) { 189 | wn = writen(i, fd2peer[peer->peer_fd]->buf, n); 190 | if (wn <= 0) { 191 | shutdown(peer->peer_fd); 192 | cleanup(i); 193 | continue; 194 | } 195 | // non blocking write couldnt write it all at once 196 | if (wn != (int)n) { 197 | memmove(fd2peer[peer->peer_fd]->buf, 198 | fd2peer[peer->peer_fd]->buf + wn, 199 | n - wn); 200 | pfds[i].events = POLLOUT|POLLIN; 201 | } else { 202 | pfds[i].events = POLLIN; 203 | } 204 | fd2peer[fd2peer[i]->peer_fd]->blen -= wn; 205 | } else 206 | pfds[i].events &= ~POLLOUT; 207 | pfds[i].revents = 0; 208 | continue; 209 | } 210 | 211 | pfds[i].revents = 0; 212 | 213 | // else, there is POLLIN 214 | 215 | peer_fd = mangle_request_header(); 216 | 217 | if (peer_fd == 0) { 218 | pfds[i].events = POLLIN; 219 | continue; 220 | } else if (peer_fd < 0) { 221 | cleanup(peer->peer_fd); 222 | cleanup(i); 223 | continue; 224 | } 225 | 226 | bool same_conn = (peer->peer_fd == peer_fd); 227 | // mangle_request_header() may return the same, already 228 | // esablished, connection if the same URL is requested again 229 | if (!same_conn) { 230 | // exception with the cleanup()+cleanup() pair calling, 231 | // however this one is OK, as the peer_fd is resetted by hand 232 | cleanup(peer->peer_fd); 233 | peer->peer_fd = peer_fd; 234 | } 235 | 236 | peer->transition(STATE_CONNECTED); 237 | peer->alive_time = cur_time; 238 | peer->type = HTTP_CLIENT; 239 | peer->header = 0; 240 | 241 | if (!fd2peer[peer_fd]) { 242 | fd2peer[peer_fd] = new (nothrow) rproxy_client; 243 | if (!fd2peer[peer_fd]) { 244 | err = "OOM"; 245 | cleanup(i); 246 | close(peer_fd); 247 | return -1; 248 | } 249 | } 250 | 251 | // only for new connections: 252 | if (!same_conn) { 253 | fd2peer[peer_fd]->fd = peer_fd; 254 | fd2peer[peer_fd]->peer_fd = i; 255 | fd2peer[peer_fd]->transition(STATE_CONNECTING); 256 | fd2peer[peer_fd]->type = HTTP_SERVER; 257 | } 258 | 259 | fd2peer[peer_fd]->alive_time = cur_time; 260 | 261 | pfds[peer_fd].fd = peer_fd; 262 | pfds[peer_fd].events = POLLIN|POLLOUT; 263 | pfds[peer_fd].revents = 0; 264 | 265 | // only POLLIN, since we just fetched request and need 266 | // to forward it first 267 | pfds[i].events = POLLIN; 268 | if (fd2peer[peer_fd]->blen > 0) 269 | pfds[i].events |= POLLOUT; 270 | 271 | if (peer_fd > max_fd) 272 | max_fd = peer_fd; 273 | } else if (fd2peer[i]->state() == STATE_CONNECTING) { 274 | if (finish_connecting(i) < 0) { 275 | err = "rproxy::loop::"; 276 | err += ns_socket::why(); 277 | cleanup(peer->peer_fd); 278 | cleanup(i); 279 | // log 280 | continue; 281 | } 282 | peer->transition(STATE_CONNECTED); 283 | peer->alive_time = cur_time; 284 | 285 | // POLLOUT too, since mangle_request_header() already slurped data 286 | // from client 287 | pfds[i].events = POLLIN|POLLOUT; 288 | pfds[i].revents = 0; 289 | } else if (fd2peer[i]->state() == STATE_CONNECTED) { 290 | 291 | // peer not ready yet 292 | if (!fd2peer[peer->peer_fd] || 293 | fd2peer[peer->peer_fd]->state() == STATE_CONNECTING) { 294 | pfds[i].revents = 0; 295 | continue; 296 | } 297 | 298 | if (pfds[i].revents & POLLOUT) { 299 | // actually data to send? 300 | if ((n = fd2peer[peer->peer_fd]->blen) > 0) { 301 | wn = writen(i, fd2peer[peer->peer_fd]->buf, n); 302 | if (wn <= 0) { 303 | shutdown(peer->peer_fd); 304 | cleanup(i); 305 | continue; 306 | } 307 | // non blocking write couldnt write it all at once 308 | if (wn != (int)n) { 309 | memmove(fd2peer[peer->peer_fd]->buf, 310 | fd2peer[peer->peer_fd]->buf + wn, 311 | n - wn); 312 | pfds[i].events = POLLOUT|POLLIN; 313 | } else { 314 | pfds[i].events = POLLIN; 315 | } 316 | fd2peer[peer->peer_fd]->blen -= wn; 317 | } else 318 | pfds[i].events &= ~POLLOUT; 319 | } 320 | 321 | if (pfds[i].revents & POLLIN) { 322 | // still data in buffer? dont read() new data 323 | if (fd2peer[i]->blen > 0) { 324 | pfds[i].events |= POLLIN; 325 | pfds[peer->peer_fd].events = POLLOUT|POLLIN; 326 | pfds[i].revents = 0; 327 | continue; 328 | } 329 | 330 | r = more_bytes(); 331 | 332 | if (r < 0) { 333 | // no need to flush data here, as we won't be here 334 | // with fd2peer[i]->blen > 0 335 | shutdown(peer->peer_fd); 336 | cleanup(peer_idx); 337 | continue; 338 | 339 | // could not read complete header or so 340 | } else if (r == 0) { 341 | pfds[peer_idx].events |= POLLIN; 342 | pfds[peer_idx].revents = 0; 343 | continue; 344 | } 345 | 346 | // peer has data to write 347 | pfds[peer->peer_fd].events = POLLOUT|POLLIN; 348 | pfds[i].events |= POLLIN; 349 | } 350 | 351 | pfds[i].revents = 0; 352 | peer->alive_time = cur_time; 353 | fd2peer[peer->peer_fd]->alive_time = cur_time; 354 | } 355 | 356 | } 357 | calc_max_fd(); 358 | } 359 | 360 | return 0; 361 | } 362 | 363 | 364 | ssize_t rproxy::more_client_bytes() 365 | { 366 | ssize_t r = 0; 367 | size_t n = sizeof(peer->buf); 368 | 369 | // already slurped in whole request? 370 | if (peer->chunk_len == 0) { 371 | peer->header = 1; 372 | peer->transition(STATE_DECIDING); 373 | peer->alive_time = cur_time; 374 | peer->header_time = 0; 375 | return 0; 376 | } 377 | 378 | if (peer->chunk_len < sizeof(peer->buf)) 379 | n = peer->chunk_len; 380 | 381 | r = read(peer_idx, peer->buf, n); 382 | 383 | if (r <= 0) 384 | return -1; 385 | 386 | peer->blen = r; 387 | 388 | // ... to change state again after each request 389 | peer->chunk_len -= r; 390 | if (peer->chunk_len == 0) { 391 | peer->header = 1; 392 | peer->transition(STATE_DECIDING); 393 | peer->header_time = 0; 394 | } 395 | 396 | return r; 397 | } 398 | 399 | 400 | ssize_t rproxy::more_bytes() 401 | { 402 | if (peer->type == HTTP_CLIENT) 403 | return more_client_bytes(); 404 | 405 | return more_server_bytes(); 406 | } 407 | 408 | 409 | ssize_t rproxy::more_server_bytes() 410 | { 411 | ssize_t r = 0; 412 | size_t n = sizeof(peer->buf); 413 | char buf[4096], *ptr = NULL; 414 | 415 | 416 | if (peer->header) { 417 | errno = 0; 418 | memset(buf, 0, sizeof(buf)); 419 | if ((r = recv(peer_idx, buf, sizeof(buf) - 1, MSG_PEEK)) <= 0) { 420 | if (errno == EAGAIN) 421 | return 0; 422 | return -1; 423 | } 424 | 425 | // If first read, set initial timestamp for header TO 426 | if (peer->header_time == 0) 427 | peer->header_time = cur_time; 428 | 429 | if ((ptr = strstr(buf, "\r\n\r\n")) == NULL) { 430 | if (cur_time - peer->header_time > TIMEOUT_HEADER) 431 | return -1; 432 | return 0; 433 | } 434 | 435 | size_t hlen = ptr - buf + 4; 436 | 437 | if (hlen >= sizeof(peer->buf)) 438 | return -1; 439 | 440 | if ((r = read(peer_idx, peer->buf, hlen)) != (ssize_t)hlen) 441 | return -1; 442 | 443 | peer->buf[hlen] = 0; 444 | peer->blen = hlen; 445 | peer->header = 0; 446 | 447 | if (mangle_server_reply() < 0) 448 | return -1; 449 | 450 | // read chunk size, if chunked encoding and complete chunk or header has been slurped 451 | } else if (peer->chunked && peer->chunk_len == 0) { 452 | errno = 0; 453 | memset(buf, 0, sizeof(buf)); 454 | if ((r = recv(peer_idx, buf, sizeof(buf) - 1, MSG_PEEK)) <= 0) 455 | return -1; 456 | 457 | // that was the last chunk? 458 | if (strncmp(buf, "0\r\n\r\n", 5) == 0) { 459 | if ((r = read(peer_idx, peer->buf, 5)) != 5) 460 | return -1; 461 | peer->blen = 5; 462 | peer->header = 1; 463 | peer->header_time = 0; 464 | } else { 465 | if ((ptr = strstr(buf, "\r\n")) == NULL) 466 | return -1; 467 | peer->chunk_len = strtoul(buf, NULL, 16); 468 | 469 | if (peer->chunk_len > 0x100000000) 470 | return -1; 471 | 472 | // also need to read in that 'chunksize\r\n' and the \r\n after the chunk 473 | peer->chunk_len += (ptr - buf + 2) + 2; 474 | n = sizeof(peer->buf); 475 | 476 | if (peer->chunk_len < n) 477 | n = peer->chunk_len; 478 | 479 | if ((r = read(peer_idx, peer->buf, n)) <= 0) 480 | return -1; 481 | 482 | peer->blen = r; 483 | peer->chunk_len -= r; 484 | } 485 | } else { 486 | if (peer->chunk_len < n) 487 | n = peer->chunk_len; 488 | 489 | r = read(peer_idx, peer->buf, n); 490 | 491 | if (r <= 0) 492 | return -1; 493 | 494 | peer->blen = r; 495 | peer->chunk_len -= r; 496 | 497 | if (peer->chunk_len == 0 && !peer->chunked) { 498 | peer->header = 1; 499 | peer->header_time = 0; 500 | } 501 | } 502 | 503 | return r; 504 | } 505 | 506 | 507 | int rproxy::mangle_server_reply() 508 | { 509 | if (peer->type != HTTP_SERVER) 510 | return 0; 511 | 512 | size_t blen = peer->blen; 513 | char *hdr_end = NULL, *location = NULL, *nl = NULL, *buf = peer->buf, *ptr = NULL; 514 | 515 | if ((hdr_end = strstr(buf, "\r\n\r\n")) == NULL) 516 | return 0; 517 | if ((size_t)(hdr_end - buf) >= blen) 518 | return 0; 519 | 520 | peer->chunk_len = 0x100000000; 521 | peer->chunked = 0; 522 | if ((ptr = strcasestr(buf, "\nContent-Length:"))) { 523 | ptr += 16; 524 | if (ptr >= hdr_end) 525 | return -1; 526 | peer->chunk_len = strtoul(ptr, NULL, 10); 527 | if (peer->chunk_len > 0x100000000) { 528 | send_error(HTTP_ERROR_414); 529 | return -1; 530 | } 531 | } else if ((ptr = strcasestr(buf, "\nTransfer-Encoding:"))) { 532 | ptr += 19; 533 | if (ptr >= hdr_end) 534 | return -1; 535 | if (strcasestr(ptr, "chunked")) { 536 | peer->chunked = 1; 537 | 538 | // will be assigned in more_server_bytes() 539 | peer->chunk_len = 0; 540 | } 541 | } 542 | 543 | if ((location = strcasestr(buf, "\nLocation:")) == NULL) 544 | return 0; 545 | if (strstr(buf, "Redirect") == NULL) 546 | return 0; 547 | 548 | location += 10; 549 | while (*location == ' ' && location < hdr_end) 550 | ++location; 551 | if ((size_t)(location - buf) >= blen) 552 | return -1; 553 | if ((nl = strchr(location, '\r')) == NULL) 554 | return -1; 555 | 556 | if ((size_t)(nl - buf) >= blen) 557 | return -1; 558 | 559 | map::iterator i = location_map.begin(); 560 | for (; i != location_map.end(); ++i) { 561 | if (strncmp(i->first.c_str(), location, i->first.size()) == 0) 562 | break; 563 | } 564 | 565 | if (i == location_map.end()) 566 | return -1; 567 | 568 | string hdr = string(buf, blen); 569 | string new_loc = rproxy_config::location; 570 | new_loc += i->second; 571 | new_loc += "/"; 572 | 573 | hdr.replace(location - buf, i->first.size(), new_loc); 574 | 575 | if (hdr.size() >= sizeof(peer->buf)) 576 | return -1; 577 | memcpy(buf, hdr.c_str(), hdr.size()); 578 | peer->blen = hdr.size(); 579 | return 0; 580 | } 581 | 582 | 583 | // return -1 on error, 0 if no complete header yet, 584 | // socket fd otherwise 585 | int rproxy::mangle_request_header() 586 | { 587 | char buf[4096], *ptr = NULL, *end_ptr = NULL, *path_begin = NULL, 588 | *path_end = NULL, *host_begin = NULL, *host_end = NULL; 589 | int r = 0; 590 | 591 | 592 | memset(buf, 0, sizeof(buf)); 593 | errno = 0; 594 | if ((r = recv(peer_idx, buf, sizeof(buf) - 1, MSG_PEEK)) <= 0) { 595 | if (errno == EAGAIN) 596 | return 0; 597 | return -1; 598 | } 599 | 600 | // If first read, set initial timestamp for header TO 601 | if (peer->header_time == 0) 602 | peer->header_time = cur_time; 603 | 604 | if ((ptr = strstr(buf, "\r\n\r\n")) == NULL) { 605 | if (cur_time - peer->header_time > TIMEOUT_HEADER) { 606 | send_error(HTTP_ERROR_400); 607 | return -1; 608 | } 609 | return 0; 610 | } 611 | 612 | size_t hlen = ptr - buf + 4; 613 | end_ptr = buf + hlen; 614 | 615 | if (read(peer_idx, buf, hlen) != (ssize_t)hlen) { 616 | send_error(HTTP_ERROR_500); 617 | return -1; 618 | } 619 | buf[hlen] = 0; 620 | 621 | if (strncmp(buf, "GET", 3) != 0 && strncmp(buf, "POST", 4) != 0 && 622 | strncmp(buf, "HEAD", 4) != 0 && strncmp(buf, "PUT", 3) != 0) { 623 | send_error(HTTP_ERROR_405); 624 | return -1; 625 | } 626 | 627 | ptr = buf; 628 | while (*ptr != ' ' && *ptr) 629 | ++ptr; 630 | if (ptr >= end_ptr) { 631 | send_error(HTTP_ERROR_400); 632 | return -1; 633 | } 634 | while (*ptr == ' ') 635 | ++ptr; 636 | if (ptr >= end_ptr || *ptr != '/') { 637 | send_error(HTTP_ERROR_400); 638 | return -1; 639 | } 640 | 641 | const string &from_ip = peer->from_ip; 642 | 643 | path_begin = ptr; 644 | if ((path_end = strchr(ptr, '?')) == NULL) { 645 | if ((path_end = strchr(ptr, ' ')) == NULL) { 646 | if ((path_end = strchr(ptr, '\r')) == NULL) { 647 | send_error(HTTP_ERROR_400); 648 | return -1; 649 | } 650 | } 651 | } 652 | 653 | log(string(buf, path_end - buf)); 654 | 655 | string path = string(path_begin, path_end - path_begin); 656 | if (path.size() < 1) { 657 | send_error(HTTP_ERROR_400); 658 | return -1; 659 | } 660 | 661 | peer->chunk_len = 0; 662 | if ((ptr = strcasestr(buf, "\nContent-Length:"))) { 663 | ptr += 16; 664 | if (ptr >= end_ptr) { 665 | send_error(HTTP_ERROR_400); 666 | return -1; 667 | } 668 | peer->chunk_len = strtoul(ptr, NULL, 10); 669 | if (peer->chunk_len > 0x100000000) { 670 | send_error(HTTP_ERROR_414); 671 | return -1; 672 | } 673 | } else if (strcasestr(buf, "\nTransfer-Encoding:")) { 674 | send_error(HTTP_ERROR_411); 675 | return -1; 676 | } 677 | 678 | // smash any existing X-Forward entries 679 | while ((ptr = strcasestr(buf, "\nX-Forwarded-For"))) { 680 | ptr[1] = 'Y'; 681 | } 682 | if ((ptr = strcasestr(buf, "\nHost:"))) { 683 | ptr += 6; 684 | host_begin = ptr; 685 | if (ptr + 2 >= end_ptr) { 686 | send_error(HTTP_ERROR_400); 687 | return -1; 688 | } 689 | while (*ptr != '\r') 690 | ++ptr; 691 | if (ptr >= end_ptr) { 692 | send_error(HTTP_ERROR_400); 693 | return -1; 694 | } 695 | host_end = ptr; 696 | } 697 | 698 | if (de_escape_path(path) < 0) { 699 | send_error(HTTP_ERROR_400); 700 | return -1; 701 | } 702 | 703 | // Find the longest match for the given path in our URL mapping 704 | map >::iterator i = url_map.begin(), match = url_map.end(); 705 | string::size_type mlen = 0; 706 | for (; i != url_map.end(); ++i) { 707 | if (strncmp(path.c_str(), i->first.c_str(), i->first.size()) == 0) { 708 | if (mlen < i->first.size()) { 709 | match = i; 710 | mlen = i->first.size(); 711 | } 712 | } 713 | } 714 | 715 | // No match? Do not kill connection, as some buggy browsers always 716 | // send a request for /favicon.ico, no matter what html-base is 717 | if (!mlen) { 718 | send_error(HTTP_ERROR_404, 0); 719 | return 0; 720 | } 721 | 722 | 723 | bool same_conn = 0; 724 | struct rproxy_config::backend b; 725 | 726 | // Is it the same path as in a possible earlier request? 727 | // Then we dont need to open a new connection 728 | if (match->first == peer->opath && peer->peer_fd > 0) { 729 | same_conn = 1; 730 | b = peer->node; 731 | } else { 732 | // Already decided about a node for this IP/path combination? 733 | map, struct backend>::iterator j = 734 | client_map.find(make_pair(from_ip, match->first)); 735 | 736 | if (j != client_map.end()) { 737 | b = j->second; 738 | } else { 739 | b = match->second.front(); 740 | match->second.pop_front(); 741 | match->second.push_back(b); 742 | client_map[make_pair(from_ip, match->first)] = b; 743 | } 744 | } 745 | 746 | // build new header, replacing Path and Host 747 | // Replace Host: first, so the offsets dont become invalid, 748 | // since Host: is located after Path 749 | string new_hdr = buf; 750 | if (host_begin) { 751 | string s = b.host; 752 | if (b.port != 80) { 753 | char p[12]; 754 | snprintf(p, sizeof(p), ":%hu", b.port); 755 | s += p; 756 | } 757 | new_hdr.replace(host_begin - buf, host_end - host_begin, s); 758 | } else { 759 | string s = "Host: "; 760 | s += b.host; 761 | if (b.port != 80) { 762 | char p[12]; 763 | snprintf(p, sizeof(p), ":%hu", b.port); 764 | s += p; 765 | } 766 | s += "\r\n\r\n"; 767 | // replace \r\n\r\n by \r\nHost: ...\r\n\r\n 768 | new_hdr.replace(hlen - 2, 2, s); 769 | } 770 | 771 | new_hdr.replace(path_begin - buf, mlen, b.path); 772 | 773 | string xfwd = "X-Forwarded-For: "; 774 | xfwd += from_ip; 775 | xfwd += "\r\n\r\n"; 776 | new_hdr.replace(new_hdr.size() - 2, 2, xfwd); 777 | 778 | if (new_hdr.size() >= sizeof(peer->buf)) { 779 | send_error(HTTP_ERROR_414); 780 | return -1; 781 | } 782 | 783 | memcpy(peer->buf, new_hdr.c_str(), new_hdr.size()); 784 | peer->blen = new_hdr.size(); 785 | 786 | if (same_conn) 787 | return peer->peer_fd; 788 | 789 | peer->opath = match->first; 790 | peer->node = b; 791 | return tcp_connect_nb(b.ai, 0); 792 | } 793 | 794 | 795 | int rproxy::de_escape_path(string &p) 796 | { 797 | if (p.find("%") == string::npos) 798 | return 0; 799 | 800 | string tmp; 801 | tmp.resize(16); 802 | size_t pos = 0; 803 | unsigned char c = 0, c1, c2; 804 | tmp = ""; 805 | while ((pos = p.find("%")) != string::npos) { 806 | // must have at least 2 chars after % escape 807 | if (pos > p.size() - 3) 808 | return -1; 809 | if (!isxdigit(p[pos + 1]) || !isxdigit(p[pos + 2])) 810 | return -1; 811 | c1 = toupper(p[pos + 1]); 812 | c2 = toupper(p[pos + 2]); 813 | if (c1 >= 'A' && c1 <= 'F') 814 | c = (10 + c1 - 'A')<<4; 815 | else 816 | c = (c1 - '0')<<4; 817 | if (c2 >= 'A' && c2 <= 'F') 818 | c += (10 + c2 - 'A'); 819 | else 820 | c += (c2 - '0'); 821 | if (c == '\r' || c == 0 || c == '\n' || c == ' ') 822 | return -1; 823 | tmp.push_back(c); 824 | p.replace(pos, 3, tmp, 0, 1); 825 | tmp = ""; 826 | } 827 | return 0; 828 | } 829 | 830 | 831 | 832 | int rproxy::send_error(http_error_code_t e, bool kill_conn) 833 | { 834 | string http_header = "HTTP/1.1 "; 835 | 836 | if (e >= HTTP_ERROR_END) 837 | e = HTTP_ERROR_400; 838 | http_header += http_error_msgs[e]; 839 | http_header += "\r\nServer: lophttpd\r\nDate: "; 840 | http_header += gmt_date; 841 | 842 | if (e == HTTP_ERROR_405) 843 | http_header += "\r\nAllow: GET, HEAD, POST, PUT"; 844 | 845 | http_header += "\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"; 846 | 847 | if (writen(peer_idx, http_header.c_str(), http_header.size()) <= 0) 848 | return -1; 849 | 850 | if (kill_conn) 851 | shutdown(peer_idx); 852 | return 0; 853 | } 854 | 855 | 856 | --------------------------------------------------------------------------------