├── Makefile ├── src ├── Makefile ├── regress │ └── usr.sbin │ │ └── httpd │ │ ├── Makefile │ │ ├── tests │ │ ├── args-default.pl │ │ ├── args-get-1048576.pl │ │ ├── args-tls.pl │ │ ├── args-get-512.pl │ │ ├── args-log-user-agent.pl │ │ ├── args-tls-get-1073741824.pl │ │ ├── args-get-1073741824.pl │ │ ├── args-get-slash.pl │ │ ├── args-get-range-512.pl │ │ ├── args-get-range-multipart.pl │ │ ├── args-tls-verify.pl │ │ ├── args-tls-get-range-512.pl │ │ ├── args-tls-get-range-multipart.pl │ │ ├── LICENSE │ │ ├── README │ │ ├── httpd.pl │ │ ├── Client.pm │ │ ├── Makefile │ │ ├── Httpd.pm │ │ ├── Proc.pm │ │ └── funcs.pl │ │ └── patterns │ │ ├── patterns-tester.lua │ │ ├── Makefile │ │ ├── test-patterns.in │ │ ├── test-patterns.out │ │ ├── patterns-tester.c │ │ └── test-patterns-lua.out ├── usr.sbin │ └── httpd │ │ ├── getdtablecount.h │ │ ├── patterns.h │ │ ├── Makefile │ │ ├── blacklist.c │ │ ├── getdtablecount.c │ │ ├── blacklist_client.h │ │ ├── compat.h │ │ ├── httpd.8 │ │ ├── log.c │ │ ├── control.c │ │ ├── http.h │ │ ├── logger.c │ │ ├── patterns.7 │ │ ├── patterns.c │ │ ├── server_file.c │ │ └── proc.c ├── etc │ └── examples │ │ └── httpd.conf ├── usr.bin │ └── htpasswd │ │ ├── Makefile │ │ ├── htpasswd.1 │ │ └── htpasswd.c ├── lib │ └── libc │ │ ├── crypt │ │ ├── cryptutil.c │ │ └── bcrypt.c │ │ └── gen │ │ └── vis.c ├── include │ └── blf.h └── share │ └── mk │ └── bsd.regress.mk ├── .gitmodules ├── .gitignore ├── files └── obhttpd.in ├── README.md └── Makefile.cipier /Makefile: -------------------------------------------------------------------------------- 1 | SUBDIR= libimsg/src libevent src 2 | 3 | .include 4 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | SUBDIR= usr.bin/htpasswd usr.sbin/httpd 2 | 3 | .include 4 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/Makefile: -------------------------------------------------------------------------------- 1 | # $OpenBSD: Makefile,v 1.2 2015/07/16 16:35:57 reyk Exp $ 2 | 3 | SUBDIR += patterns tests 4 | 5 | .include 6 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/getdtablecount.h: -------------------------------------------------------------------------------- 1 | #ifndef GETDTABLECOUNT_H 2 | #define GETDTABLECOUNT_H 3 | 4 | #ifndef HAVE_GETDTABLECOUNT 5 | int getdtablecount(void); 6 | #endif 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/args-default.pl: -------------------------------------------------------------------------------- 1 | # test default values 2 | 3 | use strict; 4 | use warnings; 5 | 6 | our %args = ( 7 | len => 512, 8 | md5 => path_md5("512") 9 | ); 10 | 11 | 1; 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libimsg"] 2 | path = libimsg 3 | url = https://github.com/koue/libimsg 4 | ignore = dirty 5 | [submodule "libevent"] 6 | path = libevent 7 | url = https://github.com/koue/libevent 8 | ignore = dirty 9 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/patterns/patterns-tester.lua: -------------------------------------------------------------------------------- 1 | -- $OpenBSD: patterns-tester.lua,v 1.1 2015/06/23 18:03:09 semarie Exp $ 2 | print(string.format("string='%s'\npattern='%s'", arg[1], arg[2])); 3 | print(string.match(arg[1], arg[2])); 4 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/args-get-1048576.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | my $len = 1048576; 5 | our %args = ( 6 | client => { 7 | path => "$len", 8 | len => $len, 9 | http_vers => [ "1.0" ], 10 | }, 11 | len => 1048576, 12 | md5 => path_md5("$len") 13 | ); 14 | 15 | 1; 16 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/args-tls.pl: -------------------------------------------------------------------------------- 1 | # test https connection 2 | 3 | use strict; 4 | use warnings; 5 | 6 | our %args = ( 7 | client => { 8 | tls => 1, 9 | loggrep => 'Issuer.*/OU=ca/', 10 | }, 11 | httpd => { 12 | listentls => 1, 13 | }, 14 | len => 512, 15 | md5 => path_md5("512") 16 | ); 17 | 18 | 1; 19 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/args-get-512.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | my $len = 512; 5 | my @lengths = ($len, $len, $len); 6 | our %args = ( 7 | client => { 8 | path => "$len", 9 | http_vers => [ "1.0" ], 10 | lengths => \@lengths, 11 | }, 12 | md5 => path_md5("$len"), 13 | lengths => \@lengths, 14 | ); 15 | 16 | 1; 17 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/args-log-user-agent.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | our %args = ( 5 | client => { 6 | header => { 7 | "User-Agent" => "regress\t\n\nGET / HTTP/1.0\r\n" 8 | } 9 | }, 10 | httpd => { 11 | loggrep => { 12 | qr/\"regress\\t\\n\\nGET \/ HTTP\/1\.0\"/ => 1, 13 | }, 14 | }, 15 | ); 16 | 17 | 1; 18 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/args-tls-get-1073741824.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | my $len = 1073741824; 5 | our %args = ( 6 | client => { 7 | tls => 1, 8 | path => "$len", 9 | len => $len, 10 | }, 11 | httpd => { 12 | listentls => 1, 13 | }, 14 | len => $len, 15 | md5 => path_md5("$len"), 16 | ); 17 | 18 | 1; 19 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/args-get-1073741824.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | my $len = 1073741824; 5 | my @lengths = ($len, $len); 6 | our %args = ( 7 | client => { 8 | path => "$len", 9 | http_vers => [ "1.0" ], 10 | lengths => \@lengths, 11 | }, 12 | md5 => path_md5("$len"), 13 | lengths => \@lengths, 14 | ); 15 | 16 | 1; 17 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/args-get-slash.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | our %args = ( 5 | client => { 6 | func => sub { 7 | my $self = shift; 8 | print "GET /\r\n\r\n"; 9 | }, 10 | nocheck => 1 11 | }, 12 | httpd => { 13 | loggrep => { 14 | qr/"GET \/" 400 0/ => 1, 15 | }, 16 | }, 17 | ); 18 | 19 | 1; 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .depend 2 | *.crt 3 | *.debug 4 | *.full 5 | *.gz 6 | *.key 7 | *.log 8 | *.o 9 | *.req 10 | *.srl 11 | md5-* 12 | src/regress/usr.sbin/httpd/patterns/patterns-tester 13 | src/regress/usr.sbin/httpd/tests/htdocs 14 | src/regress/usr.sbin/httpd/tests/httpd.conf 15 | src/usr.bin/htpasswd/htpasswd 16 | src/usr.sbin/httpd/httpd 17 | src/usr.sbin/httpd/parse.c 18 | src/usr.sbin/httpd/parse.h 19 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/args-get-range-512.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | my $len = 512; 5 | my $path = 1048576; 6 | our %args = ( 7 | client => { 8 | path => $path, 9 | http_vers => [ "1.1" ], 10 | code => "206 Partial Content", 11 | header => { 12 | "Range" => "bytes=0-511", 13 | } 14 | }, 15 | len => $len, 16 | md5 => path_md5("$len") 17 | ); 18 | 19 | 1; 20 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/args-get-range-multipart.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | my $len = 512; 5 | our %args = ( 6 | client => { 7 | path => $len, 8 | http_vers => [ "1.1" ], 9 | code => "206 Partial Content", 10 | header => { 11 | "Range" => "bytes=0-255,256-300,301-", 12 | }, 13 | multipart => 1 14 | }, 15 | len => $len, 16 | md5 => path_md5("$len") 17 | ); 18 | 19 | 1; 20 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/args-tls-verify.pl: -------------------------------------------------------------------------------- 1 | # test https connection, verifying client cert 2 | 3 | use strict; 4 | use warnings; 5 | 6 | our %args = ( 7 | client => { 8 | tls => 1, 9 | offertlscert => 1, 10 | loggrep => 'Issuer.*/OU=ca/', 11 | }, 12 | httpd => { 13 | listentls => 1, 14 | verifytls => 1, 15 | }, 16 | len => 512, 17 | md5 => path_md5("512") 18 | ); 19 | 20 | 1; 21 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/args-tls-get-range-512.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | my $len = 512; 5 | my $path = 1048576; 6 | our %args = ( 7 | client => { 8 | path => $path, 9 | http_vers => [ "1.1" ], 10 | code => "206 Partial Content", 11 | header => { 12 | "Range" => "bytes=0-511", 13 | }, 14 | tls => 1 15 | }, 16 | httpd => { 17 | listentls => 1 18 | }, 19 | len => $len, 20 | md5 => path_md5("$len") 21 | ); 22 | 23 | 1; 24 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/args-tls-get-range-multipart.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | my $len = 1048576; 5 | our %args = ( 6 | client => { 7 | path => $len, 8 | http_vers => [ "1.1" ], 9 | code => "206 Partial Content", 10 | header => { 11 | "Range" => "bytes=0-255,256-10240,10241-", 12 | }, 13 | multipart => 1, 14 | tls => 1 15 | }, 16 | httpd => { 17 | listentls => 1 18 | }, 19 | len => $len, 20 | md5 => path_md5("$len") 21 | ); 22 | 23 | 1; 24 | -------------------------------------------------------------------------------- /files/obhttpd.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # PROVIDE: obhttpd 4 | # REQUIRE: DAEMON 5 | # BEFORE: LOGIN 6 | # KEYWORD: shutdown 7 | 8 | # Add the following lines to /etc/rc.conf to enable obhttpd: 9 | # obhttpd_enable="YES" 10 | # obhttpd_flags="" 11 | 12 | . /etc/rc.subr 13 | 14 | name=obhttpd 15 | rcvar=obhttpd_enable 16 | 17 | command="%%PREFIX%%/sbin/obhttpd" 18 | 19 | obhttpd_enable=${obhttpd_enable:-"NO"} 20 | obhttpd_flags=${obhttpd_flags:-"-f %%PREFIX%%/etc/obhttpd.conf"} 21 | 22 | load_rc_config $name 23 | run_rc_command "$1" 24 | -------------------------------------------------------------------------------- /src/etc/examples/httpd.conf: -------------------------------------------------------------------------------- 1 | # $OpenBSD: httpd.conf,v 1.22 2020/11/04 10:34:18 denis Exp $ 2 | 3 | chroot "/var/www" 4 | 5 | server "example.com" { 6 | listen on * port 80 7 | location "/.well-known/acme-challenge/*" { 8 | root "/acme" 9 | request strip 2 10 | } 11 | location * { 12 | block return 302 "https://$HTTP_HOST$REQUEST_URI" 13 | } 14 | } 15 | 16 | server "example.com" { 17 | listen on * tls port 443 18 | tls { 19 | certificate "/etc/ssl/example.com.fullchain.pem" 20 | key "/etc/ssl/private/example.com.key" 21 | } 22 | location "/pub/*" { 23 | directory auto index 24 | } 25 | location "/.well-known/acme-challenge/*" { 26 | root "/acme" 27 | request strip 2 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/usr.bin/htpasswd/Makefile: -------------------------------------------------------------------------------- 1 | # $OpenBSD: Makefile,v 1.4 2017/07/09 21:23:19 espie Exp $ 2 | LOCALBASE?= /usr/local 3 | BINDIR?= ${LOCALBASE}/bin 4 | 5 | PROG= htpasswd 6 | SRCS= htpasswd.c 7 | 8 | .PATH: ${.CURDIR}/../../lib/libc/crypt 9 | SRCS+= bcrypt.c \ 10 | blowfish.c \ 11 | cryptutil.c 12 | 13 | .ifdef SSLDIR 14 | LDADD= ${SSLDIR}/crypto/.libs/libcrypto.a 15 | .else 16 | LDADD= /usr/local/lib/libcrypto.a 17 | .endif 18 | 19 | DPADD= ${LIBCRYPTO} 20 | 21 | CFLAGS+= -W -Wall 22 | CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations 23 | CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual -Wsign-compare 24 | CFLAGS+= -I${.CURDIR}/../../usr.sbin/httpd \ 25 | -I${.CURDIR}/../../include 26 | .ifdef SSLDIR 27 | CFLAGS+= -I${SSLDIR}/include 28 | .else 29 | CFLAGS+= -I${LOCALBASE}/include 30 | .endif 31 | 32 | CFLAGS+= -D__dead='' 33 | 34 | .include 35 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/LICENSE: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2021 Alexander Bluhm 2 | # Copyright (c) 2014,2015 Reyk Floeter 3 | # 4 | # Permission to use, copy, modify, and distribute this software for any 5 | # purpose with or without fee is hereby granted, provided that the above 6 | # copyright notice and this permission notice appear in all copies. 7 | # 8 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/README: -------------------------------------------------------------------------------- 1 | Run httpd regression tests. The framework runs a client and an httpd. 2 | Each test creates a special httpd.conf and starts those two processes. 3 | All processes write log files that are checked for certain messages. 4 | The test arguments are kept in the args-*.pl files. 5 | 6 | SUDO=doas 7 | As httpd needs root privileges, either run the tests as root or set 8 | this variable and run make as a regular user. Only the code that 9 | requires it is run as root. 10 | 11 | KTRACE=ktrace 12 | Set this variable if you want a ktrace output from httpd. Note that 13 | ktrace is invoked after SUDO as SUDO would disable it. 14 | 15 | HTTPD=/usr/src/usr.sbin/httpd/obj/httpd 16 | Start an alternative httpd program that is not in the path. 17 | 18 | HTDOCS_SPARSE=no 19 | Set to anything other than "yes" to create real test files instead of 20 | sparse files. This needs more than 1G of free disk space. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FreeBSD port of OpenBSD httpd 2 | 3 | The httpd daemon is a HTTP server with FastCGI and TLS support. 4 | 5 | http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.sbin/httpd/ 6 | 7 | ## Installation 8 | 9 | ### Requirements 10 | * libressl 11 | * libimsg 12 | * libevent 13 | 14 | ``` 15 | pkg install libressl-devel 16 | git submodule update --init 17 | make 18 | cd src/regress/usr.sbin/httpd/tests/ && make 19 | cd - && make install 20 | ``` 21 | 22 | ## Usage 23 | 24 | `httpd -f etc/examples/httpd.conf` 25 | 26 | ## Blacklist 27 | 28 | Blacklistd(8) is a daemon that blocks and releases ports on demand. To compile 29 | httpd with blacklistd support use `make -DUSE_BLACKLIST`. To use blacklistd 30 | along with httpd add `block drop` option in httpd.conf. For example: 31 | ``` 32 | location "/*.php" { 33 | block drop 34 | } 35 | ``` 36 | All requests looking for php files will be dropped by httpd and the IP address 37 | of the client will be send to blacklistd. 38 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/patterns/Makefile: -------------------------------------------------------------------------------- 1 | # $OpenBSD: Makefile,v 1.2 2015/06/23 19:33:06 reyk Exp $ 2 | 3 | HTTPDSRC = ${.CURDIR}/../../../../usr.sbin/httpd 4 | 5 | .PATH: ${HTTPDSRC} 6 | 7 | REGRESS_TARGETS= test-patterns 8 | 9 | CLEANFILES += patterns-tester 10 | 11 | #LUA?= lua53 12 | .ifdef LUA 13 | REGRESS_TARGETS+= test-patterns-lua 14 | .endif 15 | 16 | patterns-tester: patterns-tester.c patterns.c patterns.h 17 | ${CC} -o $@ ${CFLAGS} ${.CURDIR}/patterns-tester.c ${HTTPDSRC}/patterns.c -I${HTTPDSRC} 18 | 19 | test-patterns: patterns-tester test-patterns.out test-patterns.in 20 | cat ${.CURDIR}/test-patterns.in | grep -v '^#' | \ 21 | while IFS=' ' read string pattern comments ; do \ 22 | ./patterns-tester "$${string}" "$${pattern}" 2>&1 || true; \ 23 | done | diff -I 'OpenBSD' -u ${.CURDIR}/test-patterns.out - 24 | 25 | test-patterns-lua: patterns-tester.lua test-patterns-lua.out test-patterns.in 26 | cat ${.CURDIR}/test-patterns.in | grep -v '^#' | \ 27 | while IFS=' ' read string pattern comments ; do \ 28 | ${LUA} ${.CURDIR}/patterns-tester.lua "$${string}" "$${pattern}" 2>&1 || true; \ 29 | done | sed "s/.*\/patterns\-tester\.lua/X_PATTERNS_TESTER_X/g" | \ 30 | diff -I 'OpenBSD' -u ${.CURDIR}/test-patterns-lua.out - 31 | 32 | .include 33 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/patterns/test-patterns.in: -------------------------------------------------------------------------------- 1 | # $OpenBSD: test-patterns.in,v 1.1 2015/06/23 18:03:09 semarie Exp $ 2 | # string pattern comments 3 | /page/51 ^/(%a+)/(%d+)$ 4 | /Apage/51 /[^%d][%w%u][^%c]+()[%d]+ 5 | /^page/51 /^(.a.e)/(.) 6 | /page/page-51 /(.*)/%1-(%d+) 7 | /page/[51] /page/(%b[]) 8 | :-] ]+ 9 | :-) [)]+ 10 | /page/51 $^ 11 | 1234567890 ([2-5]-) 12 | **** ^**$ equiv '[*]*' 13 | xxxx ^x*$ same as before 14 | /page/51 no-%d-match no match 15 | /page/page-51 /(.*)/%9-(%d+) invalid capture index 16 | :-) )+ invalid pattern capture 17 | /page/51 /page/51( unfinished capture 18 | /page/51 /page/51% malformed pattern (ends with '%') 19 | /page/51 /page/[51 malformed pattern (missing ']') 20 | /page/(51) /page/%b( malformed pattern (missing arguments to '%b') 21 | /page/51 ()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()() too many captures 22 | /page/51 /page/%f missing '[' after '%f' in pattern 23 | /page/51 /page%f/51 missing '[' after '%f' in pattern 24 | q********************************* *************************************q max repetition items 25 | q+++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++q max repetition items 26 | q--------------------------------- -------------------------------------q max repetition items 27 | q????????????????????????????????? ?????????????????????????????????????q max repetition items 28 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/patterns.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: patterns.h,v 1.3 2015/12/12 19:59:43 mmcc Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2015 Reyk Floeter 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #ifndef PATTERNS_H 20 | #define PATTERNS_H 21 | 22 | #include 23 | 24 | #define MAXCAPTURES 32 /* Max no. of allowed captures in pattern */ 25 | #define MAXCCALLS 200 /* Max recusion depth in pattern matching */ 26 | #define MAXREPETITION 0xfffff /* Max for repetition items */ 27 | 28 | struct str_find { 29 | off_t sm_so; /* start offset of match */ 30 | off_t sm_eo; /* end offset of match */ 31 | }; 32 | 33 | struct str_match { 34 | char **sm_match; /* allocated array of matched strings */ 35 | unsigned int sm_nmatch; /* number of elements in array */ 36 | }; 37 | 38 | __BEGIN_DECLS 39 | int str_find(const char *, const char *, struct str_find *, size_t, 40 | const char **); 41 | int str_match(const char *, const char *, struct str_match *, 42 | const char **); 43 | void str_match_free(struct str_match *); 44 | __END_DECLS 45 | 46 | #endif /* PATTERNS_H */ 47 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/Makefile: -------------------------------------------------------------------------------- 1 | # $OpenBSD: Makefile,v 1.30 2017/07/03 22:21:47 espie Exp $ 2 | 3 | LOCALBASE?= /usr/local 4 | BINDIR?= ${LOCALBASE}/sbin 5 | EVENTDIR?= ${.CURDIR}/../../../libevent 6 | IMSGDIR?= ${.CURDIR}/../../../libimsg 7 | 8 | PROG= httpd 9 | SRCS= parse.y 10 | SRCS+= config.c control.c httpd.c log.c logger.c proc.c server.c 11 | SRCS+= server_http.c server_file.c server_fcgi.c 12 | .ifdef USE_BLACKLIST 13 | SRCS+= blacklist.c 14 | .endif 15 | 16 | 17 | .PATH: ${.CURDIR}/../../lib/libc/gen 18 | SRCS+= vis.c 19 | 20 | .PATH: ${.CURDIR}/../../lib/libc/crypt 21 | SRCS+= bcrypt.c \ 22 | cryptutil.c 23 | 24 | MAN= httpd.8 httpd.conf.5 25 | 26 | LDADD= ${EVENTDIR}/src/lib/libevent/libevent.a \ 27 | ${IMSGDIR}/src/lib/libutil/libimsg.a 28 | .ifdef SSLDIR 29 | LDADD+= ${SSLDIR}/tls/.libs/libtls.a \ 30 | ${SSLDIR}/ssl/.libs/libssl.a \ 31 | ${SSLDIR}/crypto/.libs/libcrypto.a 32 | .else 33 | LDADD+= /usr/local/lib/libtls.a \ 34 | /usr/local/lib/libssl.a \ 35 | /usr/local/lib/libcrypto.a 36 | .endif 37 | 38 | LDADD+= -lcrypt -lprocstat -pthread 39 | .ifdef USE_BLACKLIST 40 | LDADD+= -lblacklist 41 | .endif 42 | SRCS+= patterns.c 43 | MAN+= patterns.7 44 | 45 | DPADD= ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} 46 | #DEBUG= -g -DDEBUG=3 -O0 47 | CFLAGS+= -Wall -I${.CURDIR} 48 | CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes 49 | CFLAGS+= -Wmissing-declarations 50 | CFLAGS+= -Wshadow -Wpointer-arith 51 | CFLAGS+= -Wsign-compare -Wcast-qual 52 | CFLAGS+= -I${EVENTDIR}/src/lib/libevent \ 53 | -I${IMSGDIR}/src/lib/libutil \ 54 | -I${.CURDIR}/../../include 55 | 56 | .ifdef SSLDIR 57 | CFLAGS+= -I${SSLDIR}/include 58 | .else 59 | CFLAGS+= -I${LOCALBASE}/include 60 | .endif 61 | 62 | .ifdef USE_BLACKLIST 63 | CFLAGS+= -DUSE_BLACKLIST 64 | .endif 65 | 66 | CFLAGS+= -D__dead='' \ 67 | -DHAVE_CONFIG_H 68 | CLEANFILES+= y.tab.h 69 | YFLAGS= 70 | 71 | .include 72 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/blacklist.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2016 The FreeBSD Foundation 3 | * 4 | * This software was developed by Kurt Lidl under sponsorship from the 5 | * FreeBSD Foundation. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. */ 27 | 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include "blacklist_client.h" 37 | 38 | static struct blacklist *blstate; 39 | 40 | void 41 | blacklist_init(void) 42 | { 43 | blstate = blacklist_open(); 44 | } 45 | 46 | void 47 | blacklist_notify(int action, int fd, const char *msg) 48 | { 49 | if (blstate == NULL) 50 | return; 51 | (void)blacklist_r(blstate, action, fd, msg); 52 | } 53 | 54 | void 55 | blacklist_stop(void) 56 | { 57 | if (blstate == NULL) 58 | return; 59 | blacklist_close(blstate); 60 | } 61 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/getdtablecount.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2015 Craig Rodrigues 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 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | __FBSDID("$FreeBSD$"); 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #ifndef KERN_PROC_NFDS 35 | #define KERN_PROC_NFDS 43 /* number of open file descriptors */ 36 | #endif 37 | 38 | int getdtablecount(void); 39 | 40 | /* 41 | * Return the count of open file descriptors for this process. 42 | * 43 | */ 44 | int 45 | getdtablecount(void) 46 | { 47 | int mib[4]; 48 | int error; 49 | int nfds; 50 | size_t len; 51 | 52 | len = sizeof(nfds); 53 | mib[0] = CTL_KERN; 54 | mib[1] = KERN_PROC; 55 | mib[2] = KERN_PROC_NFDS; 56 | mib[3] = 0; 57 | 58 | error = sysctl(mib, 4, &nfds, &len, NULL, 0); 59 | if (error) 60 | return (-1); 61 | return (nfds); 62 | } 63 | -------------------------------------------------------------------------------- /src/usr.bin/htpasswd/htpasswd.1: -------------------------------------------------------------------------------- 1 | .\" $OpenBSD: htpasswd.1,v 1.9 2022/03/31 17:27:25 naddy Exp $ 2 | .\" 3 | .\" Copyright (c) 2014 Florian Obser 4 | .\" 5 | .\" Permission to use, copy, modify, and distribute this software for any 6 | .\" purpose with or without fee is hereby granted, provided that the above 7 | .\" copyright notice and this permission notice appear in all copies. 8 | .\" 9 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | .\" 17 | .Dd $Mdocdate: March 31 2022 $ 18 | .Dt HTPASSWD 1 19 | .Os 20 | .Sh NAME 21 | .Nm htpasswd 22 | .Nd create and update user authentication files 23 | .Sh SYNOPSIS 24 | .Nm 25 | .Op Ar file 26 | .Ar login 27 | .Nm 28 | .Fl I 29 | .Op Ar file 30 | .Sh DESCRIPTION 31 | .Nm 32 | is used to create and update user authentication files for 33 | HTTP daemons. 34 | .Pp 35 | The options are as follows: 36 | .Bl -tag -width Ds 37 | .It Fl I 38 | Switch to batch mode. 39 | .Nm 40 | reads exactly one line from standard input and splits it at the first 41 | .Qo : Qc . 42 | The first part is the login, the second part is the password which 43 | .Nm 44 | then hashes using 45 | .Xr bcrypt 3 . 46 | .El 47 | .Pp 48 | .Nm 49 | prompts for a password and generates a hash using 50 | .Xr bcrypt 3 . 51 | A line suitable for a password file is written to the standard output. 52 | If invoked with two arguments 53 | .Po 54 | or one argument if the 55 | .Fl I 56 | flag is used 57 | .Pc , 58 | user authentication 59 | .Ar file 60 | is updated. 61 | .Sh SEE ALSO 62 | .Xr bcrypt 3 63 | .Sh HISTORY 64 | This reimplemented version of 65 | .Nm 66 | has been available since 67 | .Ox 5.6 . 68 | .Sh AUTHORS 69 | .An Florian Obser Aq Mt florian@openbsd.org 70 | implemented 71 | .Nm 72 | from scratch after Apache httpd was removed from 73 | .Ox 74 | base. 75 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/blacklist_client.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2016 The FreeBSD Foundation 3 | * 4 | * This software was developed by Kurt Lidl under sponsorship from the 5 | * FreeBSD Foundation. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. */ 27 | 28 | 29 | #ifndef BLACKLIST_CLIENT_H 30 | #define BLACKLIST_CLIENT_H 31 | 32 | #ifndef BLACKLIST_API_ENUM 33 | enum { 34 | BLACKLIST_AUTH_OK = 0, 35 | BLACKLIST_AUTH_FAIL, 36 | BLACKLIST_ABUSIVE_BEHAVIOR 37 | }; 38 | #endif 39 | 40 | #ifdef USE_BLACKLIST 41 | void blacklist_init(void); 42 | void blacklist_notify(int, int, const char *); 43 | void blacklist_stop(void); 44 | 45 | #define BLACKLIST_INIT() blacklist_init() 46 | #define BLACKLIST_NOTIFY(x, y, z) blacklist_notify(x, y, z) 47 | #define BLACKLIST_STOP() blacklist_stop() 48 | 49 | #else 50 | 51 | #define BLACKLIST_INIT() 52 | #define BLACKLIST_NOTIFY(x, y, z) 53 | #define BLACKLIST_STOP() 54 | 55 | #endif 56 | 57 | #endif /* BLACKLIST_CLIENT_H */ 58 | -------------------------------------------------------------------------------- /Makefile.cipier: -------------------------------------------------------------------------------- 1 | PORTNAME= obhttpd 2 | PORTVERSION= 7.3.20231129 3 | CATEGORIES= www 4 | MASTER_SITES= OPENBSD/LibreSSL:libressl 5 | DISTFILES= libressl-${SSL_VERSION}.tar.gz:libressl 6 | 7 | MAINTAINER= koue@chaosophia.net 8 | COMMENT= OpenBSD http server 9 | 10 | LICENSE= BSD3CLAUSE 11 | 12 | SSL_VERSION= 3.8.2 13 | LIBEVENT_VERSION= 7.3.20230714 14 | LIBIMSG_VERSION=7.3.20230714 15 | USE_GITHUB= yes 16 | GH_ACCOUNT= koue 17 | GH_PROJECT= httpd \ 18 | libevent:libevent \ 19 | libimsg:libimsg 20 | GH_TAGNAME= ${LIBIMSG_VERSION}:libimsg \ 21 | ${LIBEVENT_VERSION}:libevent 22 | 23 | USE_RC_SUBR= obhttpd 24 | USES= uidfix 25 | 26 | # XXX Static libraries with PIE are currently unsupported. 27 | MAKE_ARGS+= WITHOUT_PIE=true \ 28 | MANDIR=${PREFIX}/man/man \ 29 | SSLDIR=${WRKDIR}/libressl-${SSL_VERSION} \ 30 | EVENTDIR=${WRKSRC_libevent} \ 31 | IMSGDIR=${WRKSRC_libimsg} 32 | 33 | CFLAGS+= -Wall -fcommon 34 | 35 | USERS= www 36 | GROUPS= www 37 | 38 | post-patch: 39 | ${REINPLACE_CMD} -e 's|libimsg/src||g' \ 40 | -e 's|libevent||g' ${WRKSRC}/Makefile 41 | ${REINPLACE_CMD} -e 's|httpd$$|obhttpd|g' \ 42 | -e 's|httpd.conf.5|obhttpd.conf.5|g' \ 43 | -e 's|httpd.8|obhttpd.8|g' ${WRKSRC}/src/usr.sbin/httpd/Makefile 44 | ${REINPLACE_CMD} -e 's|htpasswd$$|obhtpasswd|g' \ 45 | -e 's|htpasswd.1|obhtpasswd.1|g' ${WRKSRC}/src/usr.bin/htpasswd/Makefile 46 | ${MV} ${WRKSRC}/src/usr.bin/htpasswd/htpasswd.1 ${WRKSRC}/src/usr.bin/htpasswd/obhtpasswd.1 47 | ${MV} ${WRKSRC}/src/usr.sbin/httpd/httpd.conf.5 ${WRKSRC}/src/usr.sbin/httpd/obhttpd.conf.5 48 | ${MV} ${WRKSRC}/src/usr.sbin/httpd/httpd.8 ${WRKSRC}/src/usr.sbin/httpd/obhttpd.8 49 | ${REINPLACE_CMD} -e 's|/etc/httpd.conf|${PREFIX}/etc/obhttpd.conf|g' \ 50 | ${WRKSRC}/src/usr.sbin/httpd/httpd.h 51 | 52 | pre-configure: 53 | @(cd ${WRKDIR} && ${EXTRACT_CMD} ${EXTRACT_BEFORE_ARGS} ${DISTDIR}/libressl-${SSL_VERSION}.tar.gz ${EXTRACT_AFTER_ARGS}) 54 | @(cd ${WRKDIR}/libressl-${SSL_VERSION} && ${SETENV} ./configure && ${SETENV} ${MAKE_ENV} ${MAKE}) 55 | @(cd ${WRKSRC_libimsg} && ${SETENV} ${MAKE_ENV} ${MAKE}) 56 | @(cd ${WRKSRC_libevent} && ${SETENV} ${MAKE_ENV} ${MAKE}) 57 | 58 | post-install: 59 | ${INSTALL_DATA} ${WRKSRC}/src/etc/examples/httpd.conf \ 60 | ${STAGEDIR}${PREFIX}/etc/obhttpd.conf.sample 61 | 62 | .include 63 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/compat.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-2017 Nikola Kolev 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 | * 9 | * - Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * - Redistributions in binary form must reproduce the above 12 | * copyright notice, this list of conditions and the following 13 | * disclaimer in the documentation and/or other materials provided 14 | * with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 19 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 20 | * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 26 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | * 29 | */ 30 | 31 | #ifndef COMPAT_H 32 | #define COMPAT_H 33 | 34 | #include 35 | 36 | void 37 | *reallocarray(void *, size_t, size_t); 38 | void 39 | *recallocarray(void *, size_t, size_t, size_t); 40 | void 41 | explicit_bzero(void *buf, size_t len); 42 | int 43 | bcrypt_checkpass(const char *pass, const char *goodhash); 44 | int 45 | bcrypt_newhash(const char *pass, int log_rounds, char *hash, size_t hashlen); 46 | int 47 | _bcrypt_autorounds(void); 48 | int 49 | crypt_checkpass(const char *pass, const char *goodhash); 50 | int 51 | crypt_newhash(const char *pass, const char *pref, char *hash, size_t hashlen); 52 | char * 53 | bcrypt(const char *pass, const char *salt); 54 | int 55 | stravis(char **outp, const char *src, int flag); 56 | int 57 | timingsafe_bcmp(const void *b1, const void *b2, size_t n); 58 | int 59 | timingsafe_memcmp(const void *b1, const void *b2, size_t len); 60 | char * 61 | bcrypt_gensalt(u_int8_t log_rounds); 62 | void 63 | freezero(void *ptr, size_t sz); 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/httpd.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # $OpenBSD: httpd.pl,v 1.2 2016/05/03 19:13:04 bluhm Exp $ 3 | 4 | # Copyright (c) 2010-2015 Alexander Bluhm 5 | # Copyright (c) 2015 Reyk Floeter 6 | # 7 | # Permission to use, copy, modify, and distribute this software for any 8 | # purpose with or without fee is hereby granted, provided that the above 9 | # copyright notice and this permission notice appear in all copies. 10 | # 11 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | 19 | use strict; 20 | use warnings; 21 | use Socket; 22 | use Socket6; 23 | 24 | use Cwd; 25 | use lib cwd; 26 | 27 | use Client; 28 | use Httpd; 29 | require 'funcs.pl'; 30 | 31 | sub usage { 32 | die "usage: httpd.pl chroot [test-args.pl]\n"; 33 | } 34 | 35 | my $testfile; 36 | our %args; 37 | if (@ARGV and -f $ARGV[-1]) { 38 | $testfile = pop; 39 | do $testfile 40 | or die "Do test file $testfile failed: ", $@ || $!; 41 | } 42 | @ARGV == 1 or usage(); 43 | 44 | my $redo = $args{lengths} && @{$args{lengths}}; 45 | $redo = 0 if $args{client}{http_vers}; # run only one persistent connection 46 | my($sport, $rport) = find_ports(num => 2); 47 | my($d, $c); 48 | $d = Httpd->new( 49 | chroot => $ARGV[0], 50 | listendomain => AF_INET, 51 | listenaddr => "127.0.0.1", 52 | listenport => $rport, 53 | connectdomain => AF_INET, 54 | connectaddr => "127.0.0.1", 55 | connectport => $sport, 56 | %{$args{httpd}}, 57 | testfile => $testfile, 58 | ); 59 | $c = Client->new( 60 | chroot => $ARGV[0], 61 | func => \&http_client, 62 | connectdomain => AF_INET, 63 | connectaddr => "127.0.0.1", 64 | connectport => $rport, 65 | %{$args{client}}, 66 | testfile => $testfile, 67 | ) unless $args{client}{noclient}; 68 | 69 | $d->run; 70 | $d->up; 71 | $c->run->up unless $args{client}{noclient}; 72 | 73 | $c->down unless $args{client}{noclient}; 74 | $d->kill_child; 75 | $d->down; 76 | 77 | check_logs($c, $d, %args); 78 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/patterns/test-patterns.out: -------------------------------------------------------------------------------- 1 | # $OpenBSD: test-patterns.out,v 1.2 2015/06/26 10:09:42 semarie Exp $ 2 | string='/page/51' 3 | pattern='^/(%a+)/(%d+)$' 4 | ret=0 num=3 5 | 0: /page/51 6 | 1: page 7 | 2: 51 8 | string='/Apage/51' 9 | pattern='/[^%d][%w%u][^%c]+()[%d]+' 10 | ret=0 num=2 11 | 0: /Apage/51 12 | 1: 13 | string='/^page/51' 14 | pattern='/^(.a.e)/(.)' 15 | ret=0 num=3 16 | 0: /^page/51 17 | 1: page 18 | 2: 5 19 | string='/page/page-51' 20 | pattern='/(.*)/%1-(%d+)' 21 | ret=0 num=3 22 | 0: /page/page-51 23 | 1: page 24 | 2: 51 25 | string='/page/[51]' 26 | pattern='/page/(%b[])' 27 | ret=0 num=2 28 | 0: /page/[51] 29 | 1: [51] 30 | string=':-]' 31 | pattern=']+' 32 | ret=0 num=2 33 | 0: :-] 34 | 1: ] 35 | string=':-)' 36 | pattern='[)]+' 37 | ret=0 num=2 38 | 0: :-) 39 | 1: ) 40 | string='/page/51' 41 | pattern='$^' 42 | ret=-1 num=0 43 | string='1234567890' 44 | pattern='([2-5]-)' 45 | ret=0 num=2 46 | 0: 1234567890 47 | 1: 48 | string='****' 49 | pattern='^**$' 50 | ret=0 num=2 51 | 0: **** 52 | 1: **** 53 | string='xxxx' 54 | pattern='^x*$' 55 | ret=0 num=2 56 | 0: xxxx 57 | 1: xxxx 58 | string='/page/51' 59 | pattern='no-%d-match' 60 | ret=-1 num=0 61 | patterns-tester: str_match: invalid capture index 62 | string='/page/page-51' 63 | pattern='/(.*)/%9-(%d+)' 64 | patterns-tester: str_match: invalid pattern capture 65 | string=':-)' 66 | pattern=')+' 67 | patterns-tester: str_match: unfinished capture 68 | string='/page/51' 69 | pattern='/page/51(' 70 | patterns-tester: str_match: malformed pattern (ends with '%') 71 | string='/page/51' 72 | pattern='/page/51%' 73 | patterns-tester: str_match: malformed pattern (missing ']') 74 | string='/page/51' 75 | pattern='/page/[51' 76 | patterns-tester: str_match: malformed pattern (missing arguments to '%b') 77 | string='/page/(51)' 78 | pattern='/page/%b(' 79 | patterns-tester: str_match: too many captures 80 | string='/page/51' 81 | pattern='()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()' 82 | patterns-tester: str_match: missing '[' after '%f' in pattern 83 | string='/page/51' 84 | pattern='/page/%f' 85 | patterns-tester: str_match: missing '[' after '%f' in pattern 86 | string='/page/51' 87 | pattern='/page%f/51' 88 | patterns-tester: str_match: max repetition items 89 | string='q*********************************' 90 | pattern='*************************************q' 91 | patterns-tester: str_match: max repetition items 92 | string='q+++++++++++++++++++++++++++++++++' 93 | pattern='+++++++++++++++++++++++++++++++++++++q' 94 | patterns-tester: str_match: max repetition items 95 | string='q---------------------------------' 96 | pattern='-------------------------------------q' 97 | patterns-tester: str_match: max repetition items 98 | string='q?????????????????????????????????' 99 | pattern='?????????????????????????????????????q' 100 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/patterns/patterns-tester.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: patterns-tester.c,v 1.1 2015/06/23 18:03:09 semarie Exp $ */ 2 | /* 3 | * Copyright (c) 2015 Sebastien Marie 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "patterns.h" 24 | 25 | #ifdef __OpenBSD__ 26 | extern char * malloc_options; 27 | #endif 28 | 29 | static void read_string(char *, size_t); 30 | static void read_string_stop(void); 31 | 32 | static void 33 | read_string(char *buf, size_t len) 34 | { 35 | size_t i; 36 | 37 | /* init */ 38 | bzero(buf, len); 39 | 40 | /* read */ 41 | if (fgets(buf, len, stdin) == NULL) 42 | err(1, "fgets"); 43 | 44 | /* strip '\n' */ 45 | i = strnlen(buf, len); 46 | if (i != 0) 47 | buf[i-1] = '\0'; 48 | } 49 | 50 | static void 51 | read_string_stop() 52 | { 53 | if (getchar() != EOF) 54 | errx(1, "read_string_stop: too many input"); 55 | } 56 | 57 | int 58 | main(int argc, char *argv[]) 59 | { 60 | char string[1024]; 61 | char pattern[1024]; 62 | struct str_match m; 63 | const char *errstr = NULL; 64 | int ret; 65 | size_t i; 66 | 67 | /* configure malloc */ 68 | #ifdef __OpenBSD__ 69 | malloc_options = "S"; 70 | #endif 71 | 72 | /* read testcase */ 73 | if (argc != 3) { 74 | /* from stdin (useful for afl) */ 75 | read_string(string, sizeof(string)); 76 | read_string(pattern, sizeof(pattern)); 77 | read_string_stop(); 78 | } else { 79 | /* from arguments */ 80 | strlcpy(string, argv[1], sizeof(string)); 81 | strlcpy(pattern, argv[2], sizeof(pattern)); 82 | } 83 | 84 | /* print testcase */ 85 | printf("string='%s'\n", string); 86 | printf("pattern='%s'\n", pattern); 87 | 88 | /* test it ! */ 89 | ret = str_match(string, pattern, &m, &errstr); 90 | if (errstr != NULL) 91 | errx(1, "str_match: %s", errstr); 92 | 93 | /* print result */ 94 | printf("ret=%d num=%d\n", ret, m.sm_nmatch); 95 | for (i=0; i 4 | # Copyright (c) 2015 Reyk Floeter 5 | # 6 | # Permission to use, copy, modify, and distribute this software for any 7 | # purpose with or without fee is hereby granted, provided that the above 8 | # copyright notice and this permission notice appear in all copies. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | 18 | use strict; 19 | use warnings; 20 | 21 | package Client; 22 | use parent 'Proc'; 23 | use Carp; 24 | use Socket; 25 | use Socket6; 26 | use IO::Socket; 27 | use IO::Socket::SSL; 28 | 29 | sub new { 30 | my $class = shift; 31 | my %args = @_; 32 | $args{chroot} ||= "."; 33 | $args{logfile} ||= $args{chroot}."/client.log"; 34 | $args{up} ||= "Connected"; 35 | $args{timefile} //= "time.log"; 36 | my $self = Proc::new($class, %args); 37 | $self->{connectdomain} 38 | or croak "$class connect domain not given"; 39 | $self->{connectaddr} 40 | or croak "$class connect addr not given"; 41 | $self->{connectport} 42 | or croak "$class connect port not given"; 43 | return $self; 44 | } 45 | 46 | sub child { 47 | my $self = shift; 48 | 49 | # in case we redo the connect, shutdown the old one 50 | shutdown(\*STDOUT, SHUT_WR); 51 | delete $self->{cs}; 52 | 53 | $SSL_ERROR = ""; 54 | my $iosocket = $self->{tls} ? "IO::Socket::SSL" : "IO::Socket::IP"; 55 | my $cs = $iosocket->new( 56 | Proto => "tcp", 57 | Domain => $self->{connectdomain}, 58 | PeerAddr => $self->{connectaddr}, 59 | PeerPort => $self->{connectport}, 60 | SSL_verify_mode => SSL_VERIFY_NONE, 61 | SSL_use_cert => $self->{offertlscert} ? 1 : 0, 62 | SSL_cert_file => $self->{offertlscert} ? 63 | $self->{chroot}."/client.crt" : "", 64 | SSL_key_file => $self->{offertlscert} ? 65 | $self->{chroot}."/client.key" : "", 66 | ) or die ref($self), " $iosocket socket connect failed: $!,$SSL_ERROR"; 67 | print STDERR "connect sock: ",$cs->sockhost()," ",$cs->sockport(),"\n"; 68 | print STDERR "connect peer: ",$cs->peerhost()," ",$cs->peerport(),"\n"; 69 | if ($self->{tls}) { 70 | print STDERR "tls version: ",$cs->get_sslversion(),"\n"; 71 | print STDERR "tls cipher: ",$cs->get_cipher(),"\n"; 72 | print STDERR "tls peer certificate:\n", 73 | $cs->dump_peer_certificate(); 74 | } 75 | 76 | *STDIN = *STDOUT = $self->{cs} = $cs; 77 | } 78 | 79 | 1; 80 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/httpd.8: -------------------------------------------------------------------------------- 1 | .\" $OpenBSD: httpd.8,v 1.54 2022/10/24 15:02:01 jmc Exp $ 2 | .\" 3 | .\" Copyright (c) 2014 Reyk Floeter 4 | .\" 5 | .\" Permission to use, copy, modify, and distribute this software for any 6 | .\" purpose with or without fee is hereby granted, provided that the above 7 | .\" copyright notice and this permission notice appear in all copies. 8 | .\" 9 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | .\" 17 | .Dd $Mdocdate: October 24 2022 $ 18 | .Dt HTTPD 8 19 | .Os 20 | .Sh NAME 21 | .Nm httpd 22 | .Nd HTTP daemon 23 | .Sh SYNOPSIS 24 | .Nm 25 | .Op Fl dnv 26 | .Op Fl D Ar macro Ns = Ns Ar value 27 | .Op Fl f Ar file 28 | .Sh DESCRIPTION 29 | The 30 | .Nm 31 | daemon is an HTTP server with FastCGI and TLS support. 32 | .Pp 33 | The FastCGI implementation has optional socket support. 34 | .Nm 35 | can log to 36 | .Xr syslog 3 37 | or per-server files with several standard formats. 38 | .Pp 39 | .Nm 40 | rereads its configuration file when it receives 41 | .Dv SIGHUP 42 | and reopens log files when it receives 43 | .Dv SIGUSR1 . 44 | .Pp 45 | The options are as follows: 46 | .Bl -tag -width Dssmacro=value 47 | .It Fl D Ar macro Ns = Ns Ar value 48 | Set a 49 | .Ar macro 50 | to a 51 | .Ar value . 52 | Macros can be referenced in the configuration files. 53 | .It Fl d 54 | Debug mode. 55 | Create one server and don't detach or become a daemon. 56 | This allows for easy monitoring of 57 | .Nm . 58 | .It Fl f Ar file 59 | Specifies the configuration file. 60 | The default is 61 | .Pa /etc/httpd.conf . 62 | .It Fl n 63 | Check that the configuration is valid, but don't start any servers. 64 | .It Fl v 65 | Verbose mode. 66 | Multiple 67 | .Fl v 68 | options increase the verbosity. 69 | .El 70 | .Sh FILES 71 | .Bl -tag -width "/etc/ssl/private/server.key" -compact 72 | .It Pa /etc/httpd.conf 73 | Default configuration file. 74 | .It Pa /etc/ssl/private/server.key 75 | Default SSL/TLS server key. 76 | .It Pa /etc/ssl/server.crt 77 | Default SSL/TLS server certificate. 78 | .It Pa /var/www/logs/access.log 79 | Default access log file. 80 | .It Pa /var/www/logs/error.log 81 | Default error log file. 82 | .El 83 | .Sh SEE ALSO 84 | .Xr acme-client 1 , 85 | .Xr httpd.conf 5 , 86 | .Xr slowcgi 8 87 | .Sh HISTORY 88 | The 89 | .Nm 90 | program first appeared in 91 | .Ox 5.6 . 92 | .Nm 93 | is based on 94 | .Xr relayd 8 . 95 | .Sh AUTHORS 96 | .An -nosplit 97 | The 98 | .Nm 99 | program was written by 100 | .An Reyk Floeter Aq Mt reyk@openbsd.org . 101 | -------------------------------------------------------------------------------- /src/lib/libc/crypt/cryptutil.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: cryptutil.c,v 1.12 2015/09/13 15:33:48 guenther Exp $ */ 2 | /* 3 | * Copyright (c) 2014 Ted Unangst 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #ifndef __OpenBSD__ 25 | #include "compat.h" 26 | #endif 27 | 28 | int 29 | crypt_checkpass(const char *pass, const char *goodhash) 30 | { 31 | char dummy[_PASSWORD_LEN]; 32 | 33 | if (goodhash == NULL) { 34 | /* fake it */ 35 | goto fake; 36 | } 37 | 38 | /* empty password */ 39 | if (strlen(goodhash) == 0 && strlen(pass) == 0) 40 | return 0; 41 | 42 | if (goodhash[0] == '$' && goodhash[1] == '2') { 43 | if (bcrypt_checkpass(pass, goodhash)) 44 | goto fail; 45 | return 0; 46 | } 47 | 48 | /* unsupported. fake it. */ 49 | fake: 50 | bcrypt_newhash(pass, 8, dummy, sizeof(dummy)); 51 | fail: 52 | errno = EACCES; 53 | return -1; 54 | } 55 | #ifdef __OpenBSD__ 56 | DEF_WEAK(crypt_checkpass); 57 | #endif 58 | 59 | int 60 | crypt_newhash(const char *pass, const char *pref, char *hash, size_t hashlen) 61 | { 62 | int rv = -1; 63 | const char *defaultpref = "blowfish,8"; 64 | const char *errstr; 65 | const char *choices[] = { "blowfish", "bcrypt" }; 66 | size_t maxchoice = sizeof(choices) / sizeof(choices[0]); 67 | int i; 68 | int rounds; 69 | 70 | if (pref == NULL) 71 | pref = defaultpref; 72 | 73 | for (i = 0; i < maxchoice; i++) { 74 | const char *choice = choices[i]; 75 | size_t len = strlen(choice); 76 | if (strcmp(pref, choice) == 0) { 77 | rounds = _bcrypt_autorounds(); 78 | break; 79 | } else if (strncmp(pref, choice, len) == 0 && 80 | pref[len] == ',') { 81 | if (strcmp(pref + len + 1, "a") == 0) { 82 | rounds = _bcrypt_autorounds(); 83 | } else { 84 | rounds = strtonum(pref + len + 1, 4, 31, &errstr); 85 | if (errstr) { 86 | errno = EINVAL; 87 | goto err; 88 | } 89 | } 90 | break; 91 | } 92 | } 93 | if (i == maxchoice) { 94 | errno = EINVAL; 95 | goto err; 96 | } 97 | 98 | rv = bcrypt_newhash(pass, rounds, hash, hashlen); 99 | 100 | err: 101 | return rv; 102 | } 103 | #ifdef __OpenBSD__ 104 | DEF_WEAK(crypt_newhash); 105 | #endif 106 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/Makefile: -------------------------------------------------------------------------------- 1 | # $OpenBSD: Makefile,v 1.16 2021/12/22 15:54:01 bluhm Exp $ 2 | 3 | # The following ports must be installed for the regression tests: 4 | # p5-Socket6 Perl defines relating to AF_INET6 sockets 5 | # p5-IO-Socket-SSL perl interface to SSL sockets 6 | # 7 | # Check wether all required perl packages are installed. If some 8 | # are missing print a warning and skip the tests, but do not fail. 9 | 10 | #PERL_REQUIRE != perl -Mstrict -Mwarnings -e ' \ 11 | eval { require Socket6 } or print $@; \ 12 | eval { require IO::Socket::SSL } or print $@; \ 13 | ' 14 | #.if ! empty (PERL_REQUIRE) 15 | #regress: 16 | # @echo "${PERL_REQUIRE}" 17 | # @echo 'run "pkg_add p5-Socket6 p5-IO-Socket-SSL"' 18 | # @echo SKIPPED 19 | #.endif 20 | 21 | # Automatically generate regress targets from test cases in directory. 22 | 23 | ARGS != cd ${.CURDIR} && ls args-*.pl 24 | CLEANFILES += *.log httpd.conf ktrace.out stamp-* 25 | CLEANFILES += *.pem *.req *.crt *.key *.srl md5-* 26 | 27 | HTDOCS_FILES = 512 1048576 1073741824 28 | HTDOCS_MD5 = ${HTDOCS_FILES:S,^,md5-,} 29 | HTDOCS_SPARSE = yes 30 | CLEANFILES += htdocs/* 31 | 32 | # Set variables so that make runs with and without obj directory. 33 | # Only do that if necessary to keep visible output short. 34 | 35 | .if ${.CURDIR} == ${.OBJDIR} 36 | PERLINC = -I. 37 | PERLPATH = 38 | .else 39 | PERLINC = -I${.CURDIR} 40 | PERLPATH = ${.CURDIR}/ 41 | .endif 42 | 43 | # The arg tests take a perl hash with arguments controlling the 44 | # test parameters. Generally they consist of client, httpd, server. 45 | 46 | .for a in ${ARGS} 47 | REGRESS_TARGETS += run-$a 48 | REGRESS_ROOT_TARGETS += run-$a 49 | run-$a: $a ${HTDOCS_MD5} 50 | # time SUDO="${SUDO}" KTRACE=${KTRACE} HTTPD=${HTTPD} perl ${PERLINC} ${PERLPATH}httpd.pl ${.OBJDIR} ${PERLPATH}$a 51 | time perl ${PERLINC} ${PERLPATH}httpd.pl ${.OBJDIR} ${PERLPATH}$a 52 | .endfor 53 | 54 | # populate htdocs 55 | 56 | .for d in ${HTDOCS_FILES} 57 | htdocs/$d: 58 | mkdir -m 0755 -p ${@:H} 59 | .if (${HTDOCS_SPARSE} != "yes") 60 | dd if=/dev/urandom of=$@ count=$$(($d / 512)) bs=512 61 | .else 62 | dd of=$@ seek=$$(($d / 512)) bs=512 count=0 status=none 63 | .endif 64 | 65 | md5-$d: htdocs/$d 66 | md5 -q htdocs/$d >$@ 67 | .endfor 68 | 69 | # create certificates for TLS 70 | 71 | ca.crt: 72 | openssl req -batch -new -subj /L=OpenBSD/O=httpd-regress/OU=ca/CN=root/ -nodes -newkey rsa -keyout ca.key -x509 -out ca.crt 73 | 74 | server.req: 75 | openssl req -batch -new -subj /L=OpenBSD/O=httpd-regress/OU=server/CN=localhost/ -nodes -newkey rsa -keyout server.key -out server.req 76 | 77 | client.req: 78 | openssl req -batch -new -subj /L=OpenBSD/O=httpd-regress/OU=client/CN=localhost/ -nodes -newkey rsa -keyout client.key -out $@ 79 | 80 | server.crt: ca.crt server.req 81 | openssl x509 -CAcreateserial -CAkey ca.key -CA ca.crt -req -in server.req -out server.crt 82 | 83 | client.crt: ca.crt client.req 84 | openssl x509 -CAcreateserial -CAkey ca.key -CA ca.crt -req -in client.req -out $@ 85 | 86 | ${REGRESS_TARGETS:M*tls*} ${REGRESS_TARGETS:M*https*}: server.crt client.crt 87 | 88 | # make perl syntax check for all args files 89 | 90 | .PHONY: syntax 91 | 92 | syntax: stamp-syntax 93 | 94 | stamp-syntax: ${ARGS} 95 | .for a in ${ARGS} 96 | @perl -c ${PERLPATH}$a 97 | .endfor 98 | @date >$@ 99 | 100 | .include 101 | -------------------------------------------------------------------------------- /src/include/blf.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: blf.h,v 1.8 2021/11/29 01:04:45 djm Exp $ */ 2 | /* 3 | * Blowfish - a fast block cipher designed by Bruce Schneier 4 | * 5 | * Copyright 1997 Niels Provos 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * 3. The name of the author may not be used to endorse or promote products 17 | * derived from this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef _BLF_H_ 32 | #define _BLF_H_ 33 | 34 | /* Schneier specifies a maximum key length of 56 bytes. 35 | * This ensures that every key bit affects every cipher 36 | * bit. However, the subkeys can hold up to 72 bytes. 37 | * Warning: For normal blowfish encryption only 56 bytes 38 | * of the key affect all cipherbits. 39 | */ 40 | 41 | #define BLF_N 16 /* Number of Subkeys */ 42 | #define BLF_MAXKEYLEN ((BLF_N-2)*4) /* 448 bits */ 43 | #define BLF_MAXUTILIZED ((BLF_N+2)*4) /* 576 bits */ 44 | 45 | /* Blowfish context */ 46 | typedef struct BlowfishContext { 47 | u_int32_t S[4][256]; /* S-Boxes */ 48 | u_int32_t P[BLF_N + 2]; /* Subkeys */ 49 | } blf_ctx; 50 | 51 | /* Raw access to customized Blowfish 52 | * blf_key is just: 53 | * Blowfish_initstate( state ) 54 | * Blowfish_expand0state( state, key, keylen ) 55 | */ 56 | 57 | void Blowfish_encipher(blf_ctx *, u_int32_t *, u_int32_t *); 58 | void Blowfish_decipher(blf_ctx *, u_int32_t *, u_int32_t *); 59 | void Blowfish_initstate(blf_ctx *); 60 | void Blowfish_expand0state(blf_ctx *, const u_int8_t *, u_int16_t); 61 | void Blowfish_expandstate 62 | (blf_ctx *, const u_int8_t *, u_int16_t, const u_int8_t *, u_int16_t); 63 | 64 | /* Standard Blowfish */ 65 | 66 | void blf_key(blf_ctx *, const u_int8_t *, u_int16_t); 67 | void blf_enc(blf_ctx *, u_int32_t *, u_int16_t); 68 | void blf_dec(blf_ctx *, u_int32_t *, u_int16_t); 69 | 70 | void blf_ecb_encrypt(blf_ctx *, u_int8_t *, u_int32_t); 71 | void blf_ecb_decrypt(blf_ctx *, u_int8_t *, u_int32_t); 72 | 73 | void blf_cbc_encrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t); 74 | void blf_cbc_decrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t); 75 | 76 | /* Converts u_int8_t to u_int32_t */ 77 | u_int32_t Blowfish_stream2word(const u_int8_t *, u_int16_t , u_int16_t *); 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/patterns/test-patterns-lua.out: -------------------------------------------------------------------------------- 1 | string='/page/51' 2 | pattern='^/(%a+)/(%d+)$' 3 | page 51 4 | string='/Apage/51' 5 | pattern='/[^%d][%w%u][^%c]+()[%d]+' 6 | 9 7 | string='/^page/51' 8 | pattern='/^(.a.e)/(.)' 9 | page 5 10 | string='/page/page-51' 11 | pattern='/(.*)/%1-(%d+)' 12 | page 51 13 | string='/page/[51]' 14 | pattern='/page/(%b[])' 15 | [51] 16 | string=':-]' 17 | pattern=']+' 18 | ] 19 | string=':-)' 20 | pattern='[)]+' 21 | ) 22 | string='/page/51' 23 | pattern='$^' 24 | nil 25 | string='1234567890' 26 | pattern='([2-5]-)' 27 | 28 | string='****' 29 | pattern='^**$' 30 | **** 31 | string='xxxx' 32 | pattern='^x*$' 33 | xxxx 34 | string='/page/51' 35 | pattern='no-%d-match' 36 | nil 37 | string='/page/page-51' 38 | pattern='/(.*)/%9-(%d+)' 39 | X_PATTERNS_TESTER_X:3: invalid capture index %9 40 | stack traceback: 41 | [C]: in function 'string.match' 42 | X_PATTERNS_TESTER_X:3: in main chunk 43 | [C]: in ? 44 | string=':-)' 45 | pattern=')+' 46 | X_PATTERNS_TESTER_X:3: invalid pattern capture 47 | stack traceback: 48 | [C]: in function 'string.match' 49 | X_PATTERNS_TESTER_X:3: in main chunk 50 | [C]: in ? 51 | string='/page/51' 52 | pattern='/page/51(' 53 | X_PATTERNS_TESTER_X:3: unfinished capture 54 | stack traceback: 55 | [C]: in function 'string.match' 56 | X_PATTERNS_TESTER_X:3: in main chunk 57 | [C]: in ? 58 | string='/page/51' 59 | pattern='/page/51%' 60 | X_PATTERNS_TESTER_X:3: malformed pattern (ends with '%') 61 | stack traceback: 62 | [C]: in function 'string.match' 63 | X_PATTERNS_TESTER_X:3: in main chunk 64 | [C]: in ? 65 | string='/page/51' 66 | pattern='/page/[51' 67 | X_PATTERNS_TESTER_X:3: malformed pattern (missing ']') 68 | stack traceback: 69 | [C]: in function 'string.match' 70 | X_PATTERNS_TESTER_X:3: in main chunk 71 | [C]: in ? 72 | string='/page/(51)' 73 | pattern='/page/%b(' 74 | X_PATTERNS_TESTER_X:3: malformed pattern (missing arguments to '%b') 75 | stack traceback: 76 | [C]: in function 'string.match' 77 | X_PATTERNS_TESTER_X:3: in main chunk 78 | [C]: in ? 79 | string='/page/51' 80 | pattern='()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()' 81 | X_PATTERNS_TESTER_X:3: too many captures 82 | stack traceback: 83 | [C]: in function 'string.match' 84 | X_PATTERNS_TESTER_X:3: in main chunk 85 | [C]: in ? 86 | string='/page/51' 87 | pattern='/page/%f' 88 | X_PATTERNS_TESTER_X:3: missing '[' after '%f' in pattern 89 | stack traceback: 90 | [C]: in function 'string.match' 91 | X_PATTERNS_TESTER_X:3: in main chunk 92 | [C]: in ? 93 | string='/page/51' 94 | pattern='/page%f/51' 95 | X_PATTERNS_TESTER_X:3: missing '[' after '%f' in pattern 96 | stack traceback: 97 | [C]: in function 'string.match' 98 | X_PATTERNS_TESTER_X:3: in main chunk 99 | [C]: in ? 100 | string='q*********************************' 101 | pattern='*************************************q' 102 | X_PATTERNS_TESTER_X:3: max repetition items 103 | stack traceback: 104 | [C]: in function 'string.match' 105 | X_PATTERNS_TESTER_X:3: in main chunk 106 | [C]: in ? 107 | string='q+++++++++++++++++++++++++++++++++' 108 | pattern='+++++++++++++++++++++++++++++++++++++q' 109 | X_PATTERNS_TESTER_X:3: max repetition items 110 | stack traceback: 111 | [C]: in function 'string.match' 112 | X_PATTERNS_TESTER_X:3: in main chunk 113 | [C]: in ? 114 | string='q---------------------------------' 115 | pattern='-------------------------------------q' 116 | X_PATTERNS_TESTER_X:3: max repetition items 117 | stack traceback: 118 | [C]: in function 'string.match' 119 | X_PATTERNS_TESTER_X:3: in main chunk 120 | [C]: in ? 121 | string='q?????????????????????????????????' 122 | pattern='?????????????????????????????????????q' 123 | X_PATTERNS_TESTER_X:3: max repetition items 124 | stack traceback: 125 | [C]: in function 'string.match' 126 | X_PATTERNS_TESTER_X:3: in main chunk 127 | [C]: in ? 128 | -------------------------------------------------------------------------------- /src/share/mk/bsd.regress.mk: -------------------------------------------------------------------------------- 1 | # $OpenBSD: bsd.regress.mk,v 1.27 2023/09/24 08:28:20 tb Exp $ 2 | # Documented in bsd.regress.mk(5) 3 | 4 | # No man pages for regression tests. 5 | NOMAN= 6 | 7 | # No installation. 8 | install: 9 | 10 | # If REGRESS_TARGETS is defined and PROG is not defined, set NOPROG 11 | .if defined(REGRESS_TARGETS) && !defined(PROG) && !defined(PROGS) 12 | NOPROG= 13 | .endif 14 | 15 | .include 16 | 17 | .MAIN: all 18 | all: regress 19 | 20 | # XXX - Need full path to REGRESS_LOG, otherwise there will be much pain. 21 | REGRESS_LOG?=/dev/null 22 | REGRESS_SKIP_TARGETS?= 23 | REGRESS_SKIP_SLOW?=no 24 | .if ${REGRESS_LOG} != "/dev/null" 25 | REGRESS_FAIL_EARLY?=no 26 | .endif 27 | REGRESS_FAIL_EARLY?=yes 28 | 29 | .if ! ${REGRESS_LOG:M/*} 30 | ERRORS += "Fatal: REGRESS_LOG=${REGRESS_LOG} is not an absolute path" 31 | .endif 32 | 33 | _REGRESS_NAME=${.CURDIR:S/${BSDSRCDIR}\/regress\///} 34 | _REGRESS_TMP?=/dev/null 35 | _REGRESS_OUT= | tee -a ${REGRESS_LOG} ${_REGRESS_TMP} 2>&1 > /dev/null 36 | 37 | .for p in ${PROG} ${PROGS} 38 | run-regress-$p: $p 39 | . if !commands(run-regress-$p) 40 | ./$p 41 | . endif 42 | .PHONY: run-regress-$p 43 | .endfor 44 | 45 | .if (defined(PROG) || defined(PROGS)) && !defined(REGRESS_TARGETS) 46 | REGRESS_TARGETS= ${PROG:S/^/run-regress-/} ${PROGS:S/^/run-regress-/} 47 | . if defined(REGRESS_SKIP) 48 | REGRESS_SKIP_TARGETS= ${PROG:S/^/run-regress-/} ${PROGS:S/^/run-regress-/} 49 | . endif 50 | .endif 51 | 52 | .if defined(REGRESS_SLOW_TARGETS) && ${REGRESS_SKIP_SLOW:L} != no 53 | REGRESS_SKIP_TARGETS+=${REGRESS_SLOW_TARGETS} 54 | .endif 55 | 56 | .if ${REGRESS_FAIL_EARLY:L} != no 57 | _REGRESS_FAILED = false 58 | .else 59 | _REGRESS_FAILED = true 60 | .endif 61 | 62 | .if defined(REGRESS_ROOT_TARGETS) 63 | _ROOTUSER!=id -g 64 | SUDO?= 65 | . if (${_ROOTUSER} != 0) && empty(SUDO) 66 | REGRESS_SKIP_TARGETS+=${REGRESS_ROOT_TARGETS} 67 | . endif 68 | .endif 69 | 70 | REGRESS_EXPECTED_FAILURES?= 71 | REGRESS_SETUP?= 72 | REGRESS_SETUP_ONCE?= 73 | REGRESS_CLEANUP?= 74 | 75 | .if !empty(REGRESS_SETUP) 76 | ${REGRESS_TARGETS}: ${REGRESS_SETUP} 77 | .endif 78 | 79 | .if !empty(REGRESS_SETUP_ONCE) 80 | CLEANFILES+=${REGRESS_SETUP_ONCE:S/^/stamp-/} 81 | ${REGRESS_TARGETS}: ${REGRESS_SETUP_ONCE:S/^/stamp-/} 82 | ${REGRESS_SETUP_ONCE:S/^/stamp-/}: .SILENT 83 | echo '==== ${@:S/^stamp-//} ====' 84 | ${MAKE} -C ${.CURDIR} ${@:S/^stamp-//} 85 | date >$@ 86 | echo 87 | .endif 88 | 89 | regress: .SILENT 90 | .if !empty(REGRESS_SETUP_ONCE) 91 | rm -f ${REGRESS_SETUP_ONCE:S/^/stamp-/} 92 | ${MAKE} -C ${.CURDIR} ${REGRESS_SETUP_ONCE:S/^/stamp-/} 93 | .endif 94 | .for RT in ${REGRESS_TARGETS} 95 | echo '==== ${RT} ====' 96 | . if ${REGRESS_SKIP_TARGETS:M${RT}} 97 | echo -n "SKIP " ${_REGRESS_OUT} 98 | echo SKIPPED 99 | . elif ${REGRESS_EXPECTED_FAILURES:M${RT}} 100 | if ${MAKE} -C ${.CURDIR} ${RT}; then \ 101 | echo -n "XPASS " ${_REGRESS_OUT} ; \ 102 | echo UNEXPECTED_PASS; \ 103 | ${_REGRESS_FAILED}; \ 104 | else \ 105 | echo -n "XFAIL " ${_REGRESS_OUT} ; \ 106 | echo EXPECTED_FAIL; \ 107 | fi 108 | . else 109 | if ${MAKE} -C ${.CURDIR} ${RT}; then \ 110 | echo -n "SUCCESS " ${_REGRESS_OUT} ; \ 111 | else \ 112 | echo -n "FAIL " ${_REGRESS_OUT} ; \ 113 | echo FAILED ; \ 114 | ${_REGRESS_FAILED}; \ 115 | fi 116 | . endif 117 | echo ${_REGRESS_NAME}/${RT:S/^run-regress-//} ${_REGRESS_OUT} 118 | echo 119 | .endfor 120 | .for RT in ${REGRESS_CLEANUP} 121 | echo '==== ${RT} ====' 122 | ${MAKE} -C ${.CURDIR} ${RT} 123 | echo 124 | .endfor 125 | rm -f ${REGRESS_SETUP_ONCE:S/^/stamp-/} 126 | 127 | .if defined(ERRORS) 128 | .BEGIN: 129 | . for _m in ${ERRORS} 130 | @echo 1>&2 ${_m} 131 | . endfor 132 | . if !empty(ERRORS:M"Fatal\:*") || !empty(ERRORS:M'Fatal\:*') 133 | @exit 1 134 | . endif 135 | .endif 136 | 137 | .PHONY: regress 138 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/Httpd.pm: -------------------------------------------------------------------------------- 1 | # $OpenBSD: Httpd.pm,v 1.4 2021/10/05 17:40:08 anton Exp $ 2 | 3 | # Copyright (c) 2010-2015 Alexander Bluhm 4 | # Copyright (c) 2015 Reyk Floeter 5 | # 6 | # Permission to use, copy, modify, and distribute this software for any 7 | # purpose with or without fee is hereby granted, provided that the above 8 | # copyright notice and this permission notice appear in all copies. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | 18 | use strict; 19 | use warnings; 20 | 21 | package Httpd; 22 | use parent 'Proc'; 23 | use Carp; 24 | use File::Basename; 25 | 26 | sub new { 27 | my $class = shift; 28 | my %args = @_; 29 | $args{chroot} ||= "."; 30 | $args{docroot} ||= "htdocs"; 31 | $args{logfile} ||= $args{chroot}."/httpd.log"; 32 | $args{up} ||= $args{dryrun} || "server_launch: "; 33 | $args{down} ||= $args{dryrun} ? "httpd.conf:" : "parent terminating"; 34 | $args{func} = sub { Carp::confess "$class func may not be called" }; 35 | $args{conffile} ||= "httpd.conf"; 36 | my $self = Proc::new($class, %args); 37 | ref($self->{http}) eq 'ARRAY' 38 | or $self->{http} = [ split("\n", $self->{http} || "") ]; 39 | $self->{listenaddr} 40 | or croak "$class listen addr not given"; 41 | $self->{listenport} 42 | or croak "$class listen port not given"; 43 | 44 | my $test = basename($self->{testfile} || ""); 45 | # tls does not allow a too long session id, so truncate it 46 | substr($test, 25, length($test) - 25, "") if length($test) > 25; 47 | open(my $fh, '>', $self->{conffile}) 48 | or die ref($self), " conf file $self->{conffile} create failed: $!"; 49 | 50 | # substitute variables in config file 51 | my $curdir = dirname($0) || "."; 52 | my $connectport = $self->{connectport}; 53 | my $connectaddr = $self->{connectaddr}; 54 | my $listenaddr = $self->{listenaddr}; 55 | my $listenport = $self->{listenport}; 56 | 57 | #print $fh "prefork 1\n"; # only crashes of first child are observed 58 | print $fh "prefork 4\n"; # on FreeBSD socket problems were observed 59 | # if multiple forks 60 | print $fh "chroot \"".$args{docroot}."\"\n"; 61 | print $fh "logdir \"".$args{chroot}."\"\n"; 62 | 63 | my @http = @{$self->{http}}; 64 | print $fh "server \"www.$test.local\" {"; 65 | my $tls = $self->{listentls} ? "tls " : ""; 66 | print $fh "\n\tlisten on $self->{listenaddr} ". 67 | "${tls}port $self->{listenport}" unless grep { /^listen / } @http; 68 | # substitute variables in config file 69 | foreach (@http) { 70 | s/(\$[a-z]+)/$1/eeg; 71 | } 72 | print $fh map { "\n\t$_" } @http; 73 | if ($self->{listentls}) { 74 | print $fh "\n"; 75 | print $fh "\ttls certificate \"".$args{chroot}."/server.crt\"\n"; 76 | print $fh "\ttls key \"".$args{chroot}."/server.key\""; 77 | $self->{verifytls} 78 | and print $fh "\n\ttls client ca \"".$args{chroot}."/ca.crt\""; 79 | } 80 | print $fh "\n\troot \"/\""; 81 | print $fh "\n\tlog style combined"; 82 | print $fh "\n}\n"; 83 | 84 | return $self; 85 | } 86 | 87 | sub child { 88 | my $self = shift; 89 | my @sudo = $ENV{SUDO} ? split(' ', $ENV{SUDO}) : (); 90 | my @ktrace = $ENV{KTRACE} ? ($ENV{KTRACE}, "-i") : (); 91 | my $httpd = $ENV{HTTPD} ? $ENV{HTTPD} : "../../../../usr.sbin/httpd/httpd"; 92 | my @cmd = (@sudo, @ktrace, $httpd, "-dvv", "-f", $self->{conffile}); 93 | print STDERR "execute: @cmd\n"; 94 | exec @cmd; 95 | die ref($self), " exec '@cmd' failed: $!"; 96 | } 97 | 98 | 1; 99 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/log.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: log.c,v 1.14 2017/03/21 12:06:55 bluhm Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2003, 2004 Henning Brauer 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | static int debug; 28 | static int verbose; 29 | const char *log_procname; 30 | 31 | void log_init(int, int); 32 | void log_procinit(const char *); 33 | void log_setverbose(int); 34 | int log_getverbose(void); 35 | void log_warn(const char *, ...) 36 | __attribute__((__format__ (printf, 1, 2))); 37 | void log_warnx(const char *, ...) 38 | __attribute__((__format__ (printf, 1, 2))); 39 | void log_info(const char *, ...) 40 | __attribute__((__format__ (printf, 1, 2))); 41 | void log_debug(const char *, ...) 42 | __attribute__((__format__ (printf, 1, 2))); 43 | void logit(int, const char *, ...) 44 | __attribute__((__format__ (printf, 2, 3))); 45 | void vlog(int, const char *, va_list) 46 | __attribute__((__format__ (printf, 2, 0))); 47 | __dead void fatal(const char *, ...) 48 | __attribute__((__format__ (printf, 1, 2))); 49 | __dead void fatalx(const char *, ...) 50 | __attribute__((__format__ (printf, 1, 2))); 51 | 52 | void 53 | log_init(int n_debug, int facility) 54 | { 55 | extern char *__progname; 56 | 57 | debug = n_debug; 58 | verbose = n_debug; 59 | log_procinit(__progname); 60 | 61 | if (!debug) 62 | openlog(__progname, LOG_PID | LOG_NDELAY, facility); 63 | 64 | tzset(); 65 | } 66 | 67 | void 68 | log_procinit(const char *procname) 69 | { 70 | if (procname != NULL) 71 | log_procname = procname; 72 | } 73 | 74 | void 75 | log_setverbose(int v) 76 | { 77 | verbose = v; 78 | } 79 | 80 | int 81 | log_getverbose(void) 82 | { 83 | return (verbose); 84 | } 85 | 86 | void 87 | logit(int pri, const char *fmt, ...) 88 | { 89 | va_list ap; 90 | 91 | va_start(ap, fmt); 92 | vlog(pri, fmt, ap); 93 | va_end(ap); 94 | } 95 | 96 | void 97 | vlog(int pri, const char *fmt, va_list ap) 98 | { 99 | char *nfmt; 100 | int saved_errno = errno; 101 | 102 | if (debug) { 103 | /* best effort in out of mem situations */ 104 | if (asprintf(&nfmt, "%s\n", fmt) == -1) { 105 | vfprintf(stderr, fmt, ap); 106 | fprintf(stderr, "\n"); 107 | } else { 108 | vfprintf(stderr, nfmt, ap); 109 | free(nfmt); 110 | } 111 | fflush(stderr); 112 | } else 113 | vsyslog(pri, fmt, ap); 114 | 115 | errno = saved_errno; 116 | } 117 | 118 | void 119 | log_warn(const char *emsg, ...) 120 | { 121 | char *nfmt; 122 | va_list ap; 123 | int saved_errno = errno; 124 | 125 | /* best effort to even work in out of memory situations */ 126 | if (emsg == NULL) 127 | logit(LOG_ERR, "%s", strerror(saved_errno)); 128 | else { 129 | va_start(ap, emsg); 130 | 131 | if (asprintf(&nfmt, "%s: %s", emsg, 132 | strerror(saved_errno)) == -1) { 133 | /* we tried it... */ 134 | vlog(LOG_ERR, emsg, ap); 135 | logit(LOG_ERR, "%s", strerror(saved_errno)); 136 | } else { 137 | vlog(LOG_ERR, nfmt, ap); 138 | free(nfmt); 139 | } 140 | va_end(ap); 141 | } 142 | 143 | errno = saved_errno; 144 | } 145 | 146 | void 147 | log_warnx(const char *emsg, ...) 148 | { 149 | va_list ap; 150 | 151 | va_start(ap, emsg); 152 | vlog(LOG_ERR, emsg, ap); 153 | va_end(ap); 154 | } 155 | 156 | void 157 | log_info(const char *emsg, ...) 158 | { 159 | va_list ap; 160 | 161 | va_start(ap, emsg); 162 | vlog(LOG_INFO, emsg, ap); 163 | va_end(ap); 164 | } 165 | 166 | void 167 | log_debug(const char *emsg, ...) 168 | { 169 | va_list ap; 170 | 171 | if (verbose > 1) { 172 | va_start(ap, emsg); 173 | vlog(LOG_DEBUG, emsg, ap); 174 | va_end(ap); 175 | } 176 | } 177 | 178 | static void 179 | vfatalc(int code, const char *emsg, va_list ap) 180 | { 181 | static char s[BUFSIZ]; 182 | const char *sep; 183 | 184 | if (emsg != NULL) { 185 | (void)vsnprintf(s, sizeof(s), emsg, ap); 186 | sep = ": "; 187 | } else { 188 | s[0] = '\0'; 189 | sep = ""; 190 | } 191 | if (code) 192 | logit(LOG_CRIT, "%s: %s%s%s", 193 | log_procname, s, sep, strerror(code)); 194 | else 195 | logit(LOG_CRIT, "%s%s%s", log_procname, sep, s); 196 | } 197 | 198 | void 199 | fatal(const char *emsg, ...) 200 | { 201 | va_list ap; 202 | 203 | va_start(ap, emsg); 204 | vfatalc(errno, emsg, ap); 205 | va_end(ap); 206 | exit(1); 207 | } 208 | 209 | void 210 | fatalx(const char *emsg, ...) 211 | { 212 | va_list ap; 213 | 214 | va_start(ap, emsg); 215 | vfatalc(0, emsg, ap); 216 | va_end(ap); 217 | exit(1); 218 | } 219 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/Proc.pm: -------------------------------------------------------------------------------- 1 | # $OpenBSD: Proc.pm,v 1.3 2021/10/05 17:40:08 anton Exp $ 2 | 3 | # Copyright (c) 2010-2014 Alexander Bluhm 4 | # 5 | # Permission to use, copy, modify, and distribute this software for any 6 | # purpose with or without fee is hereby granted, provided that the above 7 | # copyright notice and this permission notice appear in all copies. 8 | # 9 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | use strict; 18 | use warnings; 19 | 20 | package Proc; 21 | use Carp; 22 | use Errno; 23 | use File::Basename; 24 | use IO::File; 25 | use POSIX; 26 | use Time::HiRes qw(time alarm sleep); 27 | 28 | my %CHILDREN; 29 | 30 | sub kill_children { 31 | my @pids = @_ ? @_ : keys %CHILDREN 32 | or return; 33 | my @perms; 34 | foreach my $pid (@pids) { 35 | if (kill(TERM => $pid) != 1 and $!{EPERM}) { 36 | push @perms, $pid; 37 | } 38 | } 39 | if (my @sudo = split(' ', $ENV{SUDO}) and @perms) { 40 | local $?; # do not modify during END block 41 | my @cmd = (@sudo, '/bin/kill', '-TERM', @perms); 42 | system(@cmd); 43 | } 44 | delete @CHILDREN{@pids}; 45 | } 46 | 47 | BEGIN { 48 | $SIG{TERM} = $SIG{INT} = sub { 49 | my $sig = shift; 50 | kill_children(); 51 | $SIG{TERM} = $SIG{INT} = 'DEFAULT'; 52 | POSIX::raise($sig); 53 | }; 54 | } 55 | 56 | END { 57 | kill_children(); 58 | $SIG{TERM} = $SIG{INT} = 'DEFAULT'; 59 | } 60 | 61 | sub new { 62 | my $class = shift; 63 | my $self = { @_ }; 64 | $self->{down} ||= "Shutdown"; 65 | $self->{func} && ref($self->{func}) eq 'CODE' 66 | or croak "$class func not given"; 67 | $self->{logfile} 68 | or croak "$class log file not given"; 69 | open(my $fh, '>', $self->{logfile}) 70 | or die "$class log file $self->{logfile} create failed: $!"; 71 | $fh->autoflush; 72 | $self->{log} = $fh; 73 | return bless $self, $class; 74 | } 75 | 76 | sub run { 77 | my $self = shift; 78 | 79 | pipe(my $reader, my $writer) 80 | or die ref($self), " pipe to child failed: $!"; 81 | defined(my $pid = fork()) 82 | or die ref($self), " fork child failed: $!"; 83 | if ($pid) { 84 | $CHILDREN{$pid} = 1; 85 | $self->{pid} = $pid; 86 | close($reader); 87 | $self->{pipe} = $writer; 88 | return $self; 89 | } 90 | %CHILDREN = (); 91 | $SIG{TERM} = $SIG{INT} = 'DEFAULT'; 92 | $SIG{__DIE__} = sub { 93 | die @_ if $^S; 94 | warn @_; 95 | IO::Handle::flush(\*STDERR); 96 | POSIX::_exit(255); 97 | }; 98 | open(STDERR, '>&', $self->{log}) 99 | or die ref($self), " dup STDERR failed: $!"; 100 | close($writer); 101 | open(STDIN, '<&', $reader) 102 | or die ref($self), " dup STDIN failed: $!"; 103 | close($reader); 104 | 105 | do { 106 | $self->child(); 107 | print STDERR $self->{up}, "\n"; 108 | $self->{begin} = time(); 109 | $self->{func}->($self); 110 | } while ($self->{redo}); 111 | $self->{end} = time(); 112 | print STDERR "Shutdown", "\n"; 113 | if ($self->{timefile}) { 114 | open(my $fh, '>>', $self->{timefile}) 115 | or die ref($self), " open $self->{timefile} failed: $!"; 116 | printf $fh "time='%s' duration='%.10g' ". 117 | "test='%s'\n", 118 | scalar(localtime(time())), $self->{end} - $self->{begin}, 119 | basename($self->{testfile}); 120 | } 121 | 122 | IO::Handle::flush(\*STDOUT); 123 | IO::Handle::flush(\*STDERR); 124 | POSIX::_exit(0); 125 | } 126 | 127 | sub wait { 128 | my $self = shift; 129 | my $flags = shift; 130 | 131 | my $pid = $self->{pid} 132 | or croak ref($self), " no child pid"; 133 | my $kid = waitpid($pid, $flags); 134 | if ($kid > 0) { 135 | my $status = $?; 136 | my $code; 137 | $code = "exit: ". WEXITSTATUS($?) if WIFEXITED($?); 138 | $code = "signal: ". WTERMSIG($?) if WIFSIGNALED($?); 139 | $code = "stop: ". WSTOPSIG($?) if WIFSTOPPED($?); 140 | delete $CHILDREN{$pid} if WIFEXITED($?) || WIFSIGNALED($?); 141 | return wantarray ? ($kid, $status, $code) : $kid; 142 | } 143 | return $kid; 144 | } 145 | 146 | sub loggrep { 147 | my $self = shift; 148 | my($regex, $timeout) = @_; 149 | 150 | my $end; 151 | $end = time() + $timeout if $timeout; 152 | 153 | do { 154 | my($kid, $status, $code) = $self->wait(WNOHANG); 155 | if ($kid > 0 && $status != 0 && !$self->{dryrun}) { 156 | # child terminated with failure 157 | die ref($self), " child status: $status $code"; 158 | } 159 | open(my $fh, '<', $self->{logfile}) 160 | or die ref($self), " log file open failed: $!"; 161 | my @match = grep { /$regex/ } <$fh>; 162 | return wantarray ? @match : $match[0] if @match; 163 | close($fh); 164 | # pattern not found 165 | if ($kid == 0) { 166 | # child still running, wait for log data 167 | sleep .1; 168 | } else { 169 | # child terminated, no new log data possible 170 | return; 171 | } 172 | } while ($timeout and time() < $end); 173 | 174 | return; 175 | } 176 | 177 | sub up { 178 | my $self = shift; 179 | my $timeout = shift || 10; 180 | $self->loggrep(qr/$self->{up}/, $timeout) 181 | or croak ref($self), " no '$self->{up}' in $self->{logfile} ". 182 | "after $timeout seconds"; 183 | return $self; 184 | } 185 | 186 | sub down { 187 | my $self = shift; 188 | my $timeout = shift || 300; 189 | $self->loggrep(qr/$self->{down}/, $timeout) 190 | or croak ref($self), " no '$self->{down}' in $self->{logfile} ". 191 | "after $timeout seconds"; 192 | return $self; 193 | } 194 | 195 | sub kill_child { 196 | my $self = shift; 197 | kill_children($self->{pid}); 198 | return $self; 199 | } 200 | 201 | 1; 202 | -------------------------------------------------------------------------------- /src/usr.bin/htpasswd/htpasswd.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: htpasswd.c,v 1.18 2021/07/12 15:09:19 beck Exp $ */ 2 | /* 3 | * Copyright (c) 2014 Florian Obser 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #define _WITH_GETLINE 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #ifndef __OpenBSD__ 33 | #include "compat.h" 34 | #endif 35 | 36 | __dead void usage(void); 37 | void nag(char*); 38 | 39 | extern char *__progname; 40 | 41 | __dead void 42 | usage(void) 43 | { 44 | fprintf(stderr, "usage:\t%s [file] login\n", __progname); 45 | fprintf(stderr, "\t%s -I [file]\n", __progname); 46 | exit(1); 47 | } 48 | 49 | #define MAXNAG 5 50 | int nagcount; 51 | 52 | int 53 | main(int argc, char** argv) 54 | { 55 | char tmpl[sizeof("/tmp/htpasswd-XXXXXXXXXX")]; 56 | char hash[_PASSWORD_LEN], pass[1024], pass2[1024]; 57 | char *line = NULL, *login = NULL, *tok; 58 | int c, fd, loginlen, batch = 0; 59 | FILE *in = NULL, *out = NULL; 60 | const char *file = NULL; 61 | size_t linesize = 0; 62 | ssize_t linelen; 63 | mode_t old_umask; 64 | 65 | while ((c = getopt(argc, argv, "I")) != -1) { 66 | switch (c) { 67 | case 'I': 68 | batch = 1; 69 | break; 70 | default: 71 | usage(); 72 | /* NOT REACHED */ 73 | break; 74 | } 75 | } 76 | 77 | argc -= optind; 78 | argv += optind; 79 | 80 | #ifdef __OpenBSD__ 81 | if ((batch && argc == 1) || (!batch && argc == 2)) { 82 | if (unveil(argv[0], "rwc") == -1) 83 | err(1, "unveil %s", argv[0]); 84 | if (unveil("/tmp", "rwc") == -1) 85 | err(1, "unveil /tmp"); 86 | } 87 | if (pledge("stdio rpath wpath cpath flock tmppath tty", NULL) == -1) 88 | err(1, "pledge"); 89 | #endif 90 | 91 | if (batch) { 92 | if (argc == 1) 93 | file = argv[0]; 94 | else if (argc > 1) 95 | usage(); 96 | #ifdef __OpenBSD__ 97 | else if (pledge("stdio", NULL) == -1) 98 | err(1, "pledge"); 99 | #endif 100 | 101 | if ((linelen = getline(&line, &linesize, stdin)) == -1) 102 | err(1, "cannot read login:password from stdin"); 103 | line[linelen-1] = '\0'; 104 | 105 | if ((tok = strstr(line, ":")) == NULL) 106 | errx(1, "cannot find ':' in input"); 107 | *tok++ = '\0'; 108 | 109 | if ((loginlen = asprintf(&login, "%s:", line)) == -1) 110 | err(1, "asprintf"); 111 | 112 | if (strlcpy(pass, tok, sizeof(pass)) >= sizeof(pass)) 113 | errx(1, "password too long"); 114 | } else { 115 | 116 | switch (argc) { 117 | case 1: 118 | #ifdef __OpenBSD__ 119 | if (pledge("stdio tty", NULL) == -1) 120 | err(1, "pledge"); 121 | #endif 122 | if ((loginlen = asprintf(&login, "%s:", argv[0])) == -1) 123 | err(1, "asprintf"); 124 | break; 125 | case 2: 126 | file = argv[0]; 127 | if ((loginlen = asprintf(&login, "%s:", argv[1])) == -1) 128 | err(1, "asprintf"); 129 | break; 130 | default: 131 | usage(); 132 | /* NOT REACHED */ 133 | break; 134 | } 135 | 136 | if (!readpassphrase("Password: ", pass, sizeof(pass), 137 | RPP_ECHO_OFF)) 138 | err(1, "unable to read password"); 139 | if (!readpassphrase("Retype Password: ", pass2, sizeof(pass2), 140 | RPP_ECHO_OFF)) { 141 | explicit_bzero(pass, sizeof(pass)); 142 | err(1, "unable to read password"); 143 | } 144 | if (strcmp(pass, pass2) != 0) { 145 | explicit_bzero(pass, sizeof(pass)); 146 | explicit_bzero(pass2, sizeof(pass2)); 147 | errx(1, "passwords don't match"); 148 | } 149 | 150 | explicit_bzero(pass2, sizeof(pass2)); 151 | } 152 | 153 | if (crypt_newhash(pass, "bcrypt,a", hash, sizeof(hash)) != 0) 154 | err(1, "can't generate hash"); 155 | explicit_bzero(pass, sizeof(pass)); 156 | 157 | if (file == NULL) 158 | printf("%s%s\n", login, hash); 159 | else { 160 | if ((in = fopen(file, "r+")) == NULL) { 161 | if (errno == ENOENT) { 162 | old_umask = umask(S_IXUSR| 163 | S_IWGRP|S_IRGRP|S_IXGRP| 164 | S_IWOTH|S_IROTH|S_IXOTH); 165 | if ((out = fopen(file, "w")) == NULL) 166 | err(1, "cannot open password file for" 167 | " reading or writing"); 168 | umask(old_umask); 169 | } else 170 | err(1, "cannot open password file for" 171 | " reading or writing"); 172 | } else 173 | if (flock(fileno(in), LOCK_EX|LOCK_NB) == -1) 174 | errx(1, "cannot lock password file"); 175 | 176 | /* file already exits, copy content and filter login out */ 177 | if (out == NULL) { 178 | strlcpy(tmpl, "/tmp/htpasswd-XXXXXXXXXX", sizeof(tmpl)); 179 | if ((fd = mkstemp(tmpl)) == -1) 180 | err(1, "mkstemp"); 181 | 182 | if ((out = fdopen(fd, "w+")) == NULL) 183 | err(1, "cannot open tempfile"); 184 | 185 | while ((linelen = getline(&line, &linesize, in)) 186 | != -1) { 187 | if (strncmp(line, login, loginlen) != 0) { 188 | if (fprintf(out, "%s", line) == -1) 189 | errx(1, "cannot write to temp " 190 | "file"); 191 | nag(line); 192 | } 193 | } 194 | } 195 | if (fprintf(out, "%s%s\n", login, hash) == -1) 196 | errx(1, "cannot write new password hash"); 197 | 198 | /* file already exists, overwrite it */ 199 | if (in != NULL) { 200 | if (fseek(in, 0, SEEK_SET) == -1) 201 | err(1, "cannot seek in password file"); 202 | if (fseek(out, 0, SEEK_SET) == -1) 203 | err(1, "cannot seek in temp file"); 204 | if (ftruncate(fileno(in), 0) == -1) 205 | err(1, "cannot truncate password file"); 206 | while ((linelen = getline(&line, &linesize, out)) 207 | != -1) 208 | if (fprintf(in, "%s", line) == -1) 209 | errx(1, "cannot write to password " 210 | "file"); 211 | if (fclose(in) == EOF) 212 | err(1, "cannot close password file"); 213 | } 214 | if (fclose(out) == EOF) { 215 | if (in != NULL) 216 | err(1, "cannot close temp file"); 217 | else 218 | err(1, "cannot close password file"); 219 | } 220 | if (in != NULL && unlink(tmpl) == -1) 221 | err(1, "cannot delete temp file (%s)", tmpl); 222 | } 223 | if (nagcount >= MAXNAG) 224 | warnx("%d more logins not using bcryt.", nagcount - MAXNAG); 225 | exit(0); 226 | } 227 | 228 | void 229 | nag(char* line) 230 | { 231 | const char *tok; 232 | if (strtok(line, ":") != NULL) 233 | if ((tok = strtok(NULL, ":")) != NULL) 234 | if (strncmp(tok, "$2a$", 4) != 0 && 235 | strncmp(tok, "$2b$", 4) != 0) { 236 | nagcount++; 237 | if (nagcount <= MAXNAG) 238 | warnx("%s doesn't use bcrypt." 239 | " Update the password.", line); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/lib/libc/gen/vis.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: vis.c,v 1.26 2022/05/04 18:57:50 deraadt Exp $ */ 2 | /*- 3 | * Copyright (c) 1989, 1993 4 | * The Regents of the University of California. All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 3. Neither the name of the University nor the names of its contributors 15 | * may be used to endorse or promote products derived from this software 16 | * without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #ifndef __OpenBSD__ 40 | #include "compat.h" 41 | #endif 42 | 43 | static int 44 | isoctal(int c) 45 | { 46 | u_char uc = c; 47 | 48 | return uc >= '0' && uc <= '7'; 49 | } 50 | 51 | static int 52 | isvisible(int c, int flag) 53 | { 54 | int vis_sp = flag & VIS_SP; 55 | int vis_tab = flag & VIS_TAB; 56 | int vis_nl = flag & VIS_NL; 57 | int vis_safe = flag & VIS_SAFE; 58 | int vis_glob = flag & VIS_GLOB; 59 | #ifdef __OpenBSD__ 60 | int vis_all = flag & VIS_ALL; 61 | #else 62 | int vis_all = flag & VIS_HTTP1866; 63 | #endif 64 | u_char uc = c; 65 | 66 | if (c == '\\' || !vis_all) { 67 | if ((u_int)c <= UCHAR_MAX && isascii(uc) && 68 | ((c != '*' && c != '?' && c != '[' && c != '#') || !vis_glob) && 69 | isgraph(uc)) 70 | return 1; 71 | if (!vis_sp && c == ' ') 72 | return 1; 73 | if (!vis_tab && c == '\t') 74 | return 1; 75 | if (!vis_nl && c == '\n') 76 | return 1; 77 | if (vis_safe && (c == '\b' || c == '\007' || c == '\r' || isgraph(uc))) 78 | return 1; 79 | } 80 | return 0; 81 | } 82 | 83 | /* 84 | * vis - visually encode characters 85 | */ 86 | char * 87 | vis(char *dst, int c, int flag, int nextc) 88 | { 89 | int vis_dq = flag & VIS_DQ; 90 | int vis_noslash = flag & VIS_NOSLASH; 91 | int vis_cstyle = flag & VIS_CSTYLE; 92 | int vis_octal = flag & VIS_OCTAL; 93 | int vis_glob = flag & VIS_GLOB; 94 | 95 | if (isvisible(c, flag)) { 96 | if ((c == '"' && vis_dq) || 97 | (c == '\\' && !vis_noslash)) 98 | *dst++ = '\\'; 99 | *dst++ = c; 100 | *dst = '\0'; 101 | return (dst); 102 | } 103 | 104 | if (vis_cstyle) { 105 | switch (c) { 106 | case '\n': 107 | *dst++ = '\\'; 108 | *dst++ = 'n'; 109 | goto done; 110 | case '\r': 111 | *dst++ = '\\'; 112 | *dst++ = 'r'; 113 | goto done; 114 | case '\b': 115 | *dst++ = '\\'; 116 | *dst++ = 'b'; 117 | goto done; 118 | case '\a': 119 | *dst++ = '\\'; 120 | *dst++ = 'a'; 121 | goto done; 122 | case '\v': 123 | *dst++ = '\\'; 124 | *dst++ = 'v'; 125 | goto done; 126 | case '\t': 127 | *dst++ = '\\'; 128 | *dst++ = 't'; 129 | goto done; 130 | case '\f': 131 | *dst++ = '\\'; 132 | *dst++ = 'f'; 133 | goto done; 134 | case ' ': 135 | *dst++ = '\\'; 136 | *dst++ = 's'; 137 | goto done; 138 | case '\0': 139 | *dst++ = '\\'; 140 | *dst++ = '0'; 141 | if (isoctal(nextc)) { 142 | *dst++ = '0'; 143 | *dst++ = '0'; 144 | } 145 | goto done; 146 | } 147 | } 148 | if (((c & 0177) == ' ') || vis_octal || 149 | (vis_glob && (c == '*' || c == '?' || c == '[' || c == '#'))) { 150 | *dst++ = '\\'; 151 | *dst++ = ((u_char)c >> 6 & 07) + '0'; 152 | *dst++ = ((u_char)c >> 3 & 07) + '0'; 153 | *dst++ = ((u_char)c & 07) + '0'; 154 | goto done; 155 | } 156 | if (!vis_noslash) 157 | *dst++ = '\\'; 158 | if (c & 0200) { 159 | c &= 0177; 160 | *dst++ = 'M'; 161 | } 162 | if (iscntrl((u_char)c)) { 163 | *dst++ = '^'; 164 | if (c == 0177) 165 | *dst++ = '?'; 166 | else 167 | *dst++ = c + '@'; 168 | } else { 169 | *dst++ = '-'; 170 | *dst++ = c; 171 | } 172 | done: 173 | *dst = '\0'; 174 | return (dst); 175 | } 176 | #ifdef __OpenBSD__ 177 | DEF_WEAK(vis); 178 | #endif 179 | 180 | /* 181 | * strvis, strnvis, strvisx - visually encode characters from src into dst 182 | * 183 | * Dst must be 4 times the size of src to account for possible 184 | * expansion. The length of dst, not including the trailing NULL, 185 | * is returned. 186 | * 187 | * Strnvis will write no more than siz-1 bytes (and will NULL terminate). 188 | * The number of bytes needed to fully encode the string is returned. 189 | * 190 | * Strvisx encodes exactly len bytes from src into dst. 191 | * This is useful for encoding a block of data. 192 | */ 193 | int 194 | strvis(char *dst, const char *src, int flag) 195 | { 196 | char c; 197 | char *start; 198 | 199 | for (start = dst; (c = *src);) 200 | dst = vis(dst, c, flag, *++src); 201 | *dst = '\0'; 202 | return (dst - start); 203 | } 204 | #ifdef __OpenBSD__ 205 | DEF_WEAK(strvis); 206 | 207 | int 208 | strnvis(char *dst, const char *src, size_t siz, int flag) 209 | { 210 | int vis_dq = flag & VIS_DQ; 211 | int vis_noslash = flag & VIS_NOSLASH; 212 | char *start, *end; 213 | char tbuf[5]; 214 | int c, i; 215 | 216 | i = 0; 217 | for (start = dst, end = start + siz - 1; (c = *src) && dst < end; ) { 218 | if (isvisible(c, flag)) { 219 | if ((c == '"' && vis_dq) || 220 | (c == '\\' && !vis_noslash)) { 221 | /* need space for the extra '\\' */ 222 | if (dst + 1 >= end) { 223 | i = 2; 224 | break; 225 | } 226 | *dst++ = '\\'; 227 | } 228 | i = 1; 229 | *dst++ = c; 230 | src++; 231 | } else { 232 | i = vis(tbuf, c, flag, *++src) - tbuf; 233 | if (dst + i <= end) { 234 | memcpy(dst, tbuf, i); 235 | dst += i; 236 | } else { 237 | src--; 238 | break; 239 | } 240 | } 241 | } 242 | if (siz > 0) 243 | *dst = '\0'; 244 | if (dst + i > end) { 245 | /* adjust return value for truncation */ 246 | while ((c = *src)) 247 | dst += vis(tbuf, c, flag, *++src) - tbuf; 248 | } 249 | return (dst - start); 250 | } 251 | #endif 252 | 253 | int 254 | stravis(char **outp, const char *src, int flag) 255 | { 256 | char *buf; 257 | int len, serrno; 258 | 259 | buf = reallocarray(NULL, 4, strlen(src) + 1); 260 | if (buf == NULL) 261 | return -1; 262 | len = strvis(buf, src, flag); 263 | serrno = errno; 264 | *outp = realloc(buf, len + 1); 265 | if (*outp == NULL) { 266 | *outp = buf; 267 | errno = serrno; 268 | } 269 | return (len); 270 | } 271 | 272 | int 273 | strvisx(char *dst, const char *src, size_t len, int flag) 274 | { 275 | char c; 276 | char *start; 277 | 278 | for (start = dst; len > 1; len--) { 279 | c = *src; 280 | dst = vis(dst, c, flag, *++src); 281 | } 282 | if (len) 283 | dst = vis(dst, *src, flag, '\0'); 284 | *dst = '\0'; 285 | return (dst - start); 286 | } 287 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/control.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: control.c,v 1.15 2023/03/08 04:43:13 guenther Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2003, 2004 Henning Brauer 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "httpd.h" 35 | 36 | #define CONTROL_BACKLOG 5 37 | 38 | struct ctl_connlist ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns); 39 | 40 | void control_accept(int, short, void *); 41 | void control_close(int, struct control_sock *); 42 | 43 | int 44 | control_init(struct privsep *ps, struct control_sock *cs) 45 | { 46 | struct httpd *env = ps->ps_env; 47 | struct sockaddr_un sun; 48 | int fd; 49 | mode_t old_umask, mode; 50 | 51 | if (cs->cs_name == NULL) 52 | return (0); 53 | 54 | if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1) { 55 | log_warn("%s: socket", __func__); 56 | return (-1); 57 | } 58 | 59 | sun.sun_family = AF_UNIX; 60 | if (strlcpy(sun.sun_path, cs->cs_name, 61 | sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) { 62 | log_warn("%s: %s name too long", __func__, cs->cs_name); 63 | close(fd); 64 | return (-1); 65 | } 66 | 67 | if (unlink(cs->cs_name) == -1) 68 | if (errno != ENOENT) { 69 | log_warn("%s: unlink %s", __func__, cs->cs_name); 70 | close(fd); 71 | return (-1); 72 | } 73 | 74 | if (cs->cs_restricted) { 75 | old_umask = umask(S_IXUSR|S_IXGRP|S_IXOTH); 76 | mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH; 77 | } else { 78 | old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); 79 | mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP; 80 | } 81 | 82 | if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { 83 | log_warn("%s: bind: %s", __func__, cs->cs_name); 84 | close(fd); 85 | (void)umask(old_umask); 86 | return (-1); 87 | } 88 | (void)umask(old_umask); 89 | 90 | if (chmod(cs->cs_name, mode) == -1) { 91 | log_warn("%s: chmod", __func__); 92 | close(fd); 93 | (void)unlink(cs->cs_name); 94 | return (-1); 95 | } 96 | 97 | cs->cs_fd = fd; 98 | cs->cs_env = env; 99 | 100 | return (0); 101 | } 102 | 103 | int 104 | control_listen(struct control_sock *cs) 105 | { 106 | if (cs->cs_name == NULL) 107 | return (0); 108 | 109 | if (listen(cs->cs_fd, CONTROL_BACKLOG) == -1) { 110 | log_warn("%s: listen", __func__); 111 | return (-1); 112 | } 113 | 114 | event_set(&cs->cs_ev, cs->cs_fd, EV_READ, 115 | control_accept, cs); 116 | event_add(&cs->cs_ev, NULL); 117 | evtimer_set(&cs->cs_evt, control_accept, cs); 118 | 119 | return (0); 120 | } 121 | 122 | void 123 | control_cleanup(struct control_sock *cs) 124 | { 125 | if (cs->cs_name == NULL) 126 | return; 127 | event_del(&cs->cs_ev); 128 | event_del(&cs->cs_evt); 129 | } 130 | 131 | void 132 | control_accept(int listenfd, short event, void *arg) 133 | { 134 | int connfd; 135 | socklen_t len; 136 | struct sockaddr_un sun; 137 | struct ctl_conn *c; 138 | struct control_sock *cs = arg; 139 | 140 | event_add(&cs->cs_ev, NULL); 141 | if ((event & EV_TIMEOUT)) 142 | return; 143 | 144 | len = sizeof(sun); 145 | if ((connfd = accept4(listenfd, 146 | (struct sockaddr *)&sun, &len, SOCK_NONBLOCK)) == -1) { 147 | /* 148 | * Pause accept if we are out of file descriptors, or 149 | * libevent will haunt us here too. 150 | */ 151 | if (errno == ENFILE || errno == EMFILE) { 152 | struct timeval evtpause = { 1, 0 }; 153 | 154 | event_del(&cs->cs_ev); 155 | evtimer_add(&cs->cs_evt, &evtpause); 156 | } else if (errno != EWOULDBLOCK && errno != EINTR && 157 | errno != ECONNABORTED) 158 | log_warn("%s: accept", __func__); 159 | return; 160 | } 161 | 162 | if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) { 163 | close(connfd); 164 | log_warn("%s: calloc", __func__); 165 | return; 166 | } 167 | 168 | imsg_init(&c->iev.ibuf, connfd); 169 | c->iev.handler = control_dispatch_imsg; 170 | c->iev.events = EV_READ; 171 | c->iev.data = cs; /* proc.c cheats (reuses the handler) */ 172 | event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, 173 | c->iev.handler, cs); 174 | event_add(&c->iev.ev, NULL); 175 | 176 | TAILQ_INSERT_TAIL(&ctl_conns, c, entry); 177 | } 178 | 179 | struct ctl_conn * 180 | control_connbyfd(int fd) 181 | { 182 | struct ctl_conn *c; 183 | 184 | TAILQ_FOREACH(c, &ctl_conns, entry) { 185 | if (c->iev.ibuf.fd == fd) 186 | break; 187 | } 188 | 189 | return (c); 190 | } 191 | 192 | void 193 | control_close(int fd, struct control_sock *cs) 194 | { 195 | struct ctl_conn *c; 196 | 197 | if ((c = control_connbyfd(fd)) == NULL) { 198 | log_warn("%s: fd %d not found", __func__, fd); 199 | return; 200 | } 201 | 202 | msgbuf_clear(&c->iev.ibuf.w); 203 | TAILQ_REMOVE(&ctl_conns, c, entry); 204 | 205 | event_del(&c->iev.ev); 206 | close(c->iev.ibuf.fd); 207 | 208 | /* Some file descriptors are available again. */ 209 | if (evtimer_pending(&cs->cs_evt, NULL)) { 210 | evtimer_del(&cs->cs_evt); 211 | event_add(&cs->cs_ev, NULL); 212 | } 213 | 214 | free(c); 215 | } 216 | 217 | void 218 | control_dispatch_imsg(int fd, short event, void *arg) 219 | { 220 | struct control_sock *cs = arg; 221 | struct ctl_conn *c; 222 | struct imsg imsg; 223 | int n; 224 | int verbose; 225 | struct httpd *env = cs->cs_env; 226 | 227 | if ((c = control_connbyfd(fd)) == NULL) { 228 | log_warn("%s: fd %d not found", __func__, fd); 229 | return; 230 | } 231 | 232 | if (event & EV_READ) { 233 | if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) || 234 | n == 0) { 235 | control_close(fd, cs); 236 | return; 237 | } 238 | } 239 | 240 | if (event & EV_WRITE) { 241 | if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) { 242 | control_close(fd, cs); 243 | return; 244 | } 245 | } 246 | 247 | for (;;) { 248 | if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { 249 | control_close(fd, cs); 250 | return; 251 | } 252 | 253 | if (n == 0) 254 | break; 255 | 256 | if (c->waiting) { 257 | log_debug("%s: unexpected imsg %d", 258 | __func__, imsg.hdr.type); 259 | imsg_free(&imsg); 260 | control_close(fd, cs); 261 | return; 262 | } 263 | 264 | switch (imsg.hdr.type) { 265 | case IMSG_CTL_SHUTDOWN: 266 | case IMSG_CTL_RELOAD: 267 | case IMSG_CTL_REOPEN: 268 | proc_forward_imsg(env->sc_ps, &imsg, PROC_PARENT, -1); 269 | break; 270 | case IMSG_CTL_NOTIFY: 271 | if (c->flags & CTL_CONN_NOTIFY) { 272 | log_debug("%s: " 273 | "client requested notify more than once", 274 | __func__); 275 | imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 276 | 0, env->sc_ps->ps_instance + 1, -1, 277 | NULL, 0); 278 | break; 279 | } 280 | c->flags |= CTL_CONN_NOTIFY; 281 | break; 282 | case IMSG_CTL_VERBOSE: 283 | IMSG_SIZE_CHECK(&imsg, &verbose); 284 | 285 | memcpy(&verbose, imsg.data, sizeof(verbose)); 286 | 287 | proc_forward_imsg(env->sc_ps, &imsg, PROC_PARENT, -1); 288 | proc_forward_imsg(env->sc_ps, &imsg, PROC_SERVER, -1); 289 | 290 | memcpy(imsg.data, &verbose, sizeof(verbose)); 291 | control_imsg_forward(env->sc_ps, &imsg); 292 | log_setverbose(verbose); 293 | break; 294 | default: 295 | log_debug("%s: error handling imsg %d", 296 | __func__, imsg.hdr.type); 297 | break; 298 | } 299 | imsg_free(&imsg); 300 | } 301 | 302 | imsg_event_add(&c->iev); 303 | } 304 | 305 | void 306 | control_imsg_forward(struct privsep *ps, struct imsg *imsg) 307 | { 308 | struct ctl_conn *c; 309 | 310 | TAILQ_FOREACH(c, &ctl_conns, entry) 311 | if (c->flags & CTL_CONN_NOTIFY) 312 | imsg_compose_event(&c->iev, imsg->hdr.type, 313 | 0, ps->ps_instance + 1, -1, imsg->data, 314 | imsg->hdr.len - IMSG_HEADER_SIZE); 315 | } 316 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/http.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: http.h,v 1.16 2020/09/12 07:34:17 yasuoka Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2012 - 2015 Reyk Floeter 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #ifndef HTTP_H 20 | #define HTTP_H 21 | 22 | #define HTTP_PORT 80 23 | #define HTTPS_PORT 443 24 | 25 | enum httpmethod { 26 | HTTP_METHOD_NONE = 0, 27 | 28 | /* HTTP/1.1, RFC 7231 */ 29 | HTTP_METHOD_GET, 30 | HTTP_METHOD_HEAD, 31 | HTTP_METHOD_POST, 32 | HTTP_METHOD_PUT, 33 | HTTP_METHOD_DELETE, 34 | HTTP_METHOD_OPTIONS, 35 | HTTP_METHOD_TRACE, 36 | HTTP_METHOD_CONNECT, 37 | 38 | /* WebDAV, RFC 4918 */ 39 | HTTP_METHOD_PROPFIND, 40 | HTTP_METHOD_PROPPATCH, 41 | HTTP_METHOD_MKCOL, 42 | HTTP_METHOD_COPY, 43 | HTTP_METHOD_MOVE, 44 | HTTP_METHOD_LOCK, 45 | HTTP_METHOD_UNLOCK, 46 | 47 | /* WebDAV Versioning Extension, RFC 3253 */ 48 | HTTP_METHOD_VERSION_CONTROL, 49 | HTTP_METHOD_REPORT, 50 | HTTP_METHOD_CHECKOUT, 51 | HTTP_METHOD_CHECKIN, 52 | HTTP_METHOD_UNCHECKOUT, 53 | HTTP_METHOD_MKWORKSPACE, 54 | HTTP_METHOD_UPDATE, 55 | HTTP_METHOD_LABEL, 56 | HTTP_METHOD_MERGE, 57 | HTTP_METHOD_BASELINE_CONTROL, 58 | HTTP_METHOD_MKACTIVITY, 59 | 60 | /* WebDAV Ordered Collections, RFC 3648 */ 61 | HTTP_METHOD_ORDERPATCH, 62 | 63 | /* WebDAV Access Control, RFC 3744 */ 64 | HTTP_METHOD_ACL, 65 | 66 | /* WebDAV Redirect Reference Resources, RFC 4437 */ 67 | HTTP_METHOD_MKREDIRECTREF, 68 | HTTP_METHOD_UPDATEREDIRECTREF, 69 | 70 | /* WebDAV Search, RFC 5323 */ 71 | HTTP_METHOD_SEARCH, 72 | 73 | /* PATCH, RFC 5789 */ 74 | HTTP_METHOD_PATCH, 75 | 76 | /* Server response (internal value) */ 77 | HTTP_METHOD_RESPONSE 78 | }; 79 | 80 | struct http_method { 81 | enum httpmethod method_id; 82 | const char *method_name; 83 | }; 84 | #define HTTP_METHODS { \ 85 | { HTTP_METHOD_GET, "GET" }, \ 86 | { HTTP_METHOD_HEAD, "HEAD" }, \ 87 | { HTTP_METHOD_POST, "POST" }, \ 88 | { HTTP_METHOD_PUT, "PUT" }, \ 89 | { HTTP_METHOD_DELETE, "DELETE" }, \ 90 | { HTTP_METHOD_OPTIONS, "OPTIONS" }, \ 91 | { HTTP_METHOD_TRACE, "TRACE" }, \ 92 | { HTTP_METHOD_CONNECT, "CONNECT" }, \ 93 | { HTTP_METHOD_PROPFIND, "PROPFIND" }, \ 94 | { HTTP_METHOD_PROPPATCH, "PROPPATCH" }, \ 95 | { HTTP_METHOD_MKCOL, "MKCOL" }, \ 96 | { HTTP_METHOD_COPY, "COPY" }, \ 97 | { HTTP_METHOD_MOVE, "MOVE" }, \ 98 | { HTTP_METHOD_LOCK, "LOCK" }, \ 99 | { HTTP_METHOD_UNLOCK, "UNLOCK" }, \ 100 | { HTTP_METHOD_VERSION_CONTROL, "VERSION-CONTROL" }, \ 101 | { HTTP_METHOD_REPORT, "REPORT" }, \ 102 | { HTTP_METHOD_CHECKOUT, "CHECKOUT" }, \ 103 | { HTTP_METHOD_CHECKIN, "CHECKIN" }, \ 104 | { HTTP_METHOD_UNCHECKOUT, "UNCHECKOUT" }, \ 105 | { HTTP_METHOD_MKWORKSPACE, "MKWORKSPACE" }, \ 106 | { HTTP_METHOD_UPDATE, "UPDATE" }, \ 107 | { HTTP_METHOD_LABEL, "LABEL" }, \ 108 | { HTTP_METHOD_MERGE, "MERGE" }, \ 109 | { HTTP_METHOD_BASELINE_CONTROL, "BASELINE-CONTROL" }, \ 110 | { HTTP_METHOD_MKACTIVITY, "MKACTIVITY" }, \ 111 | { HTTP_METHOD_ORDERPATCH, "ORDERPATCH" }, \ 112 | { HTTP_METHOD_ACL, "ACL" }, \ 113 | { HTTP_METHOD_MKREDIRECTREF, "MKREDIRECTREF" }, \ 114 | { HTTP_METHOD_UPDATEREDIRECTREF, "UPDATEREDIRECTREF" }, \ 115 | { HTTP_METHOD_SEARCH, "SEARCH" }, \ 116 | { HTTP_METHOD_PATCH, "PATCH" }, \ 117 | { HTTP_METHOD_NONE, NULL } \ 118 | } 119 | 120 | struct http_error { 121 | int error_code; 122 | const char *error_name; 123 | }; 124 | 125 | /* 126 | * HTTP status codes based on IANA assignments (2014-06-11 version): 127 | * https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml 128 | * plus legacy (306) and non-standard (420). 129 | */ 130 | #define HTTP_ERRORS { \ 131 | { 100, "Continue" }, \ 132 | { 101, "Switching Protocols" }, \ 133 | { 102, "Processing" }, \ 134 | /* 103-199 unassigned */ \ 135 | { 200, "OK" }, \ 136 | { 201, "Created" }, \ 137 | { 202, "Accepted" }, \ 138 | { 203, "Non-Authoritative Information" }, \ 139 | { 204, "No Content" }, \ 140 | { 205, "Reset Content" }, \ 141 | { 206, "Partial Content" }, \ 142 | { 207, "Multi-Status" }, \ 143 | { 208, "Already Reported" }, \ 144 | /* 209-225 unassigned */ \ 145 | { 226, "IM Used" }, \ 146 | /* 227-299 unassigned */ \ 147 | { 300, "Multiple Choices" }, \ 148 | { 301, "Moved Permanently" }, \ 149 | { 302, "Found" }, \ 150 | { 303, "See Other" }, \ 151 | { 304, "Not Modified" }, \ 152 | { 305, "Use Proxy" }, \ 153 | { 306, "Switch Proxy" }, \ 154 | { 307, "Temporary Redirect" }, \ 155 | { 308, "Permanent Redirect" }, \ 156 | /* 309-399 unassigned */ \ 157 | { 400, "Bad Request" }, \ 158 | { 401, "Unauthorized" }, \ 159 | { 402, "Payment Required" }, \ 160 | { 403, "Forbidden" }, \ 161 | { 404, "Not Found" }, \ 162 | { 405, "Method Not Allowed" }, \ 163 | { 406, "Not Acceptable" }, \ 164 | { 407, "Proxy Authentication Required" }, \ 165 | { 408, "Request Timeout" }, \ 166 | { 409, "Conflict" }, \ 167 | { 410, "Gone" }, \ 168 | { 411, "Length Required" }, \ 169 | { 412, "Precondition Failed" }, \ 170 | { 413, "Payload Too Large" }, \ 171 | { 414, "URI Too Long" }, \ 172 | { 415, "Unsupported Media Type" }, \ 173 | { 416, "Range Not Satisfiable" }, \ 174 | { 417, "Expectation Failed" }, \ 175 | { 418, "I'm a teapot" }, \ 176 | /* 419-421 unassigned */ \ 177 | { 420, "Enhance Your Calm" }, \ 178 | { 422, "Unprocessable Entity" }, \ 179 | { 423, "Locked" }, \ 180 | { 424, "Failed Dependency" }, \ 181 | /* 425 unassigned */ \ 182 | { 426, "Upgrade Required" }, \ 183 | /* 427 unassigned */ \ 184 | { 428, "Precondition Required" }, \ 185 | { 429, "Too Many Requests" }, \ 186 | /* 430 unassigned */ \ 187 | { 431, "Request Header Fields Too Large" }, \ 188 | /* 432-450 unassigned */ \ 189 | { 451, "Unavailable For Legal Reasons" }, \ 190 | /* 452-499 unassigned */ \ 191 | { 500, "Internal Server Error" }, \ 192 | { 501, "Not Implemented" }, \ 193 | { 502, "Bad Gateway" }, \ 194 | { 503, "Service Unavailable" }, \ 195 | { 504, "Gateway Timeout" }, \ 196 | { 505, "HTTP Version Not Supported" }, \ 197 | { 506, "Variant Also Negotiates" }, \ 198 | { 507, "Insufficient Storage" }, \ 199 | { 508, "Loop Detected" }, \ 200 | /* 509 unassigned */ \ 201 | { 510, "Not Extended" }, \ 202 | { 511, "Network Authentication Required" }, \ 203 | /* 512-599 unassigned */ \ 204 | { 0, NULL } \ 205 | } 206 | 207 | struct http_mediatype { 208 | char *media_name; 209 | char *media_type; 210 | char *media_subtype; 211 | }; 212 | /* 213 | * Some default media types based on (2014-08-04 version): 214 | * https://www.iana.org/assignments/media-types/media-types.xhtml 215 | */ 216 | #define MEDIA_TYPES { \ 217 | { "css", "text", "css" }, \ 218 | { "html", "text", "html" }, \ 219 | { "txt", "text", "plain" }, \ 220 | { "gif", "image", "gif" }, \ 221 | { "jpeg", "image", "jpeg" }, \ 222 | { "jpg", "image", "jpeg" }, \ 223 | { "png", "image", "png" }, \ 224 | { "svg", "image", "svg+xml" }, \ 225 | { "js", "application", "javascript" }, \ 226 | { NULL } \ 227 | } 228 | 229 | /* Used during runtime */ 230 | struct http_descriptor { 231 | struct kv http_pathquery; 232 | struct kv http_matchquery; 233 | #define http_path http_pathquery.kv_key 234 | #define http_query http_pathquery.kv_value 235 | #define http_rescode http_pathquery.kv_key 236 | #define http_resmesg http_pathquery.kv_value 237 | #define query_key http_matchquery.kv_key 238 | #define query_val http_matchquery.kv_value 239 | 240 | char *http_host; 241 | enum httpmethod http_method; 242 | int http_chunked; 243 | char *http_version; 244 | unsigned int http_status; 245 | 246 | /* Rewritten path and query remain NULL if not used */ 247 | char *http_path_alias; 248 | char *http_query_alias; 249 | char *http_path_orig; 250 | 251 | /* A tree of headers and attached lists for repeated headers. */ 252 | struct kv *http_lastheader; 253 | struct kvtree http_headers; 254 | }; 255 | 256 | #endif /* HTTP_H */ 257 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/logger.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: logger.c,v 1.24 2021/01/27 07:21:53 deraadt Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2014 Reyk Floeter 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #define _WITH_DPRINTF 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "httpd.h" 33 | 34 | int logger_dispatch_parent(int, struct privsep_proc *, 35 | struct imsg *); 36 | int logger_dispatch_server(int, struct privsep_proc *, 37 | struct imsg *); 38 | void logger_shutdown(void); 39 | void logger_close(void); 40 | struct log_file *logger_open_file(const char *); 41 | int logger_open_fd(struct imsg *); 42 | int logger_open(struct server *, struct server_config *, void *); 43 | void logger_init(struct privsep *, struct privsep_proc *p, void *); 44 | int logger_start(void); 45 | int logger_log(struct imsg *); 46 | 47 | static uint32_t last_log_id = 0; 48 | 49 | struct log_files log_files; 50 | 51 | static struct privsep_proc procs[] = { 52 | { "parent", PROC_PARENT, logger_dispatch_parent }, 53 | { "server", PROC_SERVER, logger_dispatch_server } 54 | }; 55 | 56 | void 57 | logger(struct privsep *ps, struct privsep_proc *p) 58 | { 59 | proc_run(ps, p, procs, nitems(procs), logger_init, NULL); 60 | } 61 | 62 | void 63 | logger_shutdown(void) 64 | { 65 | logger_close(); 66 | config_purge(httpd_env, CONFIG_ALL); 67 | } 68 | 69 | void 70 | logger_init(struct privsep *ps, struct privsep_proc *p, void *arg) 71 | { 72 | #ifdef __OpenBSD__ 73 | if (pledge("stdio recvfd", NULL) == -1) 74 | fatal("pledge"); 75 | #endif 76 | 77 | if (config_init(ps->ps_env) == -1) 78 | fatal("failed to initialize configuration"); 79 | 80 | /* We use a custom shutdown callback */ 81 | p->p_shutdown = logger_shutdown; 82 | 83 | TAILQ_INIT(&log_files); 84 | } 85 | 86 | void 87 | logger_close(void) 88 | { 89 | struct log_file *log, *next; 90 | 91 | TAILQ_FOREACH_SAFE(log, &log_files, log_entry, next) { 92 | if (log->log_fd != -1) { 93 | close(log->log_fd); 94 | log->log_fd = -1; 95 | } 96 | TAILQ_REMOVE(&log_files, log, log_entry); 97 | free(log); 98 | } 99 | } 100 | 101 | struct log_file * 102 | logger_open_file(const char *name) 103 | { 104 | struct log_file *log; 105 | struct iovec iov[2]; 106 | 107 | if ((log = calloc(1, sizeof(*log))) == NULL) { 108 | log_warn("failed to allocate log %s", name); 109 | return (NULL); 110 | } 111 | 112 | log->log_id = ++last_log_id; 113 | (void)strlcpy(log->log_name, name, sizeof(log->log_name)); 114 | 115 | /* The file will be opened by the parent process */ 116 | log->log_fd = -1; 117 | 118 | iov[0].iov_base = &log->log_id; 119 | iov[0].iov_len = sizeof(log->log_id); 120 | iov[1].iov_base = log->log_name; 121 | iov[1].iov_len = strlen(log->log_name) + 1; 122 | 123 | if (proc_composev(httpd_env->sc_ps, PROC_PARENT, IMSG_LOG_OPEN, 124 | iov, 2) != 0) { 125 | log_warn("%s: failed to compose IMSG_LOG_OPEN imsg", __func__); 126 | goto err; 127 | } 128 | 129 | TAILQ_INSERT_TAIL(&log_files, log, log_entry); 130 | 131 | return (log); 132 | 133 | err: 134 | free(log); 135 | 136 | return (NULL); 137 | } 138 | 139 | int 140 | logger_open_fd(struct imsg *imsg) 141 | { 142 | struct log_file *log; 143 | uint32_t id; 144 | 145 | IMSG_SIZE_CHECK(imsg, &id); 146 | memcpy(&id, imsg->data, sizeof(id)); 147 | 148 | TAILQ_FOREACH(log, &log_files, log_entry) { 149 | if (log->log_id == id) { 150 | DPRINTF("%s: received log fd %d, file %s", 151 | __func__, imsg->fd, log->log_name); 152 | log->log_fd = imsg->fd; 153 | return (0); 154 | } 155 | } 156 | 157 | return (-1); 158 | } 159 | 160 | int 161 | logger_open_priv(struct imsg *imsg) 162 | { 163 | char path[PATH_MAX]; 164 | char name[PATH_MAX], *p; 165 | uint32_t id; 166 | size_t len; 167 | int fd; 168 | 169 | /* called from the privileged process */ 170 | IMSG_SIZE_CHECK(imsg, &id); 171 | memcpy(&id, imsg->data, sizeof(id)); 172 | p = (char *)imsg->data + sizeof(id); 173 | 174 | if ((size_t)snprintf(name, sizeof(name), "/%s", p) >= sizeof(name)) 175 | return (-1); 176 | if ((len = strlcpy(path, httpd_env->sc_logdir, sizeof(path))) 177 | >= sizeof(path)) 178 | return (-1); 179 | 180 | p = path + len; 181 | len = sizeof(path) - len; 182 | 183 | if (canonicalize_path(name, p, len) == NULL) { 184 | log_warnx("invalid log name"); 185 | return (-1); 186 | } 187 | 188 | if ((fd = open(path, O_WRONLY|O_APPEND|O_CREAT, 0644)) == -1) { 189 | log_warn("failed to open %s", path); 190 | return (-1); 191 | } 192 | 193 | proc_compose_imsg(httpd_env->sc_ps, PROC_LOGGER, -1, 194 | IMSG_LOG_OPEN, -1, fd, &id, sizeof(id)); 195 | 196 | DPRINTF("%s: opened log file %s, fd %d", __func__, path, fd); 197 | 198 | return (0); 199 | } 200 | 201 | int 202 | logger_open(struct server *srv, struct server_config *srv_conf, void *arg) 203 | { 204 | struct log_file *log, *logfile = NULL, *errfile = NULL; 205 | 206 | if (srv_conf->flags & (SRVFLAG_SYSLOG | SRVFLAG_NO_LOG)) 207 | return (0); 208 | 209 | /* disassociate */ 210 | srv_conf->logaccess = srv_conf->logerror = NULL; 211 | 212 | TAILQ_FOREACH(log, &log_files, log_entry) { 213 | if (strcmp(log->log_name, srv_conf->accesslog) == 0) 214 | logfile = log; 215 | if (strcmp(log->log_name, srv_conf->errorlog) == 0) 216 | errfile = log; 217 | } 218 | 219 | if (logfile == NULL) { 220 | if ((srv_conf->logaccess = 221 | logger_open_file(srv_conf->accesslog)) == NULL) 222 | return (-1); 223 | } else 224 | srv_conf->logaccess = logfile; 225 | 226 | if (errfile == NULL) { 227 | if ((srv_conf->logerror = 228 | logger_open_file(srv_conf->errorlog)) == NULL) 229 | return (-1); 230 | } else 231 | srv_conf->logerror = errfile; 232 | 233 | return (0); 234 | } 235 | 236 | int 237 | logger_start(void) 238 | { 239 | logger_close(); 240 | if (server_foreach(logger_open, NULL) == -1) 241 | fatalx("failed to open log files"); 242 | return (0); 243 | } 244 | 245 | int 246 | logger_log(struct imsg *imsg) 247 | { 248 | char *logline; 249 | uint32_t id; 250 | struct server_config *srv_conf; 251 | struct log_file *log; 252 | 253 | IMSG_SIZE_CHECK(imsg, &id); 254 | memcpy(&id, imsg->data, sizeof(id)); 255 | 256 | if ((srv_conf = serverconfig_byid(id)) == NULL) 257 | fatalx("invalid logging requestr"); 258 | 259 | if (imsg->hdr.type == IMSG_LOG_ACCESS) 260 | log = srv_conf->logaccess; 261 | else 262 | log = srv_conf->logerror; 263 | 264 | if (log == NULL || log->log_fd == -1) { 265 | log_warnx("log file %s not opened", log ? log->log_name : ""); 266 | return (0); 267 | } 268 | 269 | /* XXX get_string() would sanitize the string, but add a malloc */ 270 | logline = (char *)imsg->data + sizeof(id); 271 | 272 | /* For debug output */ 273 | log_debug("%s", logline); 274 | 275 | if (dprintf(log->log_fd, "%s\n", logline) == -1) { 276 | if (logger_start() == -1) 277 | return (-1); 278 | } 279 | 280 | return (0); 281 | } 282 | 283 | int 284 | logger_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) 285 | { 286 | switch (imsg->hdr.type) { 287 | case IMSG_CFG_SERVER: 288 | config_getserver(httpd_env, imsg); 289 | break; 290 | case IMSG_CFG_DONE: 291 | config_getcfg(httpd_env, imsg); 292 | break; 293 | case IMSG_CTL_START: 294 | case IMSG_CTL_REOPEN: 295 | logger_start(); 296 | break; 297 | case IMSG_CTL_RESET: 298 | config_getreset(httpd_env, imsg); 299 | break; 300 | case IMSG_LOG_OPEN: 301 | return (logger_open_fd(imsg)); 302 | default: 303 | return (-1); 304 | } 305 | 306 | return (0); 307 | } 308 | 309 | int 310 | logger_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg) 311 | { 312 | switch (imsg->hdr.type) { 313 | case IMSG_LOG_ACCESS: 314 | case IMSG_LOG_ERROR: 315 | logger_log(imsg); 316 | break; 317 | default: 318 | return (-1); 319 | } 320 | 321 | return (0); 322 | } 323 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/patterns.7: -------------------------------------------------------------------------------- 1 | .\" $OpenBSD: patterns.7,v 1.8 2023/11/08 11:17:20 deraadt Exp $ 2 | .\" 3 | .\" Copyright (c) 2015 Reyk Floeter 4 | .\" Copyright (C) 1994-2015 Lua.org, PUC-Rio. 5 | .\" 6 | .\" Permission is hereby granted, free of charge, to any person obtaining 7 | .\" a copy of this software and associated documentation files (the 8 | .\" "Software"), to deal in the Software without restriction, including 9 | .\" without limitation the rights to use, copy, modify, merge, publish, 10 | .\" distribute, sublicense, and/or sell copies of the Software, and to 11 | .\" permit persons to whom the Software is furnished to do so, subject to 12 | .\" the following conditions: 13 | .\" 14 | .\" The above copyright notice and this permission notice shall be 15 | .\" included in all copies or substantial portions of the Software. 16 | .\" 17 | .\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | .\" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | .\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | .\" IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | .\" CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | .\" TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | .\" SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | .\" 25 | .\" Derived from section 6.4.1 in manual.html of Lua 5.3.1: 26 | .\" $Id: patterns.7,v 1.8 2023/11/08 11:17:20 deraadt Exp $ 27 | .\" 28 | .Dd $Mdocdate: November 8 2023 $ 29 | .Dt PATTERNS 7 30 | .Os 31 | .Sh NAME 32 | .Nm patterns 33 | .Nd Lua's pattern matching rules 34 | .Sh DESCRIPTION 35 | Pattern matching in 36 | .Xr httpd 8 37 | is based on the implementation of the Lua scripting language and 38 | provides a simple and fast alternative to the regular expressions (REs) that 39 | are described in 40 | .Xr re_format 7 . 41 | Patterns are described by regular strings, which are interpreted as 42 | patterns by the pattern-matching 43 | .Dq find 44 | and 45 | .Dq match 46 | functions. 47 | This document describes the syntax and the meaning (that is, what they 48 | match) of these strings. 49 | .Sh CHARACTER CLASS 50 | A character class is used to represent a set of characters. 51 | The following combinations are allowed in describing a character 52 | class: 53 | .Bl -tag -width Ds 54 | .It Ar x 55 | (where 56 | .Ar x 57 | is not one of the magic characters 58 | .Sq ^$()%.[]*+-? ) 59 | represents the character 60 | .Ar x 61 | itself. 62 | .It . 63 | (a dot) represents all characters. 64 | .It %a 65 | represents all letters. 66 | .It %c 67 | represents all control characters. 68 | .It %d 69 | represents all digits. 70 | .It %g 71 | represents all printable characters except space. 72 | .It %l 73 | represents all lowercase letters. 74 | .It %p 75 | represents all punctuation characters. 76 | .It %s 77 | represents all space characters. 78 | .It %u 79 | represents all uppercase letters. 80 | .It %w 81 | represents all alphanumeric characters. 82 | .It %x 83 | represents all hexadecimal digits. 84 | .It Pf % Ar x 85 | (where 86 | .Ar x 87 | is any non-alphanumeric character) represents the character 88 | .Ar x . 89 | This is the standard way to escape the magic characters. 90 | Any non-alphanumeric character (including all punctuation characters, 91 | even the non-magical) can be preceded by a 92 | .Sq % 93 | when used to represent itself in a pattern. 94 | .It Bq Ar set 95 | represents the class which is the union of all 96 | characters in 97 | .Ar set . 98 | A range of characters can be specified by separating the end 99 | characters of the range, in ascending order, with a 100 | .Sq - . 101 | All classes 102 | .Sq Ar %x 103 | described above can also be used as components in 104 | .Ar set . 105 | All other characters in 106 | .Ar set 107 | represent themselves. 108 | For example, 109 | .Sq [%w_] 110 | (or 111 | .Sq [_%w] ) 112 | represents all alphanumeric characters plus the underscore, 113 | .Sq [0-7] 114 | represents the octal digits, 115 | and 116 | .Sq [0-7%l%-] 117 | represents the octal digits plus the lowercase letters plus the 118 | .Sq - 119 | character. 120 | .Pp 121 | The interaction between ranges and classes is not defined. 122 | Therefore, patterns like 123 | .Sq [%a-z] 124 | or 125 | .Sq [a-%%] 126 | have no meaning. 127 | .It Bq Ar ^set 128 | represents the complement of 129 | .Ar set , 130 | where 131 | .Ar set 132 | is interpreted as above. 133 | .El 134 | .Pp 135 | For all classes represented by single letters ( 136 | .Sq %a , 137 | .Sq %c , 138 | etc.), 139 | the corresponding uppercase letter represents the complement of the class. 140 | For instance, 141 | .Sq %S 142 | represents all non-space characters. 143 | .Pp 144 | The definitions of letter, space, and other character groups depend on 145 | the current locale. 146 | In particular, the class 147 | .Sq [a-z] 148 | may not be equivalent to 149 | .Sq %l . 150 | .Sh PATTERN ITEM 151 | A pattern item can be 152 | .Bl -bullet 153 | .It 154 | a single character class, which matches any single character in the class; 155 | .It 156 | a single character class followed by 157 | .Sq * , 158 | which matches zero or more repetitions of characters in the class. 159 | These repetition items will always match the longest possible sequence; 160 | .It 161 | a single character class followed by 162 | .Sq + , 163 | which matches one or more repetitions of characters in the class. 164 | These repetition items will always match the longest possible sequence; 165 | .It 166 | a single character class followed by 167 | .Sq - , 168 | which also matches zero or more repetitions of characters in the class. 169 | Unlike 170 | .Sq * , 171 | these repetition items will always match the shortest possible sequence; 172 | .It 173 | a single character class followed by 174 | .Sq \&? , 175 | which matches zero or one occurrence of a character in the class. 176 | It always matches one occurrence if possible; 177 | .It 178 | .Sq Pf % Ar n , 179 | for 180 | .Ar n 181 | between 1 and 9; 182 | such item matches a substring equal to the n-th captured string (see below); 183 | .It 184 | .Sq Pf %b Ar xy , 185 | where 186 | .Ar x 187 | and 188 | .Ar y 189 | are two distinct characters; 190 | such item matches strings that start with 191 | .Ar x , 192 | end with 193 | .Ar y , 194 | and where the 195 | .Ar x 196 | and 197 | .Ar y 198 | are 199 | .Em balanced . 200 | This means that if one reads the string from left to right, counting 201 | .Em +1 202 | for an 203 | .Ar x 204 | and 205 | .Em -1 206 | for a 207 | .Ar y , 208 | the ending 209 | .Ar y 210 | is the first 211 | .Ar y 212 | where the count reaches 0. 213 | For instance, the item 214 | .Sq %b() 215 | matches expressions with balanced parentheses. 216 | .It 217 | .Sq Pf %f Bq Ar set , 218 | a 219 | .Em frontier pattern ; 220 | such item matches an empty string at any position such that the next 221 | character belongs to 222 | .Ar set 223 | and the previous character does not belong to 224 | .Ar set . 225 | The set 226 | .Ar set 227 | is interpreted as previously described. 228 | The beginning and the end of the subject are handled as if 229 | they were the character 230 | .Sq \e0 . 231 | .El 232 | .Sh PATTERN 233 | A pattern is a sequence of pattern items. 234 | A caret 235 | .Sq ^ 236 | at the beginning of a pattern anchors the match at the beginning of 237 | the subject string. 238 | A 239 | .Sq $ 240 | at the end of a pattern anchors the match at the end of the subject string. 241 | At other positions, 242 | .Sq ^ 243 | and 244 | .Sq $ 245 | have no special meaning and represent themselves. 246 | .Sh CAPTURES 247 | A pattern can contain sub-patterns enclosed in parentheses; they 248 | describe captures. 249 | When a match succeeds, the substrings of the subject string that match 250 | captures are stored (captured) for future use. 251 | Captures are numbered according to their left parentheses. 252 | For instance, in the pattern 253 | .Qq (a*(.)%w(%s*)) , 254 | the part of the string matching 255 | .Qq a*(.)%w(%s*) 256 | is stored as the first capture (and therefore has number 1); 257 | the character matching 258 | .Qq \&. 259 | is captured with number 2, 260 | and the part matching 261 | .Qq %s* 262 | has number 3. 263 | .Pp 264 | As a special case, the empty capture 265 | .Sq () 266 | captures the current string position (a number). 267 | For instance, if we apply the pattern 268 | .Qq ()aa() 269 | on the string 270 | .Qq flaaap , 271 | there will be two captures: 2 and 4. 272 | .Sh SEE ALSO 273 | .Xr fnmatch 3 , 274 | .Xr re_format 7 , 275 | .Xr httpd 8 276 | .Rs 277 | .%A Roberto Ierusalimschy 278 | .%A Luiz Henrique de Figueiredo 279 | .%A Waldemar Celes 280 | .%Q Lua.org 281 | .%Q PUC-Rio 282 | .%D June 2015 283 | .%R Lua 5.3 Reference Manual 284 | .%T Patterns 285 | .%U https://www.lua.org/manual/5.3/manual.html#6.4.1 286 | .Re 287 | .Sh HISTORY 288 | The first implementation of the pattern rules were introduced with Lua 2.5. 289 | Almost twenty years later, 290 | an implementation based on Lua 5.3.1 appeared in 291 | .Ox 5.8 . 292 | .Sh AUTHORS 293 | The pattern matching is derived from the original implementation of 294 | the Lua scripting language written by 295 | .An -nosplit 296 | .An Roberto Ierusalimschy , 297 | .An Waldemar Celes , 298 | and 299 | .An Luiz Henrique de Figueiredo 300 | at PUC-Rio. 301 | It was turned into a native C API for 302 | .Xr httpd 8 303 | by 304 | .An Reyk Floeter Aq Mt reyk@openbsd.org . 305 | .Sh CAVEATS 306 | A notable difference with the Lua implementation is the position in the string 307 | returned by captures. 308 | It follows the C-style indexing (position starting from 0) 309 | instead of Lua-style indexing (position starting from 1). 310 | -------------------------------------------------------------------------------- /src/lib/libc/crypt/bcrypt.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: bcrypt.c,v 1.58 2020/07/06 13:33:05 pirofti Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2014 Ted Unangst 5 | * Copyright (c) 1997 Niels Provos 6 | * 7 | * Permission to use, copy, modify, and distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | /* This password hashing algorithm was designed by David Mazieres 20 | * and works as follows: 21 | * 22 | * 1. state := InitState () 23 | * 2. state := ExpandKey (state, salt, password) 24 | * 3. REPEAT rounds: 25 | * state := ExpandKey (state, 0, password) 26 | * state := ExpandKey (state, 0, salt) 27 | * 4. ctext := "OrpheanBeholderScryDoubt" 28 | * 5. REPEAT 64: 29 | * ctext := Encrypt_ECB (state, ctext); 30 | * 6. RETURN Concatenate (salt, ctext); 31 | * 32 | */ 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #ifndef __OpenBSD__ 45 | #include 46 | #include "compat.h" 47 | #endif 48 | 49 | /* This implementation is adaptable to current computing power. 50 | * You can have up to 2^31 rounds which should be enough for some 51 | * time to come. 52 | */ 53 | 54 | #define BCRYPT_VERSION '2' 55 | #define BCRYPT_MAXSALT 16 /* Precomputation is just so nice */ 56 | #define BCRYPT_WORDS 6 /* Ciphertext words */ 57 | #define BCRYPT_MINLOGROUNDS 4 /* we have log2(rounds) in salt */ 58 | 59 | #define BCRYPT_SALTSPACE (7 + (BCRYPT_MAXSALT * 4 + 2) / 3 + 1) 60 | #define BCRYPT_HASHSPACE 61 61 | 62 | char *bcrypt_gensalt(u_int8_t); 63 | 64 | static int encode_base64(char *, const u_int8_t *, size_t); 65 | static int decode_base64(u_int8_t *, size_t, const char *); 66 | 67 | /* 68 | * Generates a salt for this version of crypt. 69 | */ 70 | static int 71 | bcrypt_initsalt(int log_rounds, uint8_t *salt, size_t saltbuflen) 72 | { 73 | uint8_t csalt[BCRYPT_MAXSALT]; 74 | 75 | if (saltbuflen < BCRYPT_SALTSPACE) { 76 | errno = EINVAL; 77 | return -1; 78 | } 79 | 80 | arc4random_buf(csalt, sizeof(csalt)); 81 | 82 | if (log_rounds < 4) 83 | log_rounds = 4; 84 | else if (log_rounds > 31) 85 | log_rounds = 31; 86 | 87 | snprintf(salt, saltbuflen, "$2b$%2.2u$", log_rounds); 88 | encode_base64(salt + 7, csalt, sizeof(csalt)); 89 | 90 | return 0; 91 | } 92 | 93 | /* 94 | * the core bcrypt function 95 | */ 96 | static int 97 | bcrypt_hashpass(const char *key, const char *salt, char *encrypted, 98 | size_t encryptedlen) 99 | { 100 | blf_ctx state; 101 | u_int32_t rounds, i, k; 102 | u_int16_t j; 103 | size_t key_len; 104 | u_int8_t salt_len, logr, minor; 105 | u_int8_t ciphertext[4 * BCRYPT_WORDS] = "OrpheanBeholderScryDoubt"; 106 | u_int8_t csalt[BCRYPT_MAXSALT]; 107 | u_int32_t cdata[BCRYPT_WORDS]; 108 | 109 | if (encryptedlen < BCRYPT_HASHSPACE) 110 | goto inval; 111 | 112 | /* Check and discard "$" identifier */ 113 | if (salt[0] != '$') 114 | goto inval; 115 | salt += 1; 116 | 117 | if (salt[0] != BCRYPT_VERSION) 118 | goto inval; 119 | 120 | /* Check for minor versions */ 121 | switch ((minor = salt[1])) { 122 | case 'a': 123 | key_len = (u_int8_t)(strlen(key) + 1); 124 | break; 125 | case 'b': 126 | /* strlen() returns a size_t, but the function calls 127 | * below result in implicit casts to a narrower integer 128 | * type, so cap key_len at the actual maximum supported 129 | * length here to avoid integer wraparound */ 130 | key_len = strlen(key); 131 | if (key_len > 72) 132 | key_len = 72; 133 | key_len++; /* include the NUL */ 134 | break; 135 | default: 136 | goto inval; 137 | } 138 | if (salt[2] != '$') 139 | goto inval; 140 | /* Discard version + "$" identifier */ 141 | salt += 3; 142 | 143 | /* Check and parse num rounds */ 144 | if (!isdigit((unsigned char)salt[0]) || 145 | !isdigit((unsigned char)salt[1]) || salt[2] != '$') 146 | goto inval; 147 | logr = (salt[1] - '0') + ((salt[0] - '0') * 10); 148 | if (logr < BCRYPT_MINLOGROUNDS || logr > 31) 149 | goto inval; 150 | /* Computer power doesn't increase linearly, 2^x should be fine */ 151 | rounds = 1U << logr; 152 | 153 | /* Discard num rounds + "$" identifier */ 154 | salt += 3; 155 | 156 | if (strlen(salt) * 3 / 4 < BCRYPT_MAXSALT) 157 | goto inval; 158 | 159 | /* We dont want the base64 salt but the raw data */ 160 | if (decode_base64(csalt, BCRYPT_MAXSALT, salt)) 161 | goto inval; 162 | salt_len = BCRYPT_MAXSALT; 163 | 164 | /* Setting up S-Boxes and Subkeys */ 165 | Blowfish_initstate(&state); 166 | Blowfish_expandstate(&state, csalt, salt_len, 167 | (u_int8_t *) key, key_len); 168 | for (k = 0; k < rounds; k++) { 169 | Blowfish_expand0state(&state, (u_int8_t *) key, key_len); 170 | Blowfish_expand0state(&state, csalt, salt_len); 171 | } 172 | 173 | /* This can be precomputed later */ 174 | j = 0; 175 | for (i = 0; i < BCRYPT_WORDS; i++) 176 | cdata[i] = Blowfish_stream2word(ciphertext, 4 * BCRYPT_WORDS, &j); 177 | 178 | /* Now do the encryption */ 179 | for (k = 0; k < 64; k++) 180 | blf_enc(&state, cdata, BCRYPT_WORDS / 2); 181 | 182 | for (i = 0; i < BCRYPT_WORDS; i++) { 183 | ciphertext[4 * i + 3] = cdata[i] & 0xff; 184 | cdata[i] = cdata[i] >> 8; 185 | ciphertext[4 * i + 2] = cdata[i] & 0xff; 186 | cdata[i] = cdata[i] >> 8; 187 | ciphertext[4 * i + 1] = cdata[i] & 0xff; 188 | cdata[i] = cdata[i] >> 8; 189 | ciphertext[4 * i + 0] = cdata[i] & 0xff; 190 | } 191 | 192 | 193 | snprintf(encrypted, 8, "$2%c$%2.2u$", minor, logr); 194 | encode_base64(encrypted + 7, csalt, BCRYPT_MAXSALT); 195 | encode_base64(encrypted + 7 + 22, ciphertext, 4 * BCRYPT_WORDS - 1); 196 | explicit_bzero(&state, sizeof(state)); 197 | explicit_bzero(ciphertext, sizeof(ciphertext)); 198 | explicit_bzero(csalt, sizeof(csalt)); 199 | explicit_bzero(cdata, sizeof(cdata)); 200 | return 0; 201 | 202 | inval: 203 | errno = EINVAL; 204 | return -1; 205 | } 206 | 207 | /* 208 | * user friendly functions 209 | */ 210 | int 211 | bcrypt_newhash(const char *pass, int log_rounds, char *hash, size_t hashlen) 212 | { 213 | char salt[BCRYPT_SALTSPACE]; 214 | 215 | if (bcrypt_initsalt(log_rounds, salt, sizeof(salt)) != 0) 216 | return -1; 217 | 218 | if (bcrypt_hashpass(pass, salt, hash, hashlen) != 0) 219 | return -1; 220 | 221 | explicit_bzero(salt, sizeof(salt)); 222 | return 0; 223 | } 224 | #ifdef __OpenBSD__ 225 | DEF_WEAK(bcrypt_newhash); 226 | #endif 227 | 228 | int 229 | bcrypt_checkpass(const char *pass, const char *goodhash) 230 | { 231 | char hash[BCRYPT_HASHSPACE]; 232 | 233 | if (bcrypt_hashpass(pass, goodhash, hash, sizeof(hash)) != 0) 234 | return -1; 235 | if (strlen(hash) != strlen(goodhash) || 236 | timingsafe_bcmp(hash, goodhash, strlen(goodhash)) != 0) { 237 | errno = EACCES; 238 | return -1; 239 | } 240 | 241 | explicit_bzero(hash, sizeof(hash)); 242 | return 0; 243 | } 244 | #ifdef __OpenBSD__ 245 | DEF_WEAK(bcrypt_checkpass); 246 | #endif 247 | 248 | /* 249 | * Measure this system's performance by measuring the time for 8 rounds. 250 | * We are aiming for something that takes around 0.1s, but not too much over. 251 | */ 252 | int 253 | _bcrypt_autorounds(void) 254 | { 255 | struct timespec before, after; 256 | int r = 8; 257 | char buf[_PASSWORD_LEN]; 258 | int duration; 259 | 260 | #ifdef __OpenBSD__ 261 | WRAP(clock_gettime)(CLOCK_THREAD_CPUTIME_ID, &before); 262 | #else 263 | clock_gettime(CLOCK_THREAD_CPUTIME_ID, &before); 264 | #endif 265 | bcrypt_newhash("testpassword", r, buf, sizeof(buf)); 266 | #ifdef __OpenBSD__ 267 | WRAP(clock_gettime)(CLOCK_THREAD_CPUTIME_ID, &after); 268 | #else 269 | clock_gettime(CLOCK_THREAD_CPUTIME_ID, &after); 270 | #endif 271 | 272 | duration = after.tv_sec - before.tv_sec; 273 | duration *= 1000000; 274 | duration += (after.tv_nsec - before.tv_nsec) / 1000; 275 | 276 | /* too quick? slow it down. */ 277 | while (r < 16 && duration <= 60000) { 278 | r += 1; 279 | duration *= 2; 280 | } 281 | /* too slow? speed it up. */ 282 | while (r > 6 && duration > 120000) { 283 | r -= 1; 284 | duration /= 2; 285 | } 286 | 287 | return r; 288 | } 289 | 290 | /* 291 | * internal utilities 292 | */ 293 | static const u_int8_t Base64Code[] = 294 | "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 295 | 296 | static const u_int8_t index_64[128] = { 297 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 298 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 299 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 300 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 301 | 255, 255, 255, 255, 255, 255, 0, 1, 54, 55, 302 | 56, 57, 58, 59, 60, 61, 62, 63, 255, 255, 303 | 255, 255, 255, 255, 255, 2, 3, 4, 5, 6, 304 | 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 305 | 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 306 | 255, 255, 255, 255, 255, 255, 28, 29, 30, 307 | 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 308 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 309 | 51, 52, 53, 255, 255, 255, 255, 255 310 | }; 311 | #define CHAR64(c) ( (c) > 127 ? 255 : index_64[(c)]) 312 | 313 | /* 314 | * read buflen (after decoding) bytes of data from b64data 315 | */ 316 | static int 317 | decode_base64(u_int8_t *buffer, size_t len, const char *b64data) 318 | { 319 | u_int8_t *bp = buffer; 320 | const u_int8_t *p = b64data; 321 | u_int8_t c1, c2, c3, c4; 322 | 323 | while (bp < buffer + len) { 324 | c1 = CHAR64(*p); 325 | /* Invalid data */ 326 | if (c1 == 255) 327 | return -1; 328 | 329 | c2 = CHAR64(*(p + 1)); 330 | if (c2 == 255) 331 | return -1; 332 | 333 | *bp++ = (c1 << 2) | ((c2 & 0x30) >> 4); 334 | if (bp >= buffer + len) 335 | break; 336 | 337 | c3 = CHAR64(*(p + 2)); 338 | if (c3 == 255) 339 | return -1; 340 | 341 | *bp++ = ((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2); 342 | if (bp >= buffer + len) 343 | break; 344 | 345 | c4 = CHAR64(*(p + 3)); 346 | if (c4 == 255) 347 | return -1; 348 | *bp++ = ((c3 & 0x03) << 6) | c4; 349 | 350 | p += 4; 351 | } 352 | return 0; 353 | } 354 | 355 | /* 356 | * Turn len bytes of data into base64 encoded data. 357 | * This works without = padding. 358 | */ 359 | static int 360 | encode_base64(char *b64buffer, const u_int8_t *data, size_t len) 361 | { 362 | u_int8_t *bp = b64buffer; 363 | const u_int8_t *p = data; 364 | u_int8_t c1, c2; 365 | 366 | while (p < data + len) { 367 | c1 = *p++; 368 | *bp++ = Base64Code[(c1 >> 2)]; 369 | c1 = (c1 & 0x03) << 4; 370 | if (p >= data + len) { 371 | *bp++ = Base64Code[c1]; 372 | break; 373 | } 374 | c2 = *p++; 375 | c1 |= (c2 >> 4) & 0x0f; 376 | *bp++ = Base64Code[c1]; 377 | c1 = (c2 & 0x0f) << 2; 378 | if (p >= data + len) { 379 | *bp++ = Base64Code[c1]; 380 | break; 381 | } 382 | c2 = *p++; 383 | c1 |= (c2 >> 6) & 0x03; 384 | *bp++ = Base64Code[c1]; 385 | *bp++ = Base64Code[c2 & 0x3f]; 386 | } 387 | *bp = '\0'; 388 | return 0; 389 | } 390 | 391 | /* 392 | * classic interface 393 | */ 394 | char * 395 | bcrypt_gensalt(u_int8_t log_rounds) 396 | { 397 | static char gsalt[BCRYPT_SALTSPACE]; 398 | 399 | bcrypt_initsalt(log_rounds, gsalt, sizeof(gsalt)); 400 | 401 | return gsalt; 402 | } 403 | 404 | char * 405 | bcrypt(const char *pass, const char *salt) 406 | { 407 | static char gencrypted[BCRYPT_HASHSPACE]; 408 | 409 | if (bcrypt_hashpass(pass, salt, gencrypted, sizeof(gencrypted)) != 0) 410 | return NULL; 411 | 412 | return gencrypted; 413 | } 414 | #ifdef __OpenBSD__ 415 | DEF_WEAK(bcrypt); 416 | #endif 417 | -------------------------------------------------------------------------------- /src/regress/usr.sbin/httpd/tests/funcs.pl: -------------------------------------------------------------------------------- 1 | # $OpenBSD: funcs.pl,v 1.9 2021/12/22 15:54:01 bluhm Exp $ 2 | 3 | # Copyright (c) 2010-2021 Alexander Bluhm 4 | # 5 | # Permission to use, copy, modify, and distribute this software for any 6 | # purpose with or without fee is hereby granted, provided that the above 7 | # copyright notice and this permission notice appear in all copies. 8 | # 9 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | use strict; 18 | use warnings; 19 | no warnings 'experimental::smartmatch'; 20 | use feature 'switch'; 21 | use Errno; 22 | use Digest::MD5; 23 | use Socket; 24 | use Socket6; 25 | use IO::Socket; 26 | 27 | sub find_ports { 28 | my %args = @_; 29 | my $num = delete $args{num} // 1; 30 | my $domain = delete $args{domain} // AF_INET; 31 | my $addr = delete $args{addr} // "127.0.0.1"; 32 | 33 | my @sockets = (1..$num); 34 | foreach my $s (@sockets) { 35 | $s = IO::Socket::IP->new( 36 | Proto => "tcp", 37 | Domain => $domain, 38 | $addr ? (LocalAddr => $addr) : (), 39 | ) or die "find_ports: create and bind socket failed: $!"; 40 | } 41 | my @ports = map { $_->sockport() } @sockets; 42 | 43 | return @ports; 44 | } 45 | 46 | sub path_md5 { 47 | my $name = shift; 48 | my $val = `cat md5-$name`; 49 | } 50 | 51 | ######################################################################## 52 | # Client funcs 53 | ######################################################################## 54 | 55 | sub write_char { 56 | my $self = shift; 57 | my $len = shift // $self->{len} // 512; 58 | my $sleep = $self->{sleep}; 59 | 60 | my $ctx = Digest::MD5->new(); 61 | my $char = '0'; 62 | for (my $i = 1; $i < $len; $i++) { 63 | $ctx->add($char); 64 | print $char 65 | or die ref($self), " print failed: $!"; 66 | given ($char) { 67 | when(/9/) { $char = 'A' } 68 | when(/Z/) { $char = 'a' } 69 | when(/z/) { $char = "\n" } 70 | when(/\n/) { print STDERR "."; $char = '0' } 71 | default { $char++ } 72 | } 73 | if ($self->{sleep}) { 74 | IO::Handle::flush(\*STDOUT); 75 | sleep $self->{sleep}; 76 | } 77 | } 78 | if ($len) { 79 | $char = "\n"; 80 | $ctx->add($char); 81 | print $char 82 | or die ref($self), " print failed: $!"; 83 | print STDERR ".\n"; 84 | } 85 | IO::Handle::flush(\*STDOUT); 86 | 87 | print STDERR "LEN: ", $len, "\n"; 88 | print STDERR "MD5: ", $ctx->hexdigest, "\n"; 89 | } 90 | 91 | sub http_client { 92 | my $self = shift; 93 | 94 | unless ($self->{lengths}) { 95 | # only a single http request 96 | my $len = shift // $self->{len} // 512; 97 | my $cookie = $self->{cookie}; 98 | http_request($self, $len, "1.0", $cookie); 99 | http_response($self, $len); 100 | return; 101 | } 102 | 103 | $self->{http_vers} ||= ["1.1", "1.0"]; 104 | my $vers = $self->{http_vers}[0]; 105 | my @lengths = @{$self->{redo}{lengths} || $self->{lengths}}; 106 | my @cookies = @{$self->{redo}{cookies} || $self->{cookies} || []}; 107 | while (defined (my $len = shift @lengths)) { 108 | my $cookie = shift @cookies || $self->{cookie}; 109 | eval { 110 | http_request($self, $len, $vers, $cookie); 111 | http_response($self, $len); 112 | }; 113 | warn $@ if $@; 114 | if (@lengths && ($@ || $vers eq "1.0")) { 115 | # reconnect and redo the outstanding requests 116 | $self->{redo} = { 117 | lengths => \@lengths, 118 | cookies => \@cookies, 119 | }; 120 | return; 121 | } 122 | } 123 | delete $self->{redo}; 124 | shift @{$self->{http_vers}}; 125 | if (@{$self->{http_vers}}) { 126 | # run the tests again with other persistence 127 | $self->{redo} = { 128 | lengths => [@{$self->{lengths}}], 129 | cookies => [@{$self->{cookies} || []}], 130 | }; 131 | } 132 | } 133 | 134 | sub http_request { 135 | my ($self, $len, $vers, $cookie) = @_; 136 | my $method = $self->{method} || "GET"; 137 | my %header = %{$self->{header} || {}}; 138 | 139 | # encode the requested length or chunks into the url 140 | my $path = ref($len) eq 'ARRAY' ? join("/", @$len) : $len; 141 | # overwrite path with custom path 142 | if (defined($self->{path})) { 143 | $path = $self->{path}; 144 | } 145 | my @request = ("$method /$path HTTP/$vers"); 146 | push @request, "Host: foo.bar" unless defined $header{Host}; 147 | if ($vers eq "1.1" && $method eq "PUT") { 148 | if (ref($len) eq 'ARRAY') { 149 | push @request, "Transfer-Encoding: chunked" 150 | if !defined $header{'Transfer-Encoding'}; 151 | } else { 152 | push @request, "Content-Length: $len" 153 | if !defined $header{'Content-Length'}; 154 | } 155 | } 156 | foreach my $key (sort keys %header) { 157 | my $val = $header{$key}; 158 | if (ref($val) eq 'ARRAY') { 159 | push @request, "$key: $_" 160 | foreach @{$val}; 161 | } else { 162 | push @request, "$key: $val"; 163 | } 164 | } 165 | push @request, "Cookie: $cookie" if $cookie; 166 | push @request, ""; 167 | print STDERR map { ">>> $_\n" } @request; 168 | print map { "$_\r\n" } @request; 169 | if ($method eq "PUT") { 170 | if (ref($len) eq 'ARRAY') { 171 | if ($vers eq "1.1") { 172 | write_chunked($self, @$len); 173 | } else { 174 | write_char($self, $_) foreach (@$len); 175 | } 176 | } else { 177 | write_char($self, $len); 178 | } 179 | } 180 | IO::Handle::flush(\*STDOUT); 181 | # XXX client shutdown seems to be broken in httpd 182 | #shutdown(\*STDOUT, SHUT_WR) 183 | # or die ref($self), " shutdown write failed: $!" 184 | # if $vers ne "1.1"; 185 | } 186 | 187 | sub http_response { 188 | my ($self, $len) = @_; 189 | my $method = $self->{method} || "GET"; 190 | my $code = $self->{code} || "200 OK"; 191 | 192 | my $vers; 193 | my $chunked = 0; 194 | my $multipart = 0; 195 | my $boundary; 196 | { 197 | local $/ = "\r\n"; 198 | local $_ = ; 199 | defined 200 | or die ref($self), " missing http $len response"; 201 | chomp; 202 | print STDERR "<<< $_\n"; 203 | m{^HTTP/(\d\.\d) $code$} 204 | or die ref($self), " http response not $code" 205 | unless $self->{httpnok}; 206 | $vers = $1; 207 | while () { 208 | chomp; 209 | print STDERR "<<< $_\n"; 210 | last if /^$/; 211 | if (/^Content-Length: (.*)/) { 212 | if ($self->{httpnok} or $self->{multipart}) { 213 | $len = $1; 214 | } else { 215 | $1 == $len or die ref($self), 216 | " bad content length $1"; 217 | } 218 | } 219 | if (/^Transfer-Encoding: chunked$/) { 220 | $chunked = 1; 221 | } 222 | if (/^Content-Type: multipart\/byteranges; boundary=(.*)$/) { 223 | $multipart = 1; 224 | $boundary = $1; 225 | } 226 | } 227 | } 228 | die ref($self), " no multipart response" 229 | if ($self->{multipart} && $multipart == 0); 230 | 231 | if ($multipart) { 232 | read_multipart($self, $boundary); 233 | } elsif ($chunked) { 234 | read_chunked($self); 235 | } else { 236 | read_char($self, $len) 237 | if $method eq "GET"; 238 | } 239 | } 240 | 241 | sub read_chunked { 242 | my $self = shift; 243 | 244 | for (;;) { 245 | my $len; 246 | { 247 | local $/ = "\r\n"; 248 | local $_ = ; 249 | defined or die ref($self), " missing chunk size"; 250 | chomp; 251 | print STDERR "<<< $_\n"; 252 | /^[[:xdigit:]]+$/ 253 | or die ref($self), " chunk size not hex: $_"; 254 | $len = hex; 255 | } 256 | last unless $len > 0; 257 | read_char($self, $len); 258 | { 259 | local $/ = "\r\n"; 260 | local $_ = ; 261 | defined or die ref($self), " missing chunk data end"; 262 | chomp; 263 | print STDERR "<<< $_\n"; 264 | /^$/ or die ref($self), " no chunk data end: $_"; 265 | } 266 | } 267 | { 268 | local $/ = "\r\n"; 269 | while () { 270 | chomp; 271 | print STDERR "<<< $_\n"; 272 | last if /^$/; 273 | } 274 | defined or die ref($self), " missing chunk trailer"; 275 | } 276 | } 277 | 278 | sub read_multipart { 279 | my $self = shift; 280 | my $boundary = shift; 281 | my $ctx = Digest::MD5->new(); 282 | my $len = 0; 283 | 284 | for (;;) { 285 | my $part = 0; 286 | { 287 | local $/ = "\r\n"; 288 | local $_ = ; 289 | local $_ = ; 290 | defined or die ref($self), " missing boundary"; 291 | chomp; 292 | print STDERR "<<< $_\n"; 293 | /^--$boundary(--)?$/ 294 | or die ref($self), " boundary not found: $_"; 295 | if (not $1) { 296 | while () { 297 | chomp; 298 | if (/^Content-Length: (.*)/) { 299 | $part = $1; 300 | } 301 | if (/^Content-Range: bytes (\d+)-(\d+)\/(\d+)$/) { 302 | $part = $2 - $1 + 1; 303 | } 304 | print STDERR "<<< $_\n"; 305 | last if /^$/; 306 | } 307 | } 308 | } 309 | last unless $part > 0; 310 | 311 | $len += read_part($self, $ctx, $part); 312 | } 313 | 314 | print STDERR "LEN: ", $len, "\n"; 315 | print STDERR "MD5: ", $ctx->hexdigest, "\n"; 316 | 317 | } 318 | 319 | sub errignore { 320 | $SIG{PIPE} = 'IGNORE'; 321 | $SIG{__DIE__} = sub { 322 | die @_ if $^S; 323 | warn "Error ignored"; 324 | warn @_; 325 | IO::Handle::flush(\*STDERR); 326 | POSIX::_exit(0); 327 | }; 328 | } 329 | 330 | ######################################################################## 331 | # Common funcs 332 | ######################################################################## 333 | 334 | sub read_char { 335 | my $self = shift; 336 | my $max = shift // $self->{max}; 337 | 338 | my $ctx = Digest::MD5->new(); 339 | my $len = read_part($self, $ctx, $max); 340 | 341 | print STDERR "LEN: ", $len, "\n"; 342 | print STDERR "MD5: ", $ctx->hexdigest, "\n"; 343 | } 344 | 345 | sub read_part { 346 | my $self = shift; 347 | my ($ctx, $max) = @_; 348 | 349 | my $opct = 0; 350 | my $len = 0; 351 | for (;;) { 352 | if (defined($max) && $len >= $max) { 353 | print STDERR "Max\n"; 354 | last; 355 | } 356 | my $rlen = POSIX::BUFSIZ; 357 | if (defined($max) && $rlen > $max - $len) { 358 | $rlen = $max - $len; 359 | } 360 | defined(my $n = read(STDIN, my $buf, $rlen)) 361 | or die ref($self), " read failed: $!"; 362 | $n or last; 363 | $len += $n; 364 | $ctx->add($buf); 365 | my $pct = ($len / $max) * 100.0; 366 | if ($pct >= $opct + 1) { 367 | printf(STDERR "%.2f%% $len/$max\n", $pct); 368 | $opct = $pct; 369 | } 370 | } 371 | return $len; 372 | } 373 | 374 | sub write_chunked { 375 | my $self = shift; 376 | my @chunks = @_; 377 | 378 | foreach my $len (@chunks) { 379 | printf STDERR ">>> %x\n", $len; 380 | printf "%x\r\n", $len; 381 | write_char($self, $len); 382 | printf STDERR ">>> \n"; 383 | print "\r\n"; 384 | } 385 | my @trailer = ("0", "X-Chunk-Trailer: @chunks", ""); 386 | print STDERR map { ">>> $_\n" } @trailer; 387 | print map { "$_\r\n" } @trailer; 388 | } 389 | 390 | ######################################################################## 391 | # Script funcs 392 | ######################################################################## 393 | 394 | sub check_logs { 395 | my ($c, $r, %args) = @_; 396 | 397 | return if $args{nocheck}; 398 | 399 | check_len($c, $r, %args); 400 | check_md5($c, $r, %args); 401 | check_loggrep($c, $r, %args); 402 | $r->loggrep("lost child") 403 | and die "httpd lost child"; 404 | } 405 | 406 | sub check_len { 407 | my ($c, $r, %args) = @_; 408 | 409 | $args{len} ||= 512 unless $args{lengths}; 410 | 411 | my @clen; 412 | @clen = $c->loggrep(qr/^LEN: /) or die "no client len" 413 | unless $args{client}{nocheck}; 414 | # !@clen 415 | # or die "client: @clen", "len mismatch"; 416 | !defined($args{len}) || !$clen[0] || $clen[0] eq "LEN: $args{len}\n" 417 | or die "client: $clen[0]", "len $args{len} expected"; 418 | my @lengths = map { ref eq 'ARRAY' ? @$_ : $_ } 419 | @{$args{lengths} || []}; 420 | foreach my $len (@lengths) { 421 | unless ($args{client}{nocheck}) { 422 | my $clen = shift @clen; 423 | $clen eq "LEN: $len\n" 424 | or die "client: $clen", "len $len expected"; 425 | } 426 | } 427 | } 428 | 429 | sub check_md5 { 430 | my ($c, $r, %args) = @_; 431 | 432 | my @cmd5; 433 | @cmd5 = $c->loggrep(qr/^MD5: /) unless $args{client}{nocheck}; 434 | my @md5 = ref($args{md5}) eq 'ARRAY' ? @{$args{md5}} : $args{md5} || () 435 | or return; 436 | foreach my $md5 (@md5) { 437 | unless ($args{client}{nocheck}) { 438 | my $cmd5 = shift @cmd5 439 | or die "too few md5 in client log"; 440 | $cmd5 =~ /^MD5: ($md5)$/ 441 | or die "client: $cmd5", "md5 $md5 expected"; 442 | } 443 | } 444 | @cmd5 && ref($args{md5}) eq 'ARRAY' 445 | and die "too many md5 in client log"; 446 | } 447 | 448 | sub check_loggrep { 449 | my ($c, $r, %args) = @_; 450 | 451 | my %name2proc = (client => $c, httpd => $r); 452 | foreach my $name (qw(client httpd)) { 453 | my $p = $name2proc{$name} or next; 454 | my $pattern = $args{$name}{loggrep} or next; 455 | $pattern = [ $pattern ] unless ref($pattern) eq 'ARRAY'; 456 | foreach my $pat (@$pattern) { 457 | if (ref($pat) eq 'HASH') { 458 | while (my($re, $num) = each %$pat) { 459 | my @matches = $p->loggrep($re); 460 | @matches == $num 461 | or die "$name matches '@matches': ", 462 | "'$re' => $num"; 463 | } 464 | } else { 465 | $p->loggrep($pat) 466 | or die "$name log missing pattern: '$pat'"; 467 | } 468 | } 469 | } 470 | } 471 | 472 | 1; 473 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/patterns.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: patterns.c,v 1.5 2016/02/14 18:20:59 semarie Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2015 Reyk Floeter 5 | * Copyright (C) 1994-2015 Lua.org, PUC-Rio. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining 8 | * a copy of this software and associated documentation files (the 9 | * "Software"), to deal in the Software without restriction, including 10 | * without limitation the rights to use, copy, modify, merge, publish, 11 | * distribute, sublicense, and/or sell copies of the Software, and to 12 | * permit persons to whom the Software is furnished to do so, subject to 13 | * the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be 16 | * included in all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | /* 28 | * Derived from Lua 5.3.1: 29 | * $Id: patterns.c,v 1.5 2016/02/14 18:20:59 semarie Exp $ 30 | * Standard library for string operations and pattern-matching 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include "patterns.h" 41 | 42 | #define uchar(c) ((unsigned char)(c)) /* macro to 'unsign' a char */ 43 | #define CAP_UNFINISHED (-1) 44 | #define CAP_POSITION (-2) 45 | #define L_ESC '%' 46 | #define SPECIALS "^$*+?.([%-" 47 | 48 | struct match_state { 49 | int matchdepth; /* control for recursive depth (to avoid C 50 | * stack overflow) */ 51 | int repetitioncounter; /* control the repetition items */ 52 | int maxcaptures; /* configured capture limit */ 53 | const char *src_init; /* init of source string */ 54 | const char *src_end; /* end ('\0') of source string */ 55 | const char *p_end; /* end ('\0') of pattern */ 56 | const char *error; /* should be NULL */ 57 | int level; /* total number of captures (finished or 58 | * unfinished) */ 59 | struct { 60 | const char *init; 61 | ptrdiff_t len; 62 | } capture[MAXCAPTURES]; 63 | }; 64 | 65 | /* recursive function */ 66 | static const char *match(struct match_state *, const char *, const char *); 67 | 68 | static int 69 | match_error(struct match_state *ms, const char *error) 70 | { 71 | ms->error = ms->error == NULL ? error : ms->error; 72 | return (-1); 73 | } 74 | 75 | static int 76 | check_capture(struct match_state *ms, int l) 77 | { 78 | l -= '1'; 79 | if (l < 0 || l >= ms->level || ms->capture[l].len == CAP_UNFINISHED) 80 | return match_error(ms, "invalid capture index"); 81 | return (l); 82 | } 83 | 84 | static int 85 | capture_to_close(struct match_state *ms) 86 | { 87 | int level = ms->level; 88 | for (level--; level >= 0; level--) 89 | if (ms->capture[level].len == CAP_UNFINISHED) 90 | return (level); 91 | return match_error(ms, "invalid pattern capture"); 92 | } 93 | 94 | static const char * 95 | classend(struct match_state *ms, const char *p) 96 | { 97 | switch (*p++) { 98 | case L_ESC: 99 | if (p == ms->p_end) 100 | match_error(ms, 101 | "malformed pattern (ends with '%')"); 102 | return p + 1; 103 | case '[': 104 | if (*p == '^') 105 | p++; 106 | do { 107 | /* look for a ']' */ 108 | if (p == ms->p_end) { 109 | match_error(ms, 110 | "malformed pattern (missing ']')"); 111 | break; 112 | } 113 | if (*(p++) == L_ESC && p < ms->p_end) { 114 | /* skip escapes (e.g. '%]') */ 115 | p++; 116 | } 117 | } while (*p != ']'); 118 | return p + 1; 119 | default: 120 | return p; 121 | } 122 | } 123 | 124 | static int 125 | match_class(int c, int cl) 126 | { 127 | int res; 128 | switch (tolower(cl)) { 129 | case 'a': 130 | res = isalpha(c); 131 | break; 132 | case 'c': 133 | res = iscntrl(c); 134 | break; 135 | case 'd': 136 | res = isdigit(c); 137 | break; 138 | case 'g': 139 | res = isgraph(c); 140 | break; 141 | case 'l': 142 | res = islower(c); 143 | break; 144 | case 'p': 145 | res = ispunct(c); 146 | break; 147 | case 's': 148 | res = isspace(c); 149 | break; 150 | case 'u': 151 | res = isupper(c); 152 | break; 153 | case 'w': 154 | res = isalnum(c); 155 | break; 156 | case 'x': 157 | res = isxdigit(c); 158 | break; 159 | default: 160 | return (cl == c); 161 | } 162 | return (islower(cl) ? res : !res); 163 | } 164 | 165 | static int 166 | matchbracketclass(int c, const char *p, const char *ec) 167 | { 168 | int sig = 1; 169 | if (*(p + 1) == '^') { 170 | sig = 0; 171 | /* skip the '^' */ 172 | p++; 173 | } 174 | while (++p < ec) { 175 | if (*p == L_ESC) { 176 | p++; 177 | if (match_class(c, uchar(*p))) 178 | return sig; 179 | } else if ((*(p + 1) == '-') && (p + 2 < ec)) { 180 | p += 2; 181 | if (uchar(*(p - 2)) <= c && c <= uchar(*p)) 182 | return sig; 183 | } else if (uchar(*p) == c) 184 | return sig; 185 | } 186 | return !sig; 187 | } 188 | 189 | static int 190 | singlematch(struct match_state *ms, const char *s, const char *p, 191 | const char *ep) 192 | { 193 | if (s >= ms->src_end) 194 | return 0; 195 | else { 196 | int c = uchar(*s); 197 | switch (*p) { 198 | case '.': 199 | /* matches any char */ 200 | return (1); 201 | case L_ESC: 202 | return match_class(c, uchar(*(p + 1))); 203 | case '[': 204 | return matchbracketclass(c, p, ep - 1); 205 | default: 206 | return (uchar(*p) == c); 207 | } 208 | } 209 | } 210 | 211 | static const char * 212 | matchbalance(struct match_state *ms, const char *s, const char *p) 213 | { 214 | if (p >= ms->p_end - 1) { 215 | match_error(ms, 216 | "malformed pattern (missing arguments to '%b')"); 217 | return (NULL); 218 | } 219 | if (*s != *p) 220 | return (NULL); 221 | else { 222 | int b = *p; 223 | int e = *(p + 1); 224 | int cont = 1; 225 | while (++s < ms->src_end) { 226 | if (*s == e) { 227 | if (--cont == 0) 228 | return s + 1; 229 | } else if (*s == b) 230 | cont++; 231 | } 232 | } 233 | 234 | /* string ends out of balance */ 235 | return (NULL); 236 | } 237 | 238 | static const char * 239 | max_expand(struct match_state *ms, const char *s, const char *p, const char *ep) 240 | { 241 | ptrdiff_t i = 0; 242 | /* counts maximum expand for item */ 243 | while (singlematch(ms, s + i, p, ep)) 244 | i++; 245 | /* keeps trying to match with the maximum repetitions */ 246 | while (i >= 0) { 247 | const char *res = match(ms, (s + i), ep + 1); 248 | if (res) 249 | return res; 250 | /* else didn't match; reduce 1 repetition to try again */ 251 | i--; 252 | } 253 | return NULL; 254 | } 255 | 256 | static const char * 257 | min_expand(struct match_state *ms, const char *s, const char *p, const char *ep) 258 | { 259 | for (;;) { 260 | const char *res = match(ms, s, ep + 1); 261 | if (res != NULL) 262 | return res; 263 | else if (singlematch(ms, s, p, ep)) 264 | s++; /* try with one more repetition */ 265 | else 266 | return NULL; 267 | } 268 | } 269 | 270 | static const char * 271 | start_capture(struct match_state *ms, const char *s, const char *p, int what) 272 | { 273 | const char *res; 274 | 275 | int level = ms->level; 276 | if (level >= ms->maxcaptures) { 277 | match_error(ms, "too many captures"); 278 | return (NULL); 279 | } 280 | ms->capture[level].init = s; 281 | ms->capture[level].len = what; 282 | ms->level = level + 1; 283 | /* undo capture if match failed */ 284 | if ((res = match(ms, s, p)) == NULL) 285 | ms->level--; 286 | return res; 287 | } 288 | 289 | static const char * 290 | end_capture(struct match_state *ms, const char *s, const char *p) 291 | { 292 | int l = capture_to_close(ms); 293 | const char *res; 294 | if (l == -1) 295 | return NULL; 296 | /* close capture */ 297 | ms->capture[l].len = s - ms->capture[l].init; 298 | /* undo capture if match failed */ 299 | if ((res = match(ms, s, p)) == NULL) 300 | ms->capture[l].len = CAP_UNFINISHED; 301 | return res; 302 | } 303 | 304 | static const char * 305 | match_capture(struct match_state *ms, const char *s, int l) 306 | { 307 | size_t len; 308 | l = check_capture(ms, l); 309 | if (l == -1) 310 | return NULL; 311 | len = ms->capture[l].len; 312 | if ((size_t) (ms->src_end - s) >= len && 313 | memcmp(ms->capture[l].init, s, len) == 0) 314 | return s + len; 315 | else 316 | return NULL; 317 | } 318 | 319 | static const char * 320 | match(struct match_state *ms, const char *s, const char *p) 321 | { 322 | const char *ep, *res; 323 | char previous; 324 | 325 | if (ms->matchdepth-- == 0) { 326 | match_error(ms, "pattern too complex"); 327 | return (NULL); 328 | } 329 | 330 | /* using goto's to optimize tail recursion */ 331 | init: 332 | /* end of pattern? */ 333 | if (p != ms->p_end) { 334 | switch (*p) { 335 | case '(': 336 | /* start capture */ 337 | if (*(p + 1) == ')') 338 | /* position capture? */ 339 | s = start_capture(ms, s, p + 2, CAP_POSITION); 340 | else 341 | s = start_capture(ms, s, p + 1, CAP_UNFINISHED); 342 | break; 343 | case ')': 344 | /* end capture */ 345 | s = end_capture(ms, s, p + 1); 346 | break; 347 | case '$': 348 | /* is the '$' the last char in pattern? */ 349 | if ((p + 1) != ms->p_end) { 350 | /* no; go to default */ 351 | goto dflt; 352 | } 353 | /* check end of string */ 354 | s = (s == ms->src_end) ? s : NULL; 355 | break; 356 | case L_ESC: 357 | /* escaped sequences not in the format class[*+?-]? */ 358 | switch (*(p + 1)) { 359 | case 'b': 360 | /* balanced string? */ 361 | s = matchbalance(ms, s, p + 2); 362 | if (s != NULL) { 363 | p += 4; 364 | /* return match(ms, s, p + 4); */ 365 | goto init; 366 | } /* else fail (s == NULL) */ 367 | break; 368 | case 'f': 369 | /* frontier? */ 370 | p += 2; 371 | if (*p != '[') { 372 | match_error(ms, "missing '['" 373 | " after '%f' in pattern"); 374 | break; 375 | } 376 | /* points to what is next */ 377 | ep = classend(ms, p); 378 | if (ms->error != NULL) 379 | break; 380 | previous = 381 | (s == ms->src_init) ? '\0' : *(s - 1); 382 | if (!matchbracketclass(uchar(previous), 383 | p, ep - 1) && 384 | matchbracketclass(uchar(*s), 385 | p, ep - 1)) { 386 | p = ep; 387 | /* return match(ms, s, ep); */ 388 | goto init; 389 | } 390 | /* match failed */ 391 | s = NULL; 392 | break; 393 | case '0': 394 | case '1': 395 | case '2': 396 | case '3': 397 | case '4': 398 | case '5': 399 | case '6': 400 | case '7': 401 | case '8': 402 | case '9': 403 | /* capture results (%0-%9)? */ 404 | s = match_capture(ms, s, uchar(*(p + 1))); 405 | if (s != NULL) { 406 | p += 2; 407 | /* return match(ms, s, p + 2) */ 408 | goto init; 409 | } 410 | break; 411 | default: 412 | goto dflt; 413 | } 414 | break; 415 | default: 416 | 417 | /* pattern class plus optional suffix */ 418 | dflt: 419 | /* points to optional suffix */ 420 | ep = classend(ms, p); 421 | if (ms->error != NULL) 422 | break; 423 | 424 | /* does not match at least once? */ 425 | if (!singlematch(ms, s, p, ep)) { 426 | if (ms->repetitioncounter-- == 0) { 427 | match_error(ms, "max repetition items"); 428 | s = NULL; /* fail */ 429 | /* accept empty? */ 430 | } else if 431 | (*ep == '*' || *ep == '?' || *ep == '-') { 432 | p = ep + 1; 433 | /* return match(ms, s, ep + 1); */ 434 | goto init; 435 | } else { 436 | /* '+' or no suffix */ 437 | s = NULL; /* fail */ 438 | } 439 | } else { 440 | /* matched once */ 441 | /* handle optional suffix */ 442 | switch (*ep) { 443 | case '?': 444 | /* optional */ 445 | if ((res = 446 | match(ms, s + 1, ep + 1)) != NULL) 447 | s = res; 448 | else { 449 | /* 450 | * else return 451 | * match(ms, s, ep + 1); 452 | */ 453 | p = ep + 1; 454 | goto init; 455 | } 456 | break; 457 | case '+': 458 | /* 1 or more repetitions */ 459 | s++; /* 1 match already done */ 460 | /* FALLTHROUGH */ 461 | case '*': 462 | /* 0 or more repetitions */ 463 | s = max_expand(ms, s, p, ep); 464 | break; 465 | case '-': 466 | /* 0 or more repetitions (minimum) */ 467 | s = min_expand(ms, s, p, ep); 468 | break; 469 | default: 470 | /* no suffix */ 471 | s++; 472 | p = ep; 473 | /* return match(ms, s + 1, ep); */ 474 | goto init; 475 | } 476 | } 477 | break; 478 | } 479 | } 480 | ms->matchdepth++; 481 | return s; 482 | } 483 | 484 | static const char * 485 | lmemfind(const char *s1, size_t l1, 486 | const char *s2, size_t l2) 487 | { 488 | const char *init; 489 | 490 | if (l2 == 0) { 491 | /* empty strings are everywhere */ 492 | return (s1); 493 | } else if (l2 > l1) { 494 | /* avoids a negative 'l1' */ 495 | return (NULL); 496 | } else { 497 | /* 498 | * to search for a '*s2' inside 's1' 499 | * - 1st char will be checked by 'memchr' 500 | * - 's2' cannot be found after that 501 | */ 502 | l2--; 503 | l1 = l1 - l2; 504 | while (l1 > 0 && 505 | (init = (const char *)memchr(s1, *s2, l1)) != NULL) { 506 | /* 1st char is already checked */ 507 | init++; 508 | if (memcmp(init, s2 + 1, l2) == 0) 509 | return init - 1; 510 | else { 511 | /* correct 'l1' and 's1' to try again */ 512 | l1 -= init - s1; 513 | s1 = init; 514 | } 515 | } 516 | /* not found */ 517 | return (NULL); 518 | } 519 | } 520 | 521 | static int 522 | push_onecapture(struct match_state *ms, int i, const char *s, 523 | const char *e, struct str_find *sm) 524 | { 525 | if (i >= ms->level) { 526 | if (i == 0 || ms->level == 0) { 527 | /* add whole match */ 528 | sm->sm_so = (off_t)(s - ms->src_init); 529 | sm->sm_eo = (off_t)(e - s) + sm->sm_so; 530 | } else 531 | return match_error(ms, "invalid capture index"); 532 | } else { 533 | ptrdiff_t l = ms->capture[i].len; 534 | if (l == CAP_UNFINISHED) 535 | return match_error(ms, "unfinished capture"); 536 | sm->sm_so = ms->capture[i].init - ms->src_init; 537 | sm->sm_eo = sm->sm_so + l; 538 | } 539 | sm->sm_eo = sm->sm_eo < sm->sm_so ? sm->sm_so : sm->sm_eo; 540 | return (0); 541 | } 542 | 543 | static int 544 | push_captures(struct match_state *ms, const char *s, const char *e, 545 | struct str_find *sm, size_t nsm) 546 | { 547 | unsigned int i; 548 | unsigned int nlevels = (ms->level <= 0 && s) ? 1 : ms->level; 549 | 550 | if (nlevels > nsm) 551 | nlevels = nsm; 552 | for (i = 0; i < nlevels; i++) 553 | if (push_onecapture(ms, i, s, e, sm + i) == -1) 554 | break; 555 | 556 | /* number of strings pushed */ 557 | return (nlevels); 558 | } 559 | 560 | /* check whether pattern has no special characters */ 561 | static int 562 | nospecials(const char *p, size_t l) 563 | { 564 | size_t upto = 0; 565 | 566 | do { 567 | if (strpbrk(p + upto, SPECIALS)) { 568 | /* pattern has a special character */ 569 | return 0; 570 | } 571 | /* may have more after \0 */ 572 | upto += strlen(p + upto) + 1; 573 | } while (upto <= l); 574 | 575 | /* no special chars found */ 576 | return (1); 577 | } 578 | 579 | static int 580 | str_find_aux(struct match_state *ms, const char *pattern, const char *string, 581 | struct str_find *sm, size_t nsm, off_t init) 582 | { 583 | size_t ls = strlen(string); 584 | size_t lp = strlen(pattern); 585 | const char *s = string; 586 | const char *p = pattern; 587 | const char *s1, *s2; 588 | int anchor, i; 589 | 590 | if (init < 0) 591 | init = 0; 592 | else if (init > (off_t)ls) 593 | return match_error(ms, "starting after string's end"); 594 | s1 = s + init; 595 | 596 | if (nospecials(p, lp)) { 597 | /* do a plain search */ 598 | s2 = lmemfind(s1, ls - (size_t)init, p, lp); 599 | if (s2 != NULL) { 600 | i = 0; 601 | sm[i].sm_so = 0; 602 | sm[i].sm_eo = ls; 603 | if (nsm > 1) { 604 | i++; 605 | sm[i].sm_so = s2 - s; 606 | sm[i].sm_eo = (s2 - s) + lp; 607 | } 608 | return (i + 1); 609 | } 610 | return (0); 611 | } 612 | 613 | anchor = (*p == '^'); 614 | if (anchor) { 615 | p++; 616 | lp--; /* skip anchor character */ 617 | } 618 | ms->maxcaptures = (nsm > MAXCAPTURES ? MAXCAPTURES : nsm) - 1; 619 | ms->matchdepth = MAXCCALLS; 620 | ms->repetitioncounter = MAXREPETITION; 621 | ms->src_init = s; 622 | ms->src_end = s + ls; 623 | ms->p_end = p + lp; 624 | do { 625 | const char *res; 626 | ms->level = 0; 627 | if ((res = match(ms, s1, p)) != NULL) { 628 | sm->sm_so = 0; 629 | sm->sm_eo = ls; 630 | return push_captures(ms, s1, res, sm + 1, nsm - 1) + 1; 631 | 632 | } else if (ms->error != NULL) { 633 | return 0; 634 | } 635 | } while (s1++ < ms->src_end && !anchor); 636 | 637 | return 0; 638 | } 639 | 640 | int 641 | str_find(const char *string, const char *pattern, struct str_find *sm, 642 | size_t nsm, const char **errstr) 643 | { 644 | struct match_state ms; 645 | int ret; 646 | 647 | memset(&ms, 0, sizeof(ms)); 648 | memset(sm, 0, nsm * sizeof(*sm)); 649 | 650 | ret = str_find_aux(&ms, pattern, string, sm, nsm, 0); 651 | if (ms.error != NULL) { 652 | /* Return 0 on error and store the error string */ 653 | *errstr = ms.error; 654 | ret = 0; 655 | } else 656 | *errstr = NULL; 657 | 658 | return (ret); 659 | } 660 | 661 | int 662 | str_match(const char *string, const char *pattern, struct str_match *m, 663 | const char **errstr) 664 | { 665 | struct str_find sm[MAXCAPTURES]; 666 | struct match_state ms; 667 | int ret, i; 668 | size_t len, nsm; 669 | 670 | nsm = MAXCAPTURES; 671 | memset(&ms, 0, sizeof(ms)); 672 | memset(sm, 0, sizeof(sm)); 673 | memset(m, 0, sizeof(*m)); 674 | 675 | ret = str_find_aux(&ms, pattern, string, sm, nsm, 0); 676 | if (ret <= 0 || ms.error != NULL) { 677 | /* Return -1 on error and store the error string */ 678 | *errstr = ms.error; 679 | return (-1); 680 | } 681 | 682 | if ((m->sm_match = calloc(ret, sizeof(char *))) == NULL) { 683 | *errstr = strerror(errno); 684 | return (-1); 685 | } 686 | m->sm_nmatch = ret; 687 | 688 | for (i = 0; i < ret; i++) { 689 | if (sm[i].sm_so > sm[i].sm_eo) 690 | continue; 691 | len = sm[i].sm_eo - sm[i].sm_so; 692 | if ((m->sm_match[i] = strndup(string + 693 | sm[i].sm_so, len)) == NULL) { 694 | *errstr = strerror(errno); 695 | str_match_free(m); 696 | return (-1); 697 | } 698 | } 699 | 700 | *errstr = NULL; 701 | return (0); 702 | } 703 | 704 | void 705 | str_match_free(struct str_match *m) 706 | { 707 | unsigned int i = 0; 708 | for (i = 0; i < m->sm_nmatch; i++) 709 | free(m->sm_match[i]); 710 | free(m->sm_match); 711 | m->sm_match = NULL; 712 | m->sm_nmatch = 0; 713 | } 714 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/server_file.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: server_file.c,v 1.75 2022/08/15 09:40:14 op Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2006 - 2017 Reyk Floeter 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "httpd.h" 36 | #include "http.h" 37 | 38 | #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) 39 | #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) 40 | 41 | int server_file_access(struct httpd *, struct client *, 42 | char *, size_t); 43 | int server_file_request(struct httpd *, struct client *, 44 | char *, struct timespec *); 45 | int server_partial_file_request(struct httpd *, struct client *, 46 | char *, struct timespec *, char *); 47 | int server_file_index(struct httpd *, struct client *); 48 | int server_file_modified_since(struct http_descriptor *, 49 | struct timespec *); 50 | int server_file_method(struct client *); 51 | int parse_range_spec(char *, size_t, struct range *); 52 | int parse_ranges(struct client *, char *, size_t); 53 | 54 | int 55 | server_file_access(struct httpd *env, struct client *clt, 56 | char *path, size_t len) 57 | { 58 | struct http_descriptor *desc = clt->clt_descreq; 59 | struct server_config *srv_conf = clt->clt_srv_conf; 60 | struct stat st; 61 | struct kv *r, key; 62 | char *newpath, *encodedpath; 63 | int ret; 64 | 65 | errno = 0; 66 | 67 | if (stat(path, &st) == -1) { 68 | goto fail; 69 | } else if (S_ISDIR(st.st_mode)) { 70 | /* Deny access if directory indexing is disabled */ 71 | if (srv_conf->flags & SRVFLAG_NO_INDEX) { 72 | errno = EACCES; 73 | goto fail; 74 | } 75 | 76 | if (desc->http_path_alias != NULL) { 77 | /* Recursion - the index "file" is a directory? */ 78 | errno = EINVAL; 79 | goto fail; 80 | } 81 | 82 | /* Redirect to path with trailing "/" */ 83 | if (path[strlen(path) - 1] != '/') { 84 | if ((encodedpath = url_encode(desc->http_path)) == NULL) 85 | return (500); 86 | if (asprintf(&newpath, "%s/", encodedpath) == -1) { 87 | free(encodedpath); 88 | return (500); 89 | } 90 | free(encodedpath); 91 | 92 | /* Path alias will be used for the redirection */ 93 | desc->http_path_alias = newpath; 94 | 95 | /* Indicate that the file has been moved */ 96 | return (301); 97 | } 98 | 99 | /* Append the default index file to the location */ 100 | if (asprintf(&newpath, "%s%s", desc->http_path, 101 | srv_conf->index) == -1) 102 | return (500); 103 | desc->http_path_alias = newpath; 104 | if (server_getlocation(clt, newpath) != srv_conf) { 105 | /* The location has changed */ 106 | return (server_file(env, clt)); 107 | } 108 | 109 | /* Otherwise append the default index file to the path */ 110 | if (strlcat(path, srv_conf->index, len) >= len) { 111 | errno = EACCES; 112 | goto fail; 113 | } 114 | 115 | ret = server_file_access(env, clt, path, len); 116 | if (ret == 404) { 117 | /* 118 | * Index file not found; fail if auto-indexing is 119 | * not enabled, otherwise return success but 120 | * indicate directory with S_ISDIR of the previous 121 | * stat. 122 | */ 123 | if ((srv_conf->flags & SRVFLAG_AUTO_INDEX) == 0) { 124 | errno = EACCES; 125 | goto fail; 126 | } 127 | 128 | return (server_file_index(env, clt)); 129 | } 130 | return (ret); 131 | } else if (!S_ISREG(st.st_mode)) { 132 | /* Don't follow symlinks and ignore special files */ 133 | errno = EACCES; 134 | goto fail; 135 | } 136 | 137 | key.kv_key = "Range"; 138 | r = kv_find(&desc->http_headers, &key); 139 | if (r != NULL) 140 | return (server_partial_file_request(env, clt, path, 141 | &st.st_mtim, r->kv_value)); 142 | else 143 | return (server_file_request(env, clt, path, &st.st_mtim)); 144 | 145 | fail: 146 | switch (errno) { 147 | case ENOENT: 148 | case ENOTDIR: 149 | return (404); 150 | case EACCES: 151 | return (403); 152 | default: 153 | return (500); 154 | } 155 | 156 | /* NOTREACHED */ 157 | } 158 | 159 | int 160 | server_file(struct httpd *env, struct client *clt) 161 | { 162 | struct http_descriptor *desc = clt->clt_descreq; 163 | struct server_config *srv_conf = clt->clt_srv_conf; 164 | char path[PATH_MAX]; 165 | const char *stripped, *errstr = NULL; 166 | int ret = 500; 167 | 168 | if (srv_conf->flags & SRVFLAG_FCGI) 169 | return (server_fcgi(env, clt)); 170 | 171 | /* Request path is already canonicalized */ 172 | stripped = server_root_strip( 173 | desc->http_path_alias != NULL ? 174 | desc->http_path_alias : desc->http_path, 175 | srv_conf->strip); 176 | if ((size_t)snprintf(path, sizeof(path), "%s%s", 177 | srv_conf->root, stripped) >= sizeof(path)) { 178 | errstr = desc->http_path; 179 | goto abort; 180 | } 181 | 182 | /* Returns HTTP status code on error */ 183 | if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) { 184 | errstr = desc->http_path_alias != NULL ? 185 | desc->http_path_alias : desc->http_path; 186 | goto abort; 187 | } 188 | 189 | return (ret); 190 | 191 | abort: 192 | if (errstr == NULL) 193 | errstr = strerror(errno); 194 | server_abort_http(clt, ret, errstr); 195 | return (-1); 196 | } 197 | 198 | int 199 | server_file_method(struct client *clt) 200 | { 201 | struct http_descriptor *desc = clt->clt_descreq; 202 | 203 | switch (desc->http_method) { 204 | case HTTP_METHOD_GET: 205 | case HTTP_METHOD_HEAD: 206 | return (0); 207 | default: 208 | /* Other methods are not allowed */ 209 | errno = EACCES; 210 | return (405); 211 | } 212 | /* NOTREACHED */ 213 | } 214 | 215 | int 216 | server_file_request(struct httpd *env, struct client *clt, char *path, 217 | struct timespec *mtim) 218 | { 219 | struct server_config *srv_conf = clt->clt_srv_conf; 220 | struct media_type *media; 221 | const char *errstr = NULL; 222 | int fd = -1, ret, code = 500; 223 | struct stat st; 224 | size_t bufsiz; 225 | char gzpath[PATH_MAX]; 226 | 227 | if ((ret = server_file_method(clt)) != 0) { 228 | code = ret; 229 | goto abort; 230 | } 231 | 232 | media = media_find_config(env, srv_conf, path); 233 | if ((ret = server_file_modified_since(clt->clt_descreq, mtim)) != -1) { 234 | /* send the header without a body */ 235 | if ((ret = server_response_http(clt, ret, media, -1, 236 | MINIMUM(time(NULL), mtim->tv_sec))) == -1) 237 | goto fail; 238 | goto done; 239 | } 240 | 241 | /* change path to path.gz if necessary. */ 242 | if (srv_conf->flags & SRVFLAG_GZIP_STATIC) { 243 | struct http_descriptor *req = clt->clt_descreq; 244 | struct http_descriptor *resp = clt->clt_descresp; 245 | struct kv *r, key; 246 | 247 | /* check Accept-Encoding header */ 248 | key.kv_key = "Accept-Encoding"; 249 | r = kv_find(&req->http_headers, &key); 250 | 251 | if (r != NULL && strstr(r->kv_value, "gzip") != NULL) { 252 | /* append ".gz" to path and check existence */ 253 | ret = snprintf(gzpath, sizeof(gzpath), "%s.gz", path); 254 | if (ret < 0 || (size_t)ret >= sizeof(gzpath)) 255 | goto abort; 256 | if ((fd = open(gzpath, O_RDONLY)) != -1) { 257 | /* .gz must be a file, and not older */ 258 | if (fstat(fd, &st) != -1 && 259 | S_ISREG(st.st_mode) && 260 | timespeccmp(&st.st_mtim, mtim, >=)) { 261 | kv_add(&resp->http_headers, 262 | "Content-Encoding", "gzip"); 263 | /* Use original file timestamp */ 264 | st.st_mtim = *mtim; 265 | } else { 266 | close(fd); 267 | fd = -1; 268 | } 269 | } 270 | } 271 | } 272 | 273 | /* Now open the file, should be readable or we have another problem */ 274 | if (fd == -1) { 275 | if ((fd = open(path, O_RDONLY)) == -1) 276 | goto abort; 277 | if (fstat(fd, &st) == -1) 278 | goto abort; 279 | } 280 | 281 | ret = server_response_http(clt, 200, media, st.st_size, 282 | MINIMUM(time(NULL), st.st_mtim.tv_sec)); 283 | switch (ret) { 284 | case -1: 285 | goto fail; 286 | case 0: 287 | /* Connection is already finished */ 288 | close(fd); 289 | goto done; 290 | default: 291 | break; 292 | } 293 | 294 | clt->clt_fd = fd; 295 | if (clt->clt_srvbev != NULL) 296 | bufferevent_free(clt->clt_srvbev); 297 | 298 | clt->clt_srvbev_throttled = 0; 299 | clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read, 300 | server_write, server_file_error, clt); 301 | if (clt->clt_srvbev == NULL) { 302 | errstr = "failed to allocate file buffer event"; 303 | goto fail; 304 | } 305 | 306 | /* Adjust read watermark to the optimal file io size */ 307 | bufsiz = MAXIMUM(st.st_blksize, 64 * 1024); 308 | bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0, 309 | bufsiz); 310 | 311 | bufferevent_settimeout(clt->clt_srvbev, 312 | srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); 313 | bufferevent_enable(clt->clt_srvbev, EV_READ); 314 | bufferevent_disable(clt->clt_bev, EV_READ); 315 | 316 | done: 317 | server_reset_http(clt); 318 | return (0); 319 | fail: 320 | bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); 321 | bufferevent_free(clt->clt_bev); 322 | clt->clt_bev = NULL; 323 | abort: 324 | if (fd != -1) 325 | close(fd); 326 | if (errstr == NULL) 327 | errstr = strerror(errno); 328 | server_abort_http(clt, code, errstr); 329 | return (-1); 330 | } 331 | 332 | int 333 | server_partial_file_request(struct httpd *env, struct client *clt, char *path, 334 | struct timespec *mtim, char *range_str) 335 | { 336 | struct server_config *srv_conf = clt->clt_srv_conf; 337 | struct http_descriptor *resp = clt->clt_descresp; 338 | struct http_descriptor *desc = clt->clt_descreq; 339 | struct media_type *media, multipart_media; 340 | struct range_data *r = &clt->clt_ranges; 341 | struct range *range; 342 | size_t content_length = 0, bufsiz; 343 | struct stat st; 344 | int code = 500, fd = -1, i, nranges, ret; 345 | char content_range[64]; 346 | const char *errstr = NULL; 347 | 348 | /* Ignore range request for methods other than GET */ 349 | if (desc->http_method != HTTP_METHOD_GET) 350 | return server_file_request(env, clt, path, mtim); 351 | 352 | /* Now open the file, should be readable or we have another problem */ 353 | if ((fd = open(path, O_RDONLY)) == -1) 354 | goto abort; 355 | if (fstat(fd, &st) == -1) 356 | goto abort; 357 | 358 | if ((nranges = parse_ranges(clt, range_str, st.st_size)) < 1) { 359 | code = 416; 360 | (void)snprintf(content_range, sizeof(content_range), 361 | "bytes */%lld", st.st_size); 362 | errstr = content_range; 363 | goto abort; 364 | } 365 | 366 | media = media_find_config(env, srv_conf, path); 367 | r->range_media = media; 368 | 369 | if (nranges == 1) { 370 | range = &r->range[0]; 371 | (void)snprintf(content_range, sizeof(content_range), 372 | "bytes %lld-%lld/%lld", range->start, range->end, 373 | st.st_size); 374 | if (kv_add(&resp->http_headers, "Content-Range", 375 | content_range) == NULL) 376 | goto abort; 377 | 378 | range = &r->range[0]; 379 | content_length += range->end - range->start + 1; 380 | } else { 381 | /* Add boundary, all parts will be handled by the callback */ 382 | arc4random_buf(&clt->clt_boundary, sizeof(clt->clt_boundary)); 383 | 384 | /* Calculate Content-Length of the complete multipart body */ 385 | for (i = 0; i < nranges; i++) { 386 | range = &r->range[i]; 387 | 388 | /* calculate Content-Length of the complete body */ 389 | if ((ret = snprintf(NULL, 0, 390 | "\r\n--%llu\r\n" 391 | "Content-Type: %s/%s\r\n" 392 | "Content-Range: bytes %lld-%lld/%lld\r\n\r\n", 393 | clt->clt_boundary, 394 | media->media_type, media->media_subtype, 395 | range->start, range->end, st.st_size)) < 0) 396 | goto abort; 397 | 398 | /* Add data length */ 399 | content_length += ret + range->end - range->start + 1; 400 | 401 | } 402 | if ((ret = snprintf(NULL, 0, "\r\n--%llu--\r\n", 403 | clt->clt_boundary)) < 0) 404 | goto abort; 405 | content_length += ret; 406 | 407 | /* prepare multipart/byteranges media type */ 408 | (void)strlcpy(multipart_media.media_type, "multipart", 409 | sizeof(multipart_media.media_type)); 410 | (void)snprintf(multipart_media.media_subtype, 411 | sizeof(multipart_media.media_subtype), 412 | "byteranges; boundary=%llu", clt->clt_boundary); 413 | media = &multipart_media; 414 | } 415 | 416 | /* Start with first range */ 417 | r->range_toread = TOREAD_HTTP_RANGE; 418 | 419 | ret = server_response_http(clt, 206, media, content_length, 420 | MINIMUM(time(NULL), st.st_mtim.tv_sec)); 421 | switch (ret) { 422 | case -1: 423 | goto fail; 424 | case 0: 425 | /* Connection is already finished */ 426 | close(fd); 427 | goto done; 428 | default: 429 | break; 430 | } 431 | 432 | clt->clt_fd = fd; 433 | if (clt->clt_srvbev != NULL) 434 | bufferevent_free(clt->clt_srvbev); 435 | 436 | clt->clt_srvbev_throttled = 0; 437 | clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read_httprange, 438 | server_write, server_file_error, clt); 439 | if (clt->clt_srvbev == NULL) { 440 | errstr = "failed to allocate file buffer event"; 441 | goto fail; 442 | } 443 | 444 | /* Adjust read watermark to the optimal file io size */ 445 | bufsiz = MAXIMUM(st.st_blksize, 64 * 1024); 446 | bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0, 447 | bufsiz); 448 | 449 | bufferevent_settimeout(clt->clt_srvbev, 450 | srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); 451 | bufferevent_enable(clt->clt_srvbev, EV_READ); 452 | bufferevent_disable(clt->clt_bev, EV_READ); 453 | 454 | done: 455 | server_reset_http(clt); 456 | return (0); 457 | fail: 458 | bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); 459 | bufferevent_free(clt->clt_bev); 460 | clt->clt_bev = NULL; 461 | abort: 462 | if (fd != -1) 463 | close(fd); 464 | if (errstr == NULL) 465 | errstr = strerror(errno); 466 | server_abort_http(clt, code, errstr); 467 | return (-1); 468 | } 469 | 470 | int 471 | server_file_index(struct httpd *env, struct client *clt) 472 | { 473 | char path[PATH_MAX]; 474 | char tmstr[21]; 475 | struct http_descriptor *desc = clt->clt_descreq; 476 | struct server_config *srv_conf = clt->clt_srv_conf; 477 | struct dirent **namelist, *dp; 478 | int namesize, i, ret, fd = -1, namewidth, skip; 479 | int code = 500; 480 | struct evbuffer *evb = NULL; 481 | struct media_type *media; 482 | const char *stripped, *style; 483 | char *escapeduri, *escapedhtml, *escapedpath; 484 | struct tm tm; 485 | struct stat st; 486 | time_t t, dir_mtime; 487 | 488 | if ((ret = server_file_method(clt)) != 0) { 489 | code = ret; 490 | goto abort; 491 | } 492 | 493 | /* Request path is already canonicalized */ 494 | stripped = server_root_strip(desc->http_path, srv_conf->strip); 495 | if ((size_t)snprintf(path, sizeof(path), "%s%s", 496 | srv_conf->root, stripped) >= sizeof(path)) 497 | goto abort; 498 | 499 | /* Now open the file, should be readable or we have another problem */ 500 | if ((fd = open(path, O_RDONLY)) == -1) 501 | goto abort; 502 | if (fstat(fd, &st) == -1) 503 | goto abort; 504 | 505 | /* Save last modification time */ 506 | dir_mtime = MINIMUM(time(NULL), st.st_mtim.tv_sec); 507 | 508 | if ((evb = evbuffer_new()) == NULL) 509 | goto abort; 510 | 511 | if ((escapedpath = escape_html(desc->http_path)) == NULL) 512 | goto abort; 513 | 514 | /* A CSS stylesheet allows minimal customization by the user */ 515 | style = "body { background-color: white; color: black; font-family: " 516 | "sans-serif; }\nhr { border: 0; border-bottom: 1px dashed; }\n" 517 | "@media (prefers-color-scheme: dark) {\n" 518 | "body { background-color: #1E1F21; color: #EEEFF1; }\n" 519 | "a { color: #BAD7FF; }\n}"; 520 | 521 | /* Generate simple HTML index document */ 522 | if (evbuffer_add_printf(evb, 523 | "\n" 524 | "\n" 525 | "\n" 526 | "\n" 527 | "Index of %s\n" 528 | "\n" 529 | "\n" 530 | "\n" 531 | "

Index of %s

\n" 532 | "
\n
\n",
533 | 	    escapedpath, style, escapedpath) == -1) {
534 | 		free(escapedpath);
535 | 		goto abort;
536 | 	}
537 | 
538 | 	free(escapedpath);
539 | 
540 | 	if ((namesize = scandir(path, &namelist, NULL, alphasort)) == -1)
541 | 		goto abort;
542 | 
543 | 	/* Indicate failure but continue going through the list */
544 | 	skip = 0;
545 | 
546 | 	for (i = 0; i < namesize; i++) {
547 | 		struct stat subst;
548 | 
549 | 		dp = namelist[i];
550 | 
551 | 		if (skip ||
552 | 		    fstatat(fd, dp->d_name, &subst, 0) == -1) {
553 | 			free(dp);
554 | 			continue;
555 | 		}
556 | 
557 | 		t = subst.st_mtime;
558 | 		localtime_r(&t, &tm);
559 | 		strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm);
560 | 		namewidth = 51 - strlen(dp->d_name);
561 | 
562 | 		if ((escapeduri = url_encode(dp->d_name)) == NULL) {
563 | 			skip = 1;
564 | 			free(dp);
565 | 			continue;
566 | 		}
567 | 		if ((escapedhtml = escape_html(dp->d_name)) == NULL) {
568 | 			skip = 1;
569 | 			free(escapeduri);
570 | 			free(dp);
571 | 			continue;
572 | 		}
573 | 
574 | 		if (dp->d_name[0] == '.' &&
575 | 		    !(dp->d_name[1] == '.' && dp->d_name[2] == '\0')) {
576 | 			/* ignore hidden files starting with a dot */
577 | 		} else if (S_ISDIR(subst.st_mode)) {
578 | 			namewidth -= 1; /* trailing slash */
579 | 			if (evbuffer_add_printf(evb,
580 | 			    "%s/%*s%s%20s\n",
581 | 			    strchr(escapeduri, ':') != NULL ? "./" : "",
582 | 			    escapeduri, escapedhtml,
583 | 			    MAXIMUM(namewidth, 0), " ", tmstr, "-") == -1)
584 | 				skip = 1;
585 | 		} else if (S_ISREG(subst.st_mode)) {
586 | 			if (evbuffer_add_printf(evb,
587 | 			    "%s%*s%s%20llu\n",
588 | 			    strchr(escapeduri, ':') != NULL ? "./" : "",
589 | 			    escapeduri, escapedhtml,
590 | 			    MAXIMUM(namewidth, 0), " ",
591 | 			    tmstr, subst.st_size) == -1)
592 | 				skip = 1;
593 | 		}
594 | 		free(escapeduri);
595 | 		free(escapedhtml);
596 | 		free(dp);
597 | 	}
598 | 	free(namelist);
599 | 
600 | 	if (skip ||
601 | 	    evbuffer_add_printf(evb,
602 | 	    "
\n
\n\n\n") == -1) 603 | goto abort; 604 | 605 | close(fd); 606 | fd = -1; 607 | 608 | media = media_find_config(env, srv_conf, "index.html"); 609 | ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb), 610 | dir_mtime); 611 | switch (ret) { 612 | case -1: 613 | goto fail; 614 | case 0: 615 | /* Connection is already finished */ 616 | evbuffer_free(evb); 617 | goto done; 618 | default: 619 | break; 620 | } 621 | 622 | if (server_bufferevent_write_buffer(clt, evb) == -1) 623 | goto fail; 624 | evbuffer_free(evb); 625 | evb = NULL; 626 | 627 | bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); 628 | if (clt->clt_persist) 629 | clt->clt_toread = TOREAD_HTTP_HEADER; 630 | else 631 | clt->clt_toread = TOREAD_HTTP_NONE; 632 | clt->clt_done = 0; 633 | 634 | done: 635 | server_reset_http(clt); 636 | return (0); 637 | fail: 638 | bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); 639 | bufferevent_free(clt->clt_bev); 640 | clt->clt_bev = NULL; 641 | abort: 642 | if (fd != -1) 643 | close(fd); 644 | if (evb != NULL) 645 | evbuffer_free(evb); 646 | server_abort_http(clt, code, desc->http_path); 647 | return (-1); 648 | } 649 | 650 | void 651 | server_file_error(struct bufferevent *bev, short error, void *arg) 652 | { 653 | struct client *clt = arg; 654 | struct evbuffer *src, *dst; 655 | 656 | if (error & EVBUFFER_TIMEOUT) { 657 | server_close(clt, "buffer event timeout"); 658 | return; 659 | } 660 | if (error & EVBUFFER_ERROR) { 661 | if (errno == EFBIG) { 662 | bufferevent_enable(bev, EV_READ); 663 | return; 664 | } 665 | server_close(clt, "buffer event error"); 666 | return; 667 | } 668 | if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) { 669 | bufferevent_disable(bev, EV_READ|EV_WRITE); 670 | 671 | clt->clt_done = 1; 672 | 673 | src = EVBUFFER_INPUT(clt->clt_bev); 674 | 675 | /* Close the connection if a previous pipeline is empty */ 676 | if (clt->clt_pipelining && EVBUFFER_LENGTH(src) == 0) 677 | clt->clt_persist = 0; 678 | 679 | if (clt->clt_persist) { 680 | /* Close input file and wait for next HTTP request */ 681 | if (clt->clt_fd != -1) 682 | close(clt->clt_fd); 683 | clt->clt_fd = -1; 684 | clt->clt_toread = TOREAD_HTTP_HEADER; 685 | server_reset_http(clt); 686 | bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); 687 | 688 | /* Start pipelining if the buffer is not empty */ 689 | if (EVBUFFER_LENGTH(src)) { 690 | clt->clt_pipelining++; 691 | server_read_http(clt->clt_bev, arg); 692 | } 693 | return; 694 | } 695 | 696 | dst = EVBUFFER_OUTPUT(clt->clt_bev); 697 | if (EVBUFFER_LENGTH(dst)) { 698 | /* Finish writing all data first */ 699 | bufferevent_enable(clt->clt_bev, EV_WRITE); 700 | return; 701 | } 702 | 703 | server_close(clt, "done"); 704 | return; 705 | } 706 | server_close(clt, "unknown event error"); 707 | return; 708 | } 709 | 710 | int 711 | server_file_modified_since(struct http_descriptor *desc, struct timespec *mtim) 712 | { 713 | struct kv key, *since; 714 | struct tm tm; 715 | 716 | key.kv_key = "If-Modified-Since"; 717 | if ((since = kv_find(&desc->http_headers, &key)) != NULL && 718 | since->kv_value != NULL) { 719 | memset(&tm, 0, sizeof(struct tm)); 720 | 721 | /* 722 | * Return "Not modified" if the file hasn't changed since 723 | * the requested time. 724 | */ 725 | if (strptime(since->kv_value, 726 | "%a, %d %h %Y %T %Z", &tm) != NULL && 727 | timegm(&tm) >= mtim->tv_sec) 728 | return (304); 729 | } 730 | 731 | return (-1); 732 | } 733 | 734 | int 735 | parse_ranges(struct client *clt, char *str, size_t file_sz) 736 | { 737 | int i = 0; 738 | char *p, *q; 739 | struct range_data *r = &clt->clt_ranges; 740 | 741 | memset(r, 0, sizeof(*r)); 742 | 743 | /* Extract range unit */ 744 | if ((p = strchr(str, '=')) == NULL) 745 | return (-1); 746 | 747 | *p++ = '\0'; 748 | /* Check if it's a bytes range spec */ 749 | if (strcmp(str, "bytes") != 0) 750 | return (-1); 751 | 752 | while ((q = strchr(p, ',')) != NULL) { 753 | *q++ = '\0'; 754 | 755 | /* Extract start and end positions */ 756 | if (parse_range_spec(p, file_sz, &r->range[i]) == 0) 757 | continue; 758 | 759 | i++; 760 | if (i == SERVER_MAX_RANGES) 761 | return (-1); 762 | 763 | p = q; 764 | } 765 | 766 | if (parse_range_spec(p, file_sz, &r->range[i]) != 0) 767 | i++; 768 | 769 | r->range_total = file_sz; 770 | r->range_count = i; 771 | return (i); 772 | } 773 | 774 | int 775 | parse_range_spec(char *str, size_t size, struct range *r) 776 | { 777 | size_t start_str_len, end_str_len; 778 | char *p, *start_str, *end_str; 779 | const char *errstr; 780 | 781 | if ((p = strchr(str, '-')) == NULL) 782 | return (0); 783 | 784 | *p++ = '\0'; 785 | start_str = str; 786 | end_str = p; 787 | start_str_len = strlen(start_str); 788 | end_str_len = strlen(end_str); 789 | 790 | /* Either 'start' or 'end' is optional but not both */ 791 | if ((start_str_len == 0) && (end_str_len == 0)) 792 | return (0); 793 | 794 | if (end_str_len) { 795 | r->end = strtonum(end_str, 0, LLONG_MAX, &errstr); 796 | if (errstr) 797 | return (0); 798 | 799 | if ((size_t)r->end >= size) 800 | r->end = size - 1; 801 | } else 802 | r->end = size - 1; 803 | 804 | if (start_str_len) { 805 | r->start = strtonum(start_str, 0, LLONG_MAX, &errstr); 806 | if (errstr) 807 | return (0); 808 | 809 | if ((size_t)r->start >= size) 810 | return (0); 811 | } else { 812 | r->start = size - r->end; 813 | r->end = size - 1; 814 | } 815 | 816 | if (r->end < r->start) 817 | return (0); 818 | 819 | return (1); 820 | } 821 | -------------------------------------------------------------------------------- /src/usr.sbin/httpd/proc.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: proc.c,v 1.42 2023/02/15 20:44:01 tobhe Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2010 - 2016 Reyk Floeter 5 | * Copyright (c) 2008 Pierre-Yves Ritschard 6 | * 7 | * Permission to use, copy, modify, and distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include "httpd.h" 39 | 40 | #ifdef USE_BLACKLIST 41 | #include "blacklist_client.h" 42 | #endif 43 | 44 | void proc_exec(struct privsep *, struct privsep_proc *, unsigned int, int, 45 | int, char **); 46 | void proc_setup(struct privsep *, struct privsep_proc *, unsigned int); 47 | void proc_open(struct privsep *, int, int); 48 | void proc_accept(struct privsep *, int, enum privsep_procid, 49 | unsigned int); 50 | void proc_close(struct privsep *); 51 | void proc_shutdown(struct privsep_proc *); 52 | void proc_sig_handler(int, short, void *); 53 | void proc_range(struct privsep *, enum privsep_procid, int *, int *); 54 | int proc_dispatch_null(int, struct privsep_proc *, struct imsg *); 55 | 56 | enum privsep_procid 57 | proc_getid(struct privsep_proc *procs, unsigned int nproc, 58 | const char *proc_name) 59 | { 60 | struct privsep_proc *p; 61 | unsigned int proc; 62 | 63 | for (proc = 0; proc < nproc; proc++) { 64 | p = &procs[proc]; 65 | if (strcmp(p->p_title, proc_name)) 66 | continue; 67 | 68 | return (p->p_id); 69 | } 70 | 71 | return (PROC_MAX); 72 | } 73 | 74 | void 75 | proc_exec(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc, 76 | int debug, int argc, char **argv) 77 | { 78 | unsigned int proc, nargc, i, proc_i; 79 | char **nargv; 80 | struct privsep_proc *p; 81 | char num[32]; 82 | int fd; 83 | 84 | /* Prepare the new process argv. */ 85 | nargv = calloc(argc + 5, sizeof(char *)); 86 | if (nargv == NULL) 87 | fatal("%s: calloc", __func__); 88 | 89 | /* Copy call argument first. */ 90 | nargc = 0; 91 | nargv[nargc++] = argv[0]; 92 | 93 | /* Set process name argument and save the position. */ 94 | nargv[nargc++] = "-P"; 95 | proc_i = nargc; 96 | nargc++; 97 | 98 | /* Point process instance arg to stack and copy the original args. */ 99 | nargv[nargc++] = "-I"; 100 | nargv[nargc++] = num; 101 | for (i = 1; i < (unsigned int) argc; i++) 102 | nargv[nargc++] = argv[i]; 103 | 104 | nargv[nargc] = NULL; 105 | 106 | for (proc = 0; proc < nproc; proc++) { 107 | p = &procs[proc]; 108 | 109 | /* Update args with process title. */ 110 | nargv[proc_i] = (char *)(uintptr_t)p->p_title; 111 | 112 | /* Fire children processes. */ 113 | for (i = 0; i < ps->ps_instances[p->p_id]; i++) { 114 | /* Update the process instance number. */ 115 | snprintf(num, sizeof(num), "%u", i); 116 | 117 | fd = ps->ps_pipes[p->p_id][i].pp_pipes[PROC_PARENT][0]; 118 | ps->ps_pipes[p->p_id][i].pp_pipes[PROC_PARENT][0] = -1; 119 | 120 | switch (fork()) { 121 | case -1: 122 | fatal("%s: fork", __func__); 123 | break; 124 | case 0: 125 | /* First create a new session */ 126 | if (setsid() == -1) 127 | fatal("setsid"); 128 | 129 | /* Prepare parent socket. */ 130 | if (fd != PROC_PARENT_SOCK_FILENO) { 131 | if (dup2(fd, PROC_PARENT_SOCK_FILENO) 132 | == -1) 133 | fatal("dup2"); 134 | } else if (fcntl(fd, F_SETFD, 0) == -1) 135 | fatal("fcntl"); 136 | 137 | /* Daemons detach from terminal. */ 138 | if (!debug && (fd = 139 | open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { 140 | (void)dup2(fd, STDIN_FILENO); 141 | (void)dup2(fd, STDOUT_FILENO); 142 | (void)dup2(fd, STDERR_FILENO); 143 | if (fd > 2) 144 | (void)close(fd); 145 | } 146 | 147 | execvp(argv[0], nargv); 148 | fatal("%s: execvp", __func__); 149 | break; 150 | default: 151 | /* Close child end. */ 152 | close(fd); 153 | break; 154 | } 155 | } 156 | } 157 | free(nargv); 158 | } 159 | 160 | void 161 | proc_connect(struct privsep *ps) 162 | { 163 | struct imsgev *iev; 164 | unsigned int src, dst, inst; 165 | 166 | /* Don't distribute any sockets if we are not really going to run. */ 167 | if (ps->ps_noaction) 168 | return; 169 | 170 | for (dst = 0; dst < PROC_MAX; dst++) { 171 | /* We don't communicate with ourselves. */ 172 | if (dst == PROC_PARENT) 173 | continue; 174 | 175 | for (inst = 0; inst < ps->ps_instances[dst]; inst++) { 176 | iev = &ps->ps_ievs[dst][inst]; 177 | imsg_init(&iev->ibuf, ps->ps_pp->pp_pipes[dst][inst]); 178 | event_set(&iev->ev, iev->ibuf.fd, iev->events, 179 | iev->handler, iev->data); 180 | event_add(&iev->ev, NULL); 181 | } 182 | } 183 | 184 | /* Distribute the socketpair()s for everyone. */ 185 | for (src = 0; src < PROC_MAX; src++) 186 | for (dst = src; dst < PROC_MAX; dst++) { 187 | /* Parent already distributed its fds. */ 188 | if (src == PROC_PARENT || dst == PROC_PARENT) 189 | continue; 190 | 191 | proc_open(ps, src, dst); 192 | } 193 | } 194 | 195 | void 196 | proc_init(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc, 197 | int debug, int argc, char **argv, enum privsep_procid proc_id) 198 | { 199 | struct privsep_proc *p = NULL; 200 | struct privsep_pipes *pa, *pb; 201 | unsigned int proc; 202 | unsigned int dst; 203 | int fds[2]; 204 | 205 | /* Don't initiate anything if we are not really going to run. */ 206 | if (ps->ps_noaction) 207 | return; 208 | 209 | if (proc_id == PROC_PARENT) { 210 | privsep_process = PROC_PARENT; 211 | proc_setup(ps, procs, nproc); 212 | 213 | /* 214 | * Create the children sockets so we can use them 215 | * to distribute the rest of the socketpair()s using 216 | * proc_connect() later. 217 | */ 218 | for (dst = 0; dst < PROC_MAX; dst++) { 219 | /* Don't create socket for ourselves. */ 220 | if (dst == PROC_PARENT) 221 | continue; 222 | 223 | for (proc = 0; proc < ps->ps_instances[dst]; proc++) { 224 | pa = &ps->ps_pipes[PROC_PARENT][0]; 225 | pb = &ps->ps_pipes[dst][proc]; 226 | if (socketpair(AF_UNIX, 227 | SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 228 | PF_UNSPEC, fds) == -1) 229 | fatal("%s: socketpair", __func__); 230 | 231 | pa->pp_pipes[dst][proc] = fds[0]; 232 | pb->pp_pipes[PROC_PARENT][0] = fds[1]; 233 | } 234 | } 235 | 236 | /* Engage! */ 237 | proc_exec(ps, procs, nproc, debug, argc, argv); 238 | return; 239 | } 240 | 241 | /* Initialize a child */ 242 | for (proc = 0; proc < nproc; proc++) { 243 | if (procs[proc].p_id != proc_id) 244 | continue; 245 | p = &procs[proc]; 246 | break; 247 | } 248 | if (p == NULL || p->p_init == NULL) 249 | fatalx("%s: process %d missing process initialization", 250 | __func__, proc_id); 251 | 252 | p->p_init(ps, p); 253 | 254 | fatalx("failed to initiate child process"); 255 | } 256 | 257 | void 258 | proc_accept(struct privsep *ps, int fd, enum privsep_procid dst, 259 | unsigned int n) 260 | { 261 | struct privsep_pipes *pp = ps->ps_pp; 262 | struct imsgev *iev; 263 | 264 | if (ps->ps_ievs[dst] == NULL) { 265 | #if DEBUG > 1 266 | log_debug("%s: %s src %d %d to dst %d %d not connected", 267 | __func__, ps->ps_title[privsep_process], 268 | privsep_process, ps->ps_instance + 1, 269 | dst, n + 1); 270 | #endif 271 | close(fd); 272 | return; 273 | } 274 | 275 | if (pp->pp_pipes[dst][n] != -1) { 276 | log_warnx("%s: duplicated descriptor", __func__); 277 | close(fd); 278 | return; 279 | } else 280 | pp->pp_pipes[dst][n] = fd; 281 | 282 | iev = &ps->ps_ievs[dst][n]; 283 | imsg_init(&iev->ibuf, fd); 284 | event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data); 285 | event_add(&iev->ev, NULL); 286 | } 287 | 288 | void 289 | proc_setup(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc) 290 | { 291 | unsigned int i, j, src, dst, id; 292 | struct privsep_pipes *pp; 293 | 294 | /* Initialize parent title, ps_instances and procs. */ 295 | ps->ps_title[PROC_PARENT] = "parent"; 296 | 297 | for (src = 0; src < PROC_MAX; src++) 298 | /* Default to 1 process instance */ 299 | if (ps->ps_instances[src] < 1) 300 | ps->ps_instances[src] = 1; 301 | 302 | for (src = 0; src < nproc; src++) { 303 | procs[src].p_ps = ps; 304 | if (procs[src].p_cb == NULL) 305 | procs[src].p_cb = proc_dispatch_null; 306 | 307 | id = procs[src].p_id; 308 | ps->ps_title[id] = procs[src].p_title; 309 | if ((ps->ps_ievs[id] = calloc(ps->ps_instances[id], 310 | sizeof(struct imsgev))) == NULL) 311 | fatal("%s: calloc", __func__); 312 | 313 | /* With this set up, we are ready to call imsg_init(). */ 314 | for (i = 0; i < ps->ps_instances[id]; i++) { 315 | ps->ps_ievs[id][i].handler = proc_dispatch; 316 | ps->ps_ievs[id][i].events = EV_READ; 317 | ps->ps_ievs[id][i].proc = &procs[src]; 318 | ps->ps_ievs[id][i].data = &ps->ps_ievs[id][i]; 319 | } 320 | } 321 | 322 | /* 323 | * Allocate pipes for all process instances (incl. parent) 324 | * 325 | * - ps->ps_pipes: N:M mapping 326 | * N source processes connected to M destination processes: 327 | * [src][instances][dst][instances], for example 328 | * [PROC_RELAY][3][PROC_CA][3] 329 | * 330 | * - ps->ps_pp: per-process 1:M part of ps->ps_pipes 331 | * Each process instance has a destination array of socketpair fds: 332 | * [dst][instances], for example 333 | * [PROC_PARENT][0] 334 | */ 335 | for (src = 0; src < PROC_MAX; src++) { 336 | /* Allocate destination array for each process */ 337 | if ((ps->ps_pipes[src] = calloc(ps->ps_instances[src], 338 | sizeof(struct privsep_pipes))) == NULL) 339 | fatal("%s: calloc", __func__); 340 | 341 | for (i = 0; i < ps->ps_instances[src]; i++) { 342 | pp = &ps->ps_pipes[src][i]; 343 | 344 | for (dst = 0; dst < PROC_MAX; dst++) { 345 | /* Allocate maximum fd integers */ 346 | if ((pp->pp_pipes[dst] = 347 | calloc(ps->ps_instances[dst], 348 | sizeof(int))) == NULL) 349 | fatal("%s: calloc", __func__); 350 | 351 | /* Mark fd as unused */ 352 | for (j = 0; j < ps->ps_instances[dst]; j++) 353 | pp->pp_pipes[dst][j] = -1; 354 | } 355 | } 356 | } 357 | 358 | ps->ps_pp = &ps->ps_pipes[privsep_process][ps->ps_instance]; 359 | } 360 | 361 | void 362 | proc_kill(struct privsep *ps) 363 | { 364 | char *cause; 365 | pid_t pid; 366 | int len, status; 367 | 368 | if (privsep_process != PROC_PARENT) 369 | return; 370 | 371 | proc_close(ps); 372 | 373 | do { 374 | pid = waitpid(WAIT_ANY, &status, 0); 375 | if (pid <= 0) 376 | continue; 377 | 378 | if (WIFSIGNALED(status)) { 379 | len = asprintf(&cause, "terminated; signal %d", 380 | WTERMSIG(status)); 381 | } else if (WIFEXITED(status)) { 382 | if (WEXITSTATUS(status) != 0) 383 | len = asprintf(&cause, "exited abnormally"); 384 | else 385 | len = 0; 386 | } else 387 | len = -1; 388 | 389 | if (len == 0) { 390 | /* child exited OK, don't print a warning message */ 391 | } else if (len != -1) { 392 | log_warnx("lost child: pid %u %s", pid, cause); 393 | free(cause); 394 | } else 395 | log_warnx("lost child: pid %u", pid); 396 | } while (pid != -1 || errno == EINTR); 397 | } 398 | 399 | void 400 | proc_open(struct privsep *ps, int src, int dst) 401 | { 402 | struct privsep_pipes *pa, *pb; 403 | struct privsep_fd pf; 404 | int fds[2]; 405 | unsigned int i, j; 406 | 407 | /* Exchange pipes between process. */ 408 | for (i = 0; i < ps->ps_instances[src]; i++) { 409 | for (j = 0; j < ps->ps_instances[dst]; j++) { 410 | /* Don't create sockets for ourself. */ 411 | if (src == dst && i == j) 412 | continue; 413 | 414 | /* Servers don't talk to each other. */ 415 | if (src == PROC_SERVER && dst == PROC_SERVER) 416 | continue; 417 | 418 | pa = &ps->ps_pipes[src][i]; 419 | pb = &ps->ps_pipes[dst][j]; 420 | if (socketpair(AF_UNIX, 421 | SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 422 | PF_UNSPEC, fds) == -1) 423 | fatal("%s: socketpair", __func__); 424 | 425 | pa->pp_pipes[dst][j] = fds[0]; 426 | pb->pp_pipes[src][i] = fds[1]; 427 | 428 | pf.pf_procid = src; 429 | pf.pf_instance = i; 430 | if (proc_compose_imsg(ps, dst, j, IMSG_CTL_PROCFD, 431 | -1, pb->pp_pipes[src][i], &pf, sizeof(pf)) == -1) 432 | fatal("%s: proc_compose_imsg", __func__); 433 | 434 | pf.pf_procid = dst; 435 | pf.pf_instance = j; 436 | if (proc_compose_imsg(ps, src, i, IMSG_CTL_PROCFD, 437 | -1, pa->pp_pipes[dst][j], &pf, sizeof(pf)) == -1) 438 | fatal("%s: proc_compose_imsg", __func__); 439 | 440 | /* 441 | * We have to flush to send the descriptors and close 442 | * them to avoid the fd ramp on startup. 443 | */ 444 | if (proc_flush_imsg(ps, src, i) == -1 || 445 | proc_flush_imsg(ps, dst, j) == -1) 446 | fatal("%s: imsg_flush", __func__); 447 | } 448 | } 449 | } 450 | 451 | void 452 | proc_close(struct privsep *ps) 453 | { 454 | unsigned int dst, n; 455 | struct privsep_pipes *pp; 456 | 457 | if (ps == NULL) 458 | return; 459 | 460 | pp = ps->ps_pp; 461 | 462 | for (dst = 0; dst < PROC_MAX; dst++) { 463 | if (ps->ps_ievs[dst] == NULL) 464 | continue; 465 | 466 | for (n = 0; n < ps->ps_instances[dst]; n++) { 467 | if (pp->pp_pipes[dst][n] == -1) 468 | continue; 469 | 470 | /* Cancel the fd, close and invalidate the fd */ 471 | event_del(&(ps->ps_ievs[dst][n].ev)); 472 | imsg_clear(&(ps->ps_ievs[dst][n].ibuf)); 473 | close(pp->pp_pipes[dst][n]); 474 | pp->pp_pipes[dst][n] = -1; 475 | } 476 | free(ps->ps_ievs[dst]); 477 | } 478 | } 479 | 480 | void 481 | proc_shutdown(struct privsep_proc *p) 482 | { 483 | struct privsep *ps = p->p_ps; 484 | 485 | #ifdef USE_BLACKLIST 486 | BLACKLIST_STOP(); 487 | #endif 488 | 489 | if (p->p_id == PROC_CONTROL && ps) 490 | control_cleanup(&ps->ps_csock); 491 | 492 | if (p->p_shutdown != NULL) 493 | (*p->p_shutdown)(); 494 | 495 | proc_close(ps); 496 | 497 | log_info("%s exiting, pid %d", p->p_title, getpid()); 498 | 499 | exit(0); 500 | } 501 | 502 | void 503 | proc_sig_handler(int sig, short event, void *arg) 504 | { 505 | struct privsep_proc *p = arg; 506 | 507 | switch (sig) { 508 | case SIGINT: 509 | case SIGTERM: 510 | proc_shutdown(p); 511 | break; 512 | case SIGCHLD: 513 | case SIGHUP: 514 | case SIGPIPE: 515 | case SIGUSR1: 516 | /* ignore */ 517 | break; 518 | default: 519 | fatalx("%s: unexpected signal", __func__); 520 | /* NOTREACHED */ 521 | } 522 | } 523 | 524 | void 525 | proc_run(struct privsep *ps, struct privsep_proc *p, 526 | struct privsep_proc *procs, unsigned int nproc, 527 | void (*run)(struct privsep *, struct privsep_proc *, void *), void *arg) 528 | { 529 | struct passwd *pw; 530 | const char *root; 531 | struct control_sock *rcs; 532 | 533 | log_procinit(p->p_title); 534 | 535 | /* Set the process group of the current process */ 536 | setpgid(0, 0); 537 | 538 | if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) { 539 | if (control_init(ps, &ps->ps_csock) == -1) 540 | fatalx("%s: control_init", __func__); 541 | TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry) 542 | if (control_init(ps, rcs) == -1) 543 | fatalx("%s: control_init", __func__); 544 | } 545 | 546 | /* Use non-standard user */ 547 | if (p->p_pw != NULL) 548 | pw = p->p_pw; 549 | else 550 | pw = ps->ps_pw; 551 | 552 | /* Change root directory */ 553 | if (p->p_chroot != NULL) 554 | root = p->p_chroot; 555 | else 556 | root = pw->pw_dir; 557 | 558 | if (chroot(root) == -1) 559 | fatal("%s: chroot", __func__); 560 | if (chdir("/") == -1) 561 | fatal("%s: chdir(\"/\")", __func__); 562 | 563 | privsep_process = p->p_id; 564 | 565 | setproctitle("%s", p->p_title); 566 | 567 | if (setgroups(1, &pw->pw_gid) || 568 | setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 569 | setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 570 | fatal("%s: cannot drop privileges", __func__); 571 | 572 | event_init(); 573 | 574 | signal_set(&ps->ps_evsigint, SIGINT, proc_sig_handler, p); 575 | signal_set(&ps->ps_evsigterm, SIGTERM, proc_sig_handler, p); 576 | signal_set(&ps->ps_evsigchld, SIGCHLD, proc_sig_handler, p); 577 | signal_set(&ps->ps_evsighup, SIGHUP, proc_sig_handler, p); 578 | signal_set(&ps->ps_evsigpipe, SIGPIPE, proc_sig_handler, p); 579 | signal_set(&ps->ps_evsigusr1, SIGUSR1, proc_sig_handler, p); 580 | 581 | signal_add(&ps->ps_evsigint, NULL); 582 | signal_add(&ps->ps_evsigterm, NULL); 583 | signal_add(&ps->ps_evsigchld, NULL); 584 | signal_add(&ps->ps_evsighup, NULL); 585 | signal_add(&ps->ps_evsigpipe, NULL); 586 | signal_add(&ps->ps_evsigusr1, NULL); 587 | 588 | proc_setup(ps, procs, nproc); 589 | proc_accept(ps, PROC_PARENT_SOCK_FILENO, PROC_PARENT, 0); 590 | if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) { 591 | if (control_listen(&ps->ps_csock) == -1) 592 | fatalx("%s: control_listen", __func__); 593 | TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry) 594 | if (control_listen(rcs) == -1) 595 | fatalx("%s: control_listen", __func__); 596 | } 597 | 598 | DPRINTF("%s: %s %d/%d, pid %d", __func__, p->p_title, 599 | ps->ps_instance + 1, ps->ps_instances[p->p_id], getpid()); 600 | 601 | if (run != NULL) 602 | run(ps, p, arg); 603 | 604 | event_dispatch(); 605 | 606 | proc_shutdown(p); 607 | } 608 | 609 | void 610 | proc_dispatch(int fd, short event, void *arg) 611 | { 612 | struct imsgev *iev = arg; 613 | struct privsep_proc *p = iev->proc; 614 | struct privsep *ps = p->p_ps; 615 | struct imsgbuf *ibuf; 616 | struct imsg imsg; 617 | ssize_t n; 618 | int verbose; 619 | const char *title; 620 | struct privsep_fd pf; 621 | 622 | title = ps->ps_title[privsep_process]; 623 | ibuf = &iev->ibuf; 624 | 625 | if (event & EV_READ) { 626 | if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) 627 | fatal("%s: imsg_read", __func__); 628 | if (n == 0) { 629 | /* this pipe is dead, so remove the event handler */ 630 | event_del(&iev->ev); 631 | event_loopexit(NULL); 632 | return; 633 | } 634 | } 635 | 636 | if (event & EV_WRITE) { 637 | if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) 638 | fatal("%s: msgbuf_write", __func__); 639 | if (n == 0) { 640 | /* this pipe is dead, so remove the event handler */ 641 | event_del(&iev->ev); 642 | event_loopexit(NULL); 643 | return; 644 | } 645 | } 646 | 647 | for (;;) { 648 | if ((n = imsg_get(ibuf, &imsg)) == -1) 649 | fatal("%s: imsg_get", __func__); 650 | if (n == 0) 651 | break; 652 | 653 | #if DEBUG > 1 654 | log_debug("%s: %s %d got imsg %d peerid %d from %s %d", 655 | __func__, title, ps->ps_instance + 1, 656 | imsg.hdr.type, imsg.hdr.peerid, p->p_title, imsg.hdr.pid); 657 | #endif 658 | 659 | /* 660 | * Check the message with the program callback 661 | */ 662 | if ((p->p_cb)(fd, p, &imsg) == 0) { 663 | /* Message was handled by the callback, continue */ 664 | imsg_free(&imsg); 665 | continue; 666 | } 667 | 668 | /* 669 | * Generic message handling 670 | */ 671 | switch (imsg.hdr.type) { 672 | case IMSG_CTL_VERBOSE: 673 | IMSG_SIZE_CHECK(&imsg, &verbose); 674 | memcpy(&verbose, imsg.data, sizeof(verbose)); 675 | log_setverbose(verbose); 676 | break; 677 | case IMSG_CTL_PROCFD: 678 | IMSG_SIZE_CHECK(&imsg, &pf); 679 | memcpy(&pf, imsg.data, sizeof(pf)); 680 | proc_accept(ps, imsg.fd, pf.pf_procid, 681 | pf.pf_instance); 682 | break; 683 | default: 684 | fatalx("%s: %s %d got invalid imsg %d peerid %d " 685 | "from %s %d", 686 | __func__, title, ps->ps_instance + 1, 687 | imsg.hdr.type, imsg.hdr.peerid, 688 | p->p_title, imsg.hdr.pid); 689 | } 690 | imsg_free(&imsg); 691 | } 692 | imsg_event_add(iev); 693 | } 694 | 695 | int 696 | proc_dispatch_null(int fd, struct privsep_proc *p, struct imsg *imsg) 697 | { 698 | return (-1); 699 | } 700 | 701 | /* 702 | * imsg helper functions 703 | */ 704 | 705 | void 706 | imsg_event_add(struct imsgev *iev) 707 | { 708 | if (iev->handler == NULL) { 709 | imsg_flush(&iev->ibuf); 710 | return; 711 | } 712 | 713 | iev->events = EV_READ; 714 | if (iev->ibuf.w.queued) 715 | iev->events |= EV_WRITE; 716 | 717 | event_del(&iev->ev); 718 | event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data); 719 | event_add(&iev->ev, NULL); 720 | } 721 | 722 | int 723 | imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid, 724 | pid_t pid, int fd, void *data, uint16_t datalen) 725 | { 726 | int ret; 727 | 728 | if ((ret = imsg_compose(&iev->ibuf, type, peerid, 729 | pid, fd, data, datalen)) == -1) 730 | return (ret); 731 | imsg_event_add(iev); 732 | return (ret); 733 | } 734 | 735 | int 736 | imsg_composev_event(struct imsgev *iev, uint16_t type, uint32_t peerid, 737 | pid_t pid, int fd, const struct iovec *iov, int iovcnt) 738 | { 739 | int ret; 740 | 741 | if ((ret = imsg_composev(&iev->ibuf, type, peerid, 742 | pid, fd, iov, iovcnt)) == -1) 743 | return (ret); 744 | imsg_event_add(iev); 745 | return (ret); 746 | } 747 | 748 | void 749 | proc_range(struct privsep *ps, enum privsep_procid id, int *n, int *m) 750 | { 751 | if (*n == -1) { 752 | /* Use a range of all target instances */ 753 | *n = 0; 754 | *m = ps->ps_instances[id]; 755 | } else { 756 | /* Use only a single slot of the specified peer process */ 757 | *m = *n + 1; 758 | } 759 | } 760 | 761 | int 762 | proc_compose_imsg(struct privsep *ps, enum privsep_procid id, int n, 763 | uint16_t type, uint32_t peerid, int fd, void *data, uint16_t datalen) 764 | { 765 | int m; 766 | 767 | proc_range(ps, id, &n, &m); 768 | for (; n < m; n++) { 769 | if (imsg_compose_event(&ps->ps_ievs[id][n], 770 | type, peerid, ps->ps_instance + 1, fd, data, datalen) == -1) 771 | return (-1); 772 | } 773 | 774 | return (0); 775 | } 776 | 777 | int 778 | proc_compose(struct privsep *ps, enum privsep_procid id, 779 | uint16_t type, void *data, uint16_t datalen) 780 | { 781 | return (proc_compose_imsg(ps, id, -1, type, -1, -1, data, datalen)); 782 | } 783 | 784 | int 785 | proc_composev_imsg(struct privsep *ps, enum privsep_procid id, int n, 786 | uint16_t type, uint32_t peerid, int fd, const struct iovec *iov, int iovcnt) 787 | { 788 | int m; 789 | 790 | proc_range(ps, id, &n, &m); 791 | for (; n < m; n++) 792 | if (imsg_composev_event(&ps->ps_ievs[id][n], 793 | type, peerid, ps->ps_instance + 1, fd, iov, iovcnt) == -1) 794 | return (-1); 795 | 796 | return (0); 797 | } 798 | 799 | int 800 | proc_composev(struct privsep *ps, enum privsep_procid id, 801 | uint16_t type, const struct iovec *iov, int iovcnt) 802 | { 803 | return (proc_composev_imsg(ps, id, -1, type, -1, -1, iov, iovcnt)); 804 | } 805 | 806 | int 807 | proc_forward_imsg(struct privsep *ps, struct imsg *imsg, 808 | enum privsep_procid id, int n) 809 | { 810 | return (proc_compose_imsg(ps, id, n, imsg->hdr.type, 811 | imsg->hdr.peerid, imsg->fd, imsg->data, IMSG_DATA_SIZE(imsg))); 812 | } 813 | 814 | struct imsgbuf * 815 | proc_ibuf(struct privsep *ps, enum privsep_procid id, int n) 816 | { 817 | int m; 818 | 819 | proc_range(ps, id, &n, &m); 820 | return (&ps->ps_ievs[id][n].ibuf); 821 | } 822 | 823 | struct imsgev * 824 | proc_iev(struct privsep *ps, enum privsep_procid id, int n) 825 | { 826 | int m; 827 | 828 | proc_range(ps, id, &n, &m); 829 | return (&ps->ps_ievs[id][n]); 830 | } 831 | 832 | /* This function should only be called with care as it breaks async I/O */ 833 | int 834 | proc_flush_imsg(struct privsep *ps, enum privsep_procid id, int n) 835 | { 836 | struct imsgbuf *ibuf; 837 | int m, ret = 0; 838 | 839 | proc_range(ps, id, &n, &m); 840 | for (; n < m; n++) { 841 | if ((ibuf = proc_ibuf(ps, id, n)) == NULL) 842 | return (-1); 843 | do { 844 | ret = imsg_flush(ibuf); 845 | } while (ret == -1 && errno == EAGAIN); 846 | if (ret == -1) 847 | break; 848 | imsg_event_add(&ps->ps_ievs[id][n]); 849 | } 850 | 851 | return (ret); 852 | } 853 | --------------------------------------------------------------------------------