aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE.md30
-rw-r--r--httpd/Makefile5
-rw-r--r--httpd/config.c150
-rw-r--r--httpd/control.c3
-rw-r--r--httpd/http.h8
-rw-r--r--httpd/httpd.c18
-rw-r--r--httpd/httpd.conf.589
-rw-r--r--httpd/httpd.h29
-rw-r--r--httpd/parse.y75
-rw-r--r--httpd/patterns.7309
-rw-r--r--httpd/patterns.c712
-rw-r--r--httpd/patterns.h47
-rw-r--r--httpd/server.c55
-rw-r--r--httpd/server_fcgi.c5
-rw-r--r--httpd/server_file.c38
-rw-r--r--httpd/server_http.c255
-rw-r--r--regress/Makefile5
-rw-r--r--regress/patterns/Makefile32
-rw-r--r--regress/patterns/patterns-tester.c98
-rw-r--r--regress/patterns/patterns-tester.lua3
-rw-r--r--regress/patterns/test-patterns-lua.out127
-rw-r--r--regress/patterns/test-patterns.in27
-rw-r--r--regress/patterns/test-patterns.out99
-rw-r--r--regress/tests/Client.pm75
-rw-r--r--regress/tests/Httpd.pm93
-rw-r--r--regress/tests/LICENSE14
-rw-r--r--regress/tests/Makefile105
-rw-r--r--regress/tests/Proc.pm200
-rw-r--r--regress/tests/README20
-rw-r--r--regress/tests/args-default.pl11
-rw-r--r--regress/tests/args-get-1048576.pl15
-rw-r--r--regress/tests/args-get-1073741824.pl16
-rw-r--r--regress/tests/args-get-512.pl16
-rw-r--r--regress/tests/args-get-slash.pl20
-rw-r--r--regress/tests/args-log-user-agent.pl17
-rw-r--r--regress/tests/args-tls-get-1073741824.pl18
-rw-r--r--regress/tests/args-tls.pl18
-rw-r--r--regress/tests/funcs.pl487
-rw-r--r--regress/tests/httpd.pl73
39 files changed, 3233 insertions, 184 deletions
diff --git a/LICENSE.md b/LICENSE.md
index 43a3745..ad978cf 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -23,3 +23,33 @@ httpd is free software under OpenBSD's ISC-style license.
> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+* One exception is
+[`patterns.c`](https://github.com/reyk/httpd/blob/master/httpd/patterns.c)
+that is derived from the pattern matching code in Lua's `lstrlib.c`.
+
+> Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
+> Copyright (C) 1994-2015 Lua.org, PUC-Rio.
+>
+> Permission is hereby granted, free of charge, to any person obtaining
+> a copy of this software and associated documentation files (the
+> "Software"), to deal in the Software without restriction, including
+> without limitation the rights to use, copy, modify, merge, publish,
+> distribute, sublicense, and/or sell copies of the Software, and to
+> permit persons to whom the Software is furnished to do so, subject to
+> the following conditions:
+>
+> The above copyright notice and this permission notice shall be
+> included in all copies or substantial portions of the Software.
+>
+> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+>
+> Derived from Lua 5.3.1:
+> $Id: patterns.c,v 1.2 2015/06/23 15:35:20 semarie Exp $
+> Standard library for string operations and pattern-matching
diff --git a/httpd/Makefile b/httpd/Makefile
index 885ad42..e01dec1 100644
--- a/httpd/Makefile
+++ b/httpd/Makefile
@@ -1,4 +1,4 @@
-# $OpenBSD: Makefile,v 1.27 2015/02/23 10:39:10 reyk Exp $
+# $OpenBSD: Makefile,v 1.28 2015/06/23 15:23:14 reyk Exp $
PROG= httpd
SRCS= parse.y
@@ -6,6 +6,9 @@ SRCS+= config.c control.c httpd.c log.c logger.c proc.c
SRCS+= server.c server_http.c server_file.c server_fcgi.c
MAN= httpd.8 httpd.conf.5
+SRCS+= patterns.c
+MAN+= patterns.7
+
LDADD= -levent -ltls -lssl -lcrypto -lutil
DPADD= ${LIBEVENT} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} ${LIBUTIL}
#DEBUG= -g -DDEBUG=3 -O0
diff --git a/httpd/config.c b/httpd/config.c
index 7635c33..3c88990 100644
--- a/httpd/config.c
+++ b/httpd/config.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: config.c,v 1.37 2015/04/11 14:52:49 jsing Exp $ */
+/* $OpenBSD: config.c,v 1.39 2015/07/15 16:00:39 jsing Exp $ */
/*
* Copyright (c) 2011 - 2015 Reyk Floeter <reyk@openbsd.org>
@@ -193,14 +193,6 @@ config_setserver(struct httpd *env, struct server *srv)
iov[c].iov_base = srv->srv_conf.return_uri;
iov[c++].iov_len = srv->srv_conf.return_uri_len;
}
- if (srv->srv_conf.tls_cert_len != 0) {
- iov[c].iov_base = srv->srv_conf.tls_cert;
- iov[c++].iov_len = srv->srv_conf.tls_cert_len;
- }
- if (srv->srv_conf.tls_key_len != 0) {
- iov[c].iov_base = srv->srv_conf.tls_key;
- iov[c++].iov_len = srv->srv_conf.tls_key_len;
- }
if (id == PROC_SERVER &&
(srv->srv_conf.flags & SRVFLAG_LOCATION) == 0) {
@@ -220,6 +212,9 @@ config_setserver(struct httpd *env, struct server *srv)
return (-1);
}
}
+
+ /* Configure TLS if necessary. */
+ config_settls(env, srv);
} else {
if (proc_composev_imsg(ps, id, -1, IMSG_CFG_SERVER, -1,
iov, c) != 0) {
@@ -235,6 +230,72 @@ config_setserver(struct httpd *env, struct server *srv)
}
int
+config_settls(struct httpd *env, struct server *srv)
+{
+ struct privsep *ps = env->sc_ps;
+ struct tls_config tls;
+ struct iovec iov[2];
+ size_t c;
+
+ if ((srv->srv_conf.flags & SRVFLAG_TLS) == 0)
+ return (0);
+
+ log_debug("%s: configuring TLS for %s", __func__, srv->srv_conf.name);
+
+ if (srv->srv_conf.tls_cert_len != 0) {
+ DPRINTF("%s: sending TLS cert \"%s[%u]\" to %s fd %d", __func__,
+ srv->srv_conf.name, srv->srv_conf.id,
+ ps->ps_title[PROC_SERVER], srv->srv_s);
+
+ memset(&tls, 0, sizeof(tls));
+ tls.id = srv->srv_conf.id;
+ tls.port = srv->srv_conf.port;
+ memcpy(&tls.ss, &srv->srv_conf.ss, sizeof(tls.ss));
+ tls.tls_cert_len = srv->srv_conf.tls_cert_len;
+
+ c = 0;
+ iov[c].iov_base = &tls;
+ iov[c++].iov_len = sizeof(tls);
+ iov[c].iov_base = srv->srv_conf.tls_cert;
+ iov[c++].iov_len = srv->srv_conf.tls_cert_len;
+
+ if (proc_composev_imsg(ps, PROC_SERVER, -1, IMSG_CFG_TLS, -1,
+ iov, c) != 0) {
+ log_warn("%s: failed to compose IMSG_CFG_TLS imsg for "
+ "`%s'", __func__, srv->srv_conf.name);
+ return (-1);
+ }
+ }
+
+ if (srv->srv_conf.tls_key_len != 0) {
+ DPRINTF("%s: sending TLS key \"%s[%u]\" to %s fd %d", __func__,
+ srv->srv_conf.name, srv->srv_conf.id,
+ ps->ps_title[PROC_SERVER], srv->srv_s);
+
+ memset(&tls, 0, sizeof(tls));
+ tls.id = srv->srv_conf.id;
+ tls.port = srv->srv_conf.port;
+ memcpy(&tls.ss, &srv->srv_conf.ss, sizeof(tls.ss));
+ tls.tls_key_len = srv->srv_conf.tls_key_len;
+
+ c = 0;
+ iov[c].iov_base = &tls;
+ iov[c++].iov_len = sizeof(tls);
+ iov[c].iov_base = srv->srv_conf.tls_key;
+ iov[c++].iov_len = srv->srv_conf.tls_key_len;
+
+ if (proc_composev_imsg(ps, PROC_SERVER, -1, IMSG_CFG_TLS, -1,
+ iov, c) != 0) {
+ log_warn("%s: failed to compose IMSG_CFG_TLS imsg for "
+ "`%s'", __func__, srv->srv_conf.name);
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+int
config_getserver_auth(struct httpd *env, struct server_config *srv_conf)
{
struct privsep *ps = env->sc_ps;
@@ -422,9 +483,7 @@ config_getserver(struct httpd *env, struct imsg *imsg)
/* Reset these variables to avoid free'ing invalid pointers */
serverconfig_reset(&srv_conf);
- if ((IMSG_DATA_SIZE(imsg) - s) <
- (srv_conf.tls_cert_len + srv_conf.tls_key_len +
- srv_conf.return_uri_len)) {
+ if ((IMSG_DATA_SIZE(imsg) - s) < (size_t)srv_conf.return_uri_len) {
log_debug("%s: invalid message length", __func__);
goto fail;
}
@@ -475,30 +534,69 @@ config_getserver(struct httpd *env, struct imsg *imsg)
goto fail;
s += srv->srv_conf.return_uri_len;
}
- if (srv->srv_conf.tls_cert_len != 0) {
+
+ return (0);
+
+ fail:
+ if (imsg->fd != -1)
+ close(imsg->fd);
+ if (srv != NULL)
+ serverconfig_free(&srv->srv_conf);
+ free(srv);
+
+ return (-1);
+}
+
+int
+config_gettls(struct httpd *env, struct imsg *imsg)
+{
+#ifdef DEBUG
+ struct privsep *ps = env->sc_ps;
+#endif
+ struct server *srv = NULL;
+ struct tls_config tls_conf;
+ u_int8_t *p = imsg->data;
+ size_t s;
+
+ IMSG_SIZE_CHECK(imsg, &tls_conf);
+ memcpy(&tls_conf, p, sizeof(tls_conf));
+ s = sizeof(tls_conf);
+
+ if ((IMSG_DATA_SIZE(imsg) - s) <
+ (tls_conf.tls_cert_len + tls_conf.tls_key_len)) {
+ log_debug("%s: invalid message length", __func__);
+ goto fail;
+ }
+
+ /* Find server with matching listening socket. */
+ if ((srv = server_byaddr((struct sockaddr *)
+ &tls_conf.ss, tls_conf.port)) == NULL) {
+ log_debug("%s: server not found", __func__);
+ goto fail;
+ }
+
+ DPRINTF("%s: %s %d TLS configuration \"%s[%u]\"", __func__,
+ ps->ps_title[privsep_process], ps->ps_instance,
+ srv->srv_conf.name, srv->srv_conf.id);
+
+ if (tls_conf.tls_cert_len != 0) {
+ srv->srv_conf.tls_cert_len = tls_conf.tls_cert_len;
if ((srv->srv_conf.tls_cert = get_data(p + s,
- srv->srv_conf.tls_cert_len)) == NULL)
+ tls_conf.tls_cert_len)) == NULL)
goto fail;
- s += srv->srv_conf.tls_cert_len;
+ s += tls_conf.tls_cert_len;
}
- if (srv->srv_conf.tls_key_len != 0) {
+ if (tls_conf.tls_key_len != 0) {
+ srv->srv_conf.tls_key_len = tls_conf.tls_key_len;
if ((srv->srv_conf.tls_key = get_data(p + s,
- srv->srv_conf.tls_key_len)) == NULL)
+ tls_conf.tls_key_len)) == NULL)
goto fail;
- s += srv->srv_conf.tls_key_len;
+ s += tls_conf.tls_key_len;
}
return (0);
fail:
- if (imsg->fd != -1)
- close(imsg->fd);
- if (srv != NULL) {
- free(srv->srv_conf.tls_cert);
- free(srv->srv_conf.tls_key);
- }
- free(srv);
-
return (-1);
}
diff --git a/httpd/control.c b/httpd/control.c
index fe60375..5c71545 100644
--- a/httpd/control.c
+++ b/httpd/control.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: control.c,v 1.6 2015/01/21 22:21:05 reyk Exp $ */
+/* $OpenBSD: control.c,v 1.7 2015/05/28 17:08:08 florian Exp $ */
/*
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -126,7 +126,6 @@ control_cleanup(struct control_sock *cs)
return;
event_del(&cs->cs_ev);
event_del(&cs->cs_evt);
- (void)unlink(cs->cs_name);
}
/* ARGSUSED */
diff --git a/httpd/http.h b/httpd/http.h
index 00cf235..9042342 100644
--- a/httpd/http.h
+++ b/httpd/http.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: http.h,v 1.12 2015/02/11 12:52:01 florian Exp $ */
+/* $OpenBSD: http.h,v 1.13 2015/06/11 18:49:09 reyk Exp $ */
/*
* Copyright (c) 2012 - 2015 Reyk Floeter <reyk@openbsd.org>
@@ -16,8 +16,8 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifndef _HTTP_H
-#define _HTTP_H
+#ifndef HTTP_H
+#define HTTP_H
#define HTTP_PORT 80
#define HTTPS_PORT 443
@@ -250,4 +250,4 @@ struct http_descriptor {
struct kvtree http_headers;
};
-#endif /* _HTTP_H */
+#endif /* HTTP_H */
diff --git a/httpd/httpd.c b/httpd/httpd.c
index c6f183f..5f6788c 100644
--- a/httpd/httpd.c
+++ b/httpd/httpd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: httpd.c,v 1.35 2015/02/23 18:43:18 reyk Exp $ */
+/* $OpenBSD: httpd.c,v 1.37 2015/06/03 02:24:36 millert Exp $ */
/*
* Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
@@ -80,6 +80,8 @@ parent_sig_handler(int sig, short event, void *arg)
/* FALLTHROUGH */
case SIGCHLD:
do {
+ int len;
+
pid = waitpid(WAIT_ANY, &status, WNOHANG);
if (pid <= 0)
continue;
@@ -87,17 +89,21 @@ parent_sig_handler(int sig, short event, void *arg)
fail = 0;
if (WIFSIGNALED(status)) {
fail = 1;
- asprintf(&cause, "terminated; signal %d",
+ len = asprintf(&cause, "terminated; signal %d",
WTERMSIG(status));
} else if (WIFEXITED(status)) {
if (WEXITSTATUS(status) != 0) {
fail = 1;
- asprintf(&cause, "exited abnormally");
+ len = asprintf(&cause,
+ "exited abnormally");
} else
- asprintf(&cause, "exited okay");
+ len = asprintf(&cause, "exited okay");
} else
fatalx("unexpected cause of SIGCHLD");
+ if (len == -1)
+ fatal("asprintf");
+
die = 1;
for (id = 0; id < PROC_MAX; id++)
@@ -210,7 +216,7 @@ main(int argc, char *argv[])
errx(1, "unknown user %s", HTTPD_USER);
/* Configure the control socket */
- ps->ps_csock.cs_name = HTTPD_SOCKET;
+ ps->ps_csock.cs_name = NULL;
log_init(debug);
log_verbose(verbose);
@@ -404,6 +410,8 @@ parent_shutdown(struct httpd *env)
proc_kill(env->sc_ps);
control_cleanup(&env->sc_ps->ps_csock);
+ if (env->sc_ps->ps_csock.cs_name != NULL)
+ (void)unlink(env->sc_ps->ps_csock.cs_name);
free(env->sc_ps);
free(env);
diff --git a/httpd/httpd.conf.5 b/httpd/httpd.conf.5
index 26d6798..b3eaad8 100644
--- a/httpd/httpd.conf.5
+++ b/httpd/httpd.conf.5
@@ -1,4 +1,4 @@
-.\" $OpenBSD: httpd.conf.5,v 1.57 2015/03/26 19:16:57 jmc Exp $
+.\" $OpenBSD: httpd.conf.5,v 1.64 2015/07/15 17:10:47 jsing Exp $
.\"
.\" Copyright (c) 2014, 2015 Reyk Floeter <reyk@openbsd.org>
.\"
@@ -14,7 +14,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: March 26 2015 $
+.Dd $Mdocdate: July 15 2015 $
.Dt HTTPD.CONF 5
.Os
.Sh NAME
@@ -131,14 +131,38 @@ The configured web servers.
.Pp
Each
.Ic server
-must have a
-.Ar name
-and include one or more lines of the following syntax:
+section starts with a declaration of the server
+.Ar name :
+.Bl -tag -width Ds
+.It Ic server Ar name Brq ...
+Match the server name using shell globbing rules.
+This can be an explicit name,
+.Ar www.example.com ,
+or a name including wildcards,
+.Ar *.example.com .
+.It Ic server match Ar name Brq ...
+Match the server name using pattern matching,
+see
+.Xr patterns 7 .
+.El
+.Pp
+Followed by a block of options that is enclosed in curly brackets:
.Bl -tag -width Ds
.It Ic alias Ar name
Specify an additional alias
.Ar name
for this server.
+.It Ic alias match Ar name
+Like the
+.Ic alias
+option,
+but
+.Ic match
+the
+.Ar name
+using pattern matching instead of shell globbing rules,
+see
+.Xr patterns 7 .
.It Oo Ic no Oc Ic authenticate Oo Ar realm Oc Ic with Pa htpasswd
Authenticate a remote user for
.Ar realm
@@ -152,7 +176,7 @@ Use the
directive to disable authentication in a location.
.It Ic block drop
Drop the connection without sending an error page.
-.It Ic block Op Ic return Ar code Op uri
+.It Ic block Op Ic return Ar code Op Ar uri
Close the connection and send an error page.
If the optional return code is not specified,
.Xr httpd 8
@@ -166,7 +190,7 @@ argument can be used with return codes in the 3xx range to send a
header for redirection to a specified URI.
.Pp
The
-.Ar url
+.Ar uri
may contain predefined macros that will be expanded at runtime:
.Pp
.Bl -tag -width $DOCUMENT_URI -offset indent -compact
@@ -188,6 +212,12 @@ The configured IP address of the server.
The configured TCP server port of the server.
.It Ic $SERVER_NAME
The name of the server.
+.It Pf % Ar n
+The capture index
+.Ar n
+of a string that was captured by the enclosing
+.Ic location match
+option.
.El
.It Ic connection Ar option
Set the specified options and limits for HTTP connections.
@@ -247,6 +277,22 @@ except
.Ic location
and
.Ic tcp .
+.It Ic location match Ar path Brq ...
+Like the
+.Ic location
+option,
+but
+.Ic match
+the
+.Ar path
+using pattern matching instead of shell globbing rules,
+see
+.Xr patterns 7 .
+The pattern may contain captures that can be used in the
+.Ar uri
+of an enclosed
+.Ic block return
+option.
.It Oo Ic no Oc Ic log Op Ar option
Set the specified logging options.
Logging is enabled by default using the standard
@@ -366,6 +412,8 @@ Specify the certificate to use for this server.
The
.Ar file
should contain a PEM encoded certificate.
+The default is
+.Pa /etc/ssl/server.crt .
.It Ic ciphers Ar string
Specify the TLS cipher string.
If not specified, the default value
@@ -392,6 +440,8 @@ should contain a PEM encoded private key and reside outside of the
.Xr chroot 2
root directory of
.Nm httpd .
+The default is
+.Pa /etc/ssl/private/server.key .
.It Ic protocols Ar string
Specify the TLS protocols to enable for this server.
If not specified, the value
@@ -451,14 +501,14 @@ server "default" {
}
types {
- text/css css
- text/html htm html
- text/txt txt
- image/gif gif
- image/jpeg jpg jpeg
- image/png png
- application/javascript js
- application/xml xml
+ text/css css
+ text/html html htm
+ text/txt txt
+ image/gif gif
+ image/jpeg jpeg jpg
+ image/png png
+ application/javascript js
+ application/xml xml
}
.Ed
.Pp
@@ -514,16 +564,9 @@ server "www.example.com" {
listen on 10.0.0.1 port 80
}
.Ed
-.Pp
-The syntax of the types section is also compatible with the format
-used by nginx, so it is possible to include its
-.Pa mime.types
-file directly:
-.Bd -literal -offset indent
-include "/etc/nginx/mime.types"
-.Ed
.Sh SEE ALSO
.Xr htpasswd 1 ,
+.Xr patterns 7 ,
.Xr httpd 8 ,
.Xr slowcgi 8
.Sh AUTHORS
diff --git a/httpd/httpd.h b/httpd/httpd.h
index 50f300a..2cb7934 100644
--- a/httpd/httpd.h
+++ b/httpd/httpd.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: httpd.h,v 1.82 2015/03/15 22:08:45 florian Exp $ */
+/* $OpenBSD: httpd.h,v 1.88 2015/07/16 16:29:25 florian Exp $ */
/*
* Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
@@ -34,6 +34,9 @@
#include <event.h>
#include <imsg.h>
#include <tls.h>
+#include <vis.h>
+
+#include "patterns.h"
#define CONF_FILE "/etc/httpd.conf"
#define HTTPD_SOCKET "/var/run/httpd.sock"
@@ -45,6 +48,7 @@
#define HTTPD_LOGROOT "/logs"
#define HTTPD_ACCESS_LOG "access.log"
#define HTTPD_ERROR_LOG "error.log"
+#define HTTPD_LOGVIS VIS_NL|VIS_TAB|VIS_CSTYLE
#define HTTPD_TLS_CERT "/etc/ssl/server.crt"
#define HTTPD_TLS_KEY "/etc/ssl/private/server.key"
#define HTTPD_TLS_CIPHERS "HIGH:!aNULL"
@@ -62,6 +66,8 @@
#define SERVER_MAXREQUESTBODY 1048576 /* 1M */
#define SERVER_BACKLOG 10
#define SERVER_OUTOF_FD_RETRIES 5
+#define SERVER_MAX_PREFETCH 256
+#define SERVER_MIN_PREFETCHED 32
#define MEDIATYPE_NAMEMAX 128 /* file name extension */
#define MEDIATYPE_TYPEMAX 64 /* length of type/subtype */
@@ -199,6 +205,7 @@ enum imsg_type {
IMSG_CTL_START,
IMSG_CTL_REOPEN,
IMSG_CFG_SERVER,
+ IMSG_CFG_TLS,
IMSG_CFG_MEDIA,
IMSG_CFG_AUTH,
IMSG_CFG_DONE,
@@ -278,6 +285,7 @@ struct client {
void *clt_srv_conf;
u_int32_t clt_srv_id;
struct sockaddr_storage clt_srv_ss;
+ struct str_match clt_srv_match;
int clt_s;
in_port_t clt_port;
@@ -341,12 +349,15 @@ SPLAY_HEAD(client_tree, client);
#define SRVFLAG_NO_AUTH 0x00020000
#define SRVFLAG_BLOCK 0x00040000
#define SRVFLAG_NO_BLOCK 0x00080000
+#define SRVFLAG_LOCATION_MATCH 0x00100000
+#define SRVFLAG_SERVER_MATCH 0x00200000
#define SRVFLAG_BITS \
"\10\01INDEX\02NO_INDEX\03AUTO_INDEX\04NO_AUTO_INDEX" \
"\05ROOT\06LOCATION\07FCGI\10NO_FCGI\11LOG\12NO_LOG\13SOCKET" \
"\14SYSLOG\15NO_SYSLOG\16TLS\17ACCESS_LOG\20ERROR_LOG" \
- "\21AUTH\22NO_AUTH\23BLOCK\24NO_BLOCK"
+ "\21AUTH\22NO_AUTH\23BLOCK\24NO_BLOCK\25LOCATION_MATCH" \
+ "\26SERVER_MATCH"
#define TCPFLAG_NODELAY 0x01
#define TCPFLAG_NNODELAY 0x02
@@ -436,6 +447,16 @@ struct server_config {
};
TAILQ_HEAD(serverhosts, server_config);
+struct tls_config {
+ u_int32_t id;
+
+ in_port_t port;
+ struct sockaddr_storage ss;
+
+ size_t tls_cert_len;
+ size_t tls_key_len;
+};
+
struct server {
TAILQ_ENTRY(server) srv_entry;
struct server_config srv_conf;
@@ -557,7 +578,7 @@ int server_headers(struct client *, void *,
int (*)(struct client *, struct kv *, void *), void *);
int server_writeresponse_http(struct client *);
int server_response_http(struct client *, u_int, struct media_type *,
- size_t, time_t);
+ off_t, time_t);
void server_reset_http(struct client *);
void server_close_http(struct client *);
int server_response(struct httpd *, struct client *);
@@ -674,7 +695,9 @@ int config_setreset(struct httpd *, u_int);
int config_getreset(struct httpd *, struct imsg *);
int config_getcfg(struct httpd *, struct imsg *);
int config_setserver(struct httpd *, struct server *);
+int config_settls(struct httpd *, struct server *);
int config_getserver(struct httpd *, struct imsg *);
+int config_gettls(struct httpd *, struct imsg *);
int config_setmedia(struct httpd *, struct media_type *);
int config_getmedia(struct httpd *, struct imsg *);
int config_setauth(struct httpd *, struct auth *);
diff --git a/httpd/parse.y b/httpd/parse.y
index 0aae421..0870819 100644
--- a/httpd/parse.y
+++ b/httpd/parse.y
@@ -1,4 +1,4 @@
-/* $OpenBSD: parse.y,v 1.67 2015/04/01 04:51:15 jsg Exp $ */
+/* $OpenBSD: parse.y,v 1.70 2015/07/16 19:05:28 reyk Exp $ */
/*
* Copyright (c) 2007 - 2015 Reyk Floeter <reyk@openbsd.org>
@@ -107,7 +107,7 @@ int host_if(const char *, struct addresslist *,
int host(const char *, struct addresslist *,
int, struct portrange *, const char *, int);
void host_free(struct addresslist *);
-struct server *server_inherit(struct server *, const char *,
+struct server *server_inherit(struct server *, struct server_config *,
struct server_config *);
int getservice(char *);
int is_if_in_group(const char *, const char *);
@@ -131,14 +131,14 @@ typedef struct {
%token ACCESS ALIAS AUTO BACKLOG BODY BUFFER CERTIFICATE CHROOT CIPHERS COMMON
%token COMBINED CONNECTION DHE DIRECTORY ECDHE ERR FCGI INDEX IP KEY LISTEN
-%token LOCATION LOG LOGDIR MAXIMUM NO NODELAY ON PORT PREFORK PROTOCOLS
+%token LOCATION LOG LOGDIR MATCH MAXIMUM NO NODELAY ON PORT PREFORK PROTOCOLS
%token REQUEST REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TIMEOUT
%token TLS TYPES
%token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS
%token <v.string> STRING
%token <v.number> NUMBER
%type <v.port> port
-%type <v.number> opttls
+%type <v.number> opttls optmatch
%type <v.tv> timeout
%type <v.string> numberstring optstring
%type <v.auth> authopts
@@ -200,26 +200,26 @@ main : PREFORK NUMBER {
}
;
-server : SERVER STRING {
+server : SERVER optmatch STRING {
struct server *s;
if (!loadcfg) {
- free($2);
+ free($3);
YYACCEPT;
}
if ((s = calloc(1, sizeof (*s))) == NULL)
fatal("out of memory");
- if (strlcpy(s->srv_conf.name, $2,
+ if (strlcpy(s->srv_conf.name, $3,
sizeof(s->srv_conf.name)) >=
sizeof(s->srv_conf.name)) {
yyerror("server name truncated");
- free($2);
+ free($3);
free(s);
YYERROR;
}
- free($2);
+ free($3);
strlcpy(s->srv_conf.root, HTTPD_DOCROOT,
sizeof(s->srv_conf.root));
@@ -235,7 +235,9 @@ server : SERVER STRING {
s->srv_conf.timeout.tv_sec = SERVER_TIMEOUT;
s->srv_conf.maxrequests = SERVER_MAXREQUESTS;
s->srv_conf.maxrequestbody = SERVER_MAXREQUESTBODY;
- s->srv_conf.flags |= SRVFLAG_LOG;
+ s->srv_conf.flags = SRVFLAG_LOG;
+ if ($2)
+ s->srv_conf.flags |= SRVFLAG_SERVER_MATCH;
s->srv_conf.logformat = LOG_FORMAT_COMMON;
s->srv_conf.tls_protocols = TLS_PROTOCOLS_DEFAULT;
if ((s->srv_conf.tls_cert_file =
@@ -334,7 +336,7 @@ server : SERVER STRING {
continue;
if ((sn = server_inherit(srv,
- b->name, a)) == NULL) {
+ b, a)) == NULL) {
serverconfig_free(srv_conf);
free(srv);
YYABORT;
@@ -405,30 +407,37 @@ serveroptsl : LISTEN ON STRING opttls port {
}
if (alias != NULL) {
+ /* IP-based; use name match flags from parent */
+ alias->flags &= ~SRVFLAG_SERVER_MATCH;
+ alias->flags |= srv->srv_conf.flags &
+ SRVFLAG_SERVER_MATCH;
TAILQ_INSERT_TAIL(&srv->srv_hosts,
alias, entry);
}
}
- | ALIAS STRING {
+ | ALIAS optmatch STRING {
struct server_config *alias;
if (parentsrv != NULL) {
yyerror("alias inside location");
- free($2);
+ free($3);
YYERROR;
}
if ((alias = calloc(1, sizeof(*alias))) == NULL)
fatal("out of memory");
- if (strlcpy(alias->name, $2, sizeof(alias->name)) >=
+ if (strlcpy(alias->name, $3, sizeof(alias->name)) >=
sizeof(alias->name)) {
yyerror("server alias truncated");
- free($2);
+ free($3);
free(alias);
YYERROR;
}
- free($2);
+ free($3);
+
+ if ($2)
+ alias->flags |= SRVFLAG_SERVER_MATCH;
TAILQ_INSERT_TAIL(&srv->srv_hosts, alias, entry);
}
@@ -456,38 +465,38 @@ serveroptsl : LISTEN ON STRING opttls port {
| fastcgi
| authenticate
| filter
- | LOCATION STRING {
+ | LOCATION optmatch STRING {
struct server *s;
if (srv->srv_conf.ss.ss_family == AF_UNSPEC) {
yyerror("listen address not specified");
- free($2);
+ free($3);
YYERROR;
}
if (parentsrv != NULL) {
- yyerror("location %s inside location", $2);
- free($2);
+ yyerror("location %s inside location", $3);
+ free($3);
YYERROR;
}
if (!loadcfg) {
- free($2);
+ free($3);
YYACCEPT;
}
if ((s = calloc(1, sizeof (*s))) == NULL)
fatal("out of memory");
- if (strlcpy(s->srv_conf.location, $2,
+ if (strlcpy(s->srv_conf.location, $3,
sizeof(s->srv_conf.location)) >=
sizeof(s->srv_conf.location)) {
yyerror("server location truncated");
- free($2);
+ free($3);
free(s);
YYERROR;
}
- free($2);
+ free($3);
if (strlcpy(s->srv_conf.name, srv->srv_conf.name,
sizeof(s->srv_conf.name)) >=
@@ -501,6 +510,8 @@ serveroptsl : LISTEN ON STRING opttls port {
/* A location entry uses the parent id */
s->srv_conf.parent_id = srv->srv_conf.id;
s->srv_conf.flags = SRVFLAG_LOCATION;
+ if ($2)
+ s->srv_conf.flags |= SRVFLAG_LOCATION_MATCH;
s->srv_s = -1;
memcpy(&s->srv_conf.ss, &srv->srv_conf.ss,
sizeof(s->srv_conf.ss));
@@ -884,6 +895,10 @@ block : BLOCK {
}
;
+optmatch : /* empty */ { $$ = 0; }
+ | MATCH { $$ = 1; }
+ ;
+
optstring : /* empty */ { $$ = NULL; }
| STRING { $$ = $1; }
;
@@ -1108,6 +1123,7 @@ lookup(char *s)
{ "location", LOCATION },
{ "log", LOG },
{ "logdir", LOGDIR },
+ { "match", MATCH },
{ "max", MAXIMUM },
{ "no", NO },
{ "nodelay", NODELAY },
@@ -1889,7 +1905,7 @@ host_free(struct addresslist *al)
}
struct server *
-server_inherit(struct server *src, const char *name,
+server_inherit(struct server *src, struct server_config *alias,
struct server_config *addr)
{
struct server *dst, *s, *dstl;
@@ -1927,7 +1943,7 @@ server_inherit(struct server *src, const char *name,
}
/* Now set alias and listen address */
- strlcpy(dst->srv_conf.name, name, sizeof(dst->srv_conf.name));
+ strlcpy(dst->srv_conf.name, alias->name, sizeof(dst->srv_conf.name));
memcpy(&dst->srv_conf.ss, &addr->ss, sizeof(dst->srv_conf.ss));
dst->srv_conf.port = addr->port;
dst->srv_conf.prefixlen = addr->prefixlen;
@@ -1936,6 +1952,10 @@ server_inherit(struct server *src, const char *name,
else
dst->srv_conf.flags &= ~SRVFLAG_TLS;
+ /* Don't inherit the "match" option, use it from the alias */
+ dst->srv_conf.flags &= ~SRVFLAG_SERVER_MATCH;
+ dst->srv_conf.flags |= (alias->flags & SRVFLAG_SERVER_MATCH);
+
if (server_tls_load_keypair(dst) == -1) {
yyerror("failed to load public/private keys "
"for server %s", dst->srv_conf.name);
@@ -1975,7 +1995,8 @@ server_inherit(struct server *src, const char *name,
fatal("out of memory");
memcpy(&dstl->srv_conf, &s->srv_conf, sizeof(dstl->srv_conf));
- strlcpy(dstl->srv_conf.name, name, sizeof(dstl->srv_conf.name));
+ strlcpy(dstl->srv_conf.name, alias->name,
+ sizeof(dstl->srv_conf.name));
/* Copy the new Id and listen address */
dstl->srv_conf.id = ++last_server_id;
diff --git a/httpd/patterns.7 b/httpd/patterns.7
new file mode 100644
index 0000000..a01ede5
--- /dev/null
+++ b/httpd/patterns.7
@@ -0,0 +1,309 @@
+.\" $OpenBSD: patterns.7,v 1.5 2015/06/30 19:01:05 jmc Exp $
+.\"
+.\" Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
+.\" Copyright (C) 1994-2015 Lua.org, PUC-Rio.
+.\"
+.\" Permission is hereby granted, free of charge, to any person obtaining
+.\" a copy of this software and associated documentation files (the
+.\" "Software"), to deal in the Software without restriction, including
+.\" without limitation the rights to use, copy, modify, merge, publish,
+.\" distribute, sublicense, and/or sell copies of the Software, and to
+.\" permit persons to whom the Software is furnished to do so, subject to
+.\" the following conditions:
+.\"
+.\" The above copyright notice and this permission notice shall be
+.\" included in all copies or substantial portions of the Software.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+.\" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+.\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+.\" IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+.\" CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+.\" TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+.\" SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+.\"
+.\" Derived from section 6.4.1 in manual.html of Lua 5.3.1:
+.\" $Id: patterns.7,v 1.5 2015/06/30 19:01:05 jmc Exp $
+.\"
+.Dd $Mdocdate: June 30 2015 $
+.Dt PATTERNS 7
+.Os
+.Sh NAME
+.Nm patterns
+.Nd Lua's pattern matching rules
+.Sh DESCRIPTION
+Pattern matching in
+.Xr httpd 8
+is based on the implementation of the Lua scripting language and
+provides a simple and fast alternative to the regular expressions (REs) that
+are described in
+.Xr re_format 7 .
+Patterns are described by regular strings, which are interpreted as
+patterns by the pattern-matching
+.Dq find
+and
+.Dq match
+functions.
+This document describes the syntax and the meaning (that is, what they
+match) of these strings.
+.Sh CHARACTER CLASS
+A character class is used to represent a set of characters.
+The following combinations are allowed in describing a character
+class:
+.Bl -tag -width Ds
+.It Ar x
+(where
+.Ar x
+is not one of the magic characters
+.Sq ^$()%.[]*+-? )
+represents the character
+.Ar x
+itself.
+.It .
+(a dot) represents all characters.
+.It %a
+represents all letters.
+.It %c
+represents all control characters.
+.It %d
+represents all digits.
+.It %g
+represents all printable characters except space.
+.It %l
+represents all lowercase letters.
+.It %p
+represents all punctuation characters.
+.It %s
+represents all space characters.
+.It %u
+represents all uppercase letters.
+.It %w
+represents all alphanumeric characters.
+.It %x
+represents all hexadecimal digits.
+.It Pf % Ar x
+(where
+.Ar x
+is any non-alphanumeric character) represents the character
+.Ar x .
+This is the standard way to escape the magic characters.
+Any non-alphanumeric character (including all punctuation characters,
+even the non-magical) can be preceded by a
+.Sq %
+when used to represent itself in a pattern.
+.It Bq Ar set
+represents the class which is the union of all
+characters in
+.Ar set .
+A range of characters can be specified by separating the end
+characters of the range, in ascending order, with a
+.Sq - .
+All classes
+.Sq Ar %x
+described above can also be used as components in
+.Ar set .
+All other characters in
+.Ar set
+represent themselves.
+For example,
+.Sq [%w_]
+(or
+.Sq [_%w] )
+represents all alphanumeric characters plus the underscore,
+.Sq [0-7]
+represents the octal digits,
+and
+.Sq [0-7%l%-]
+represents the octal digits plus the lowercase letters plus the
+.Sq -
+character.
+.Pp
+The interaction between ranges and classes is not defined.
+Therefore, patterns like
+.Sq [%a-z]
+or
+.Sq [a-%%]
+have no meaning.
+.It Bq Ar ^set
+represents the complement of
+.Ar set ,
+where
+.Ar set
+is interpreted as above.
+.El
+.Pp
+For all classes represented by single letters (
+.Sq %a ,
+.Sq %c ,
+etc.),
+the corresponding uppercase letter represents the complement of the class.
+For instance,
+.Sq %S
+represents all non-space characters.
+.Pp
+The definitions of letter, space, and other character groups depend on
+the current locale.
+In particular, the class
+.Sq [a-z]
+may not be equivalent to
+.Sq %l .
+.Sh PATTERN ITEM
+A pattern item can be
+.Bl -bullet
+.It
+a single character class, which matches any single character in the class;
+.It
+a single character class followed by
+.Sq * ,
+which matches zero or more repetitions of characters in the class.
+These repetition items will always match the longest possible sequence;
+.It
+a single character class followed by
+.Sq + ,
+which matches one or more repetitions of characters in the class.
+These repetition items will always match the longest possible sequence;
+.It
+a single character class followed by
+.Sq - ,
+which also matches zero or more repetitions of characters in the class.
+Unlike
+.Sq * ,
+these repetition items will always match the shortest possible sequence;
+.It
+a single character class followed by
+.Sq \&? ,
+which matches zero or one occurrence of a character in the class.
+It always matches one occurrence if possible;
+.It
+.Sq Pf % Ar n ,
+for
+.Ar n
+between 1 and 9;
+such item matches a substring equal to the n-th captured string (see below);
+.It
+.Sq Pf %b Ar xy ,
+where
+.Ar x
+and
+.Ar y
+are two distinct characters;
+such item matches strings that start with
+.Ar x ,
+end with
+.Ar y ,
+and where the
+.Ar x
+and
+.Ar y
+are
+.Em balanced .
+This means that if one reads the string from left to right, counting
+.Em +1
+for an
+.Ar x
+and
+.Em -1
+for a
+.Ar y ,
+the ending
+.Ar y
+is the first
+.Ar y
+where the count reaches 0.
+For instance, the item
+.Sq %b()
+matches expressions with balanced parentheses.
+.It
+.Sq Pf %f Bq Ar set ,
+a
+.Em frontier pattern ;
+such item matches an empty string at any position such that the next
+character belongs to
+.Ar set
+and the previous character does not belong to
+.Ar set .
+The set
+.Ar set
+is interpreted as previously described.
+The beginning and the end of the subject are handled as if
+they were the character
+.Sq \e0 .
+.El
+.Sh PATTERN
+A pattern is a sequence of pattern items.
+A caret
+.Sq ^
+at the beginning of a pattern anchors the match at the beginning of
+the subject string.
+A
+.Sq $
+at the end of a pattern anchors the match at the end of the subject string.
+At other positions,
+.Sq ^
+and
+.Sq $
+have no special meaning and represent themselves.
+.Sh CAPTURES
+A pattern can contain sub-patterns enclosed in parentheses; they
+describe captures.
+When a match succeeds, the substrings of the subject string that match
+captures are stored (captured) for future use.
+Captures are numbered according to their left parentheses.
+For instance, in the pattern
+.Qq (a*(.)%w(%s*)) ,
+the part of the string matching
+.Qq a*(.)%w(%s*)
+is stored as the first capture (and therefore has number 1);
+the character matching
+.So \. Sc
+is captured with number 2,
+and the part matching
+.Qq %s*
+has number 3.
+.Pp
+As a special case, the empty capture
+.Sq ()
+captures the current string position (a number).
+For instance, if we apply the pattern
+.Qq ()aa()
+on the string
+.Qq flaaap ,
+there will be two captures: 2 and 4.
+.Sh SEE ALSO
+.Xr fnmatch 3 ,
+.Xr re_format 7 ,
+.Xr httpd 8
+.Rs
+.%A Roberto Ierusalimschy
+.%A Luiz Henrique de Figueiredo
+.%A Waldemar Celes
+.%Q Lua.org
+.%Q PUC-Rio
+.%D June 2015
+.%R Lua 5.3 Reference Manual
+.%T Patterns
+.%U http://www.lua.org/manual/5.3/manual.html#6.4.1
+.Re
+.Sh HISTORY
+The first implementation of the pattern rules were introduced with Lua 2.5.
+Almost twenty years later,
+an implementation based on Lua 5.3.1 appeared in
+.Ox 5.8 .
+.Sh AUTHORS
+The pattern matching is derived from the original implementation of
+the Lua scripting language written by
+.An -nosplit
+.An Roberto Ierusalimschy ,
+.An Waldemar Celes ,
+and
+.An Luiz Henrique de Figueiredo
+at PUC-Rio.
+It was turned into a native C API for
+.Xr httpd 8
+by
+.An Reyk Floeter Aq Mt reyk@openbsd.org .
+.Sh CAVEATS
+A notable difference with the Lua implementation is the position in the string
+returned by captures.
+It follows the C-style indexing (position starting from 0)
+instead of Lua-style indexing (position starting from 1).
diff --git a/httpd/patterns.c b/httpd/patterns.c
new file mode 100644
index 0000000..27a813f
--- /dev/null
+++ b/httpd/patterns.c
@@ -0,0 +1,712 @@
+/* $OpenBSD: patterns.c,v 1.3 2015/06/26 10:07:48 semarie Exp $ */
+
+/*
+ * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (C) 1994-2015 Lua.org, PUC-Rio.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Derived from Lua 5.3.1:
+ * $Id: patterns.c,v 1.3 2015/06/26 10:07:48 semarie Exp $
+ * Standard library for string operations and pattern-matching
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "patterns.h"
+
+#define uchar(c) ((unsigned char)(c)) /* macro to 'unsign' a char */
+#define CAP_UNFINISHED (-1)
+#define CAP_POSITION (-2)
+#define L_ESC '%'
+#define SPECIALS "^$*+?.([%-"
+
+struct match_state {
+ int matchdepth; /* control for recursive depth (to avoid C
+ * stack overflow) */
+ int repetitioncounter; /* control the repetition items */
+ int maxcaptures; /* configured capture limit */
+ const char *src_init; /* init of source string */
+ const char *src_end; /* end ('\0') of source string */
+ const char *p_end; /* end ('\0') of pattern */
+ const char *error; /* should be NULL */
+ int level; /* total number of captures (finished or
+ * unfinished) */
+ struct {
+ const char *init;
+ ptrdiff_t len;
+ } capture[MAXCAPTURES];
+};
+
+/* recursive function */
+static const char *match(struct match_state *, const char *, const char *);
+
+static int
+match_error(struct match_state *ms, const char *error)
+{
+ ms->error = ms->error == NULL ? error : ms->error;
+ return (-1);
+}
+
+static int
+check_capture(struct match_state *ms, int l)
+{
+ l -= '1';
+ if (l < 0 || l >= ms->level || ms->capture[l].len == CAP_UNFINISHED)
+ return match_error(ms, "invalid capture index");
+ return (l);
+}
+
+static int
+capture_to_close(struct match_state *ms)
+{
+ int level = ms->level;
+ for (level--; level >= 0; level--)
+ if (ms->capture[level].len == CAP_UNFINISHED)
+ return (level);
+ return match_error(ms, "invalid pattern capture");
+}
+
+static const char *
+classend(struct match_state *ms, const char *p)
+{
+ switch (*p++) {
+ case L_ESC:
+ if (p == ms->p_end)
+ match_error(ms,
+ "malformed pattern (ends with '%')");
+ return p + 1;
+ case '[':
+ if (*p == '^')
+ p++;
+ do {
+ /* look for a ']' */
+ if (p == ms->p_end) {
+ match_error(ms,
+ "malformed pattern (missing ']')");
+ break;
+ }
+ if (*(p++) == L_ESC && p < ms->p_end) {
+ /* skip escapes (e.g. '%]') */
+ p++;
+ }
+ } while (*p != ']');
+ return p + 1;
+ default:
+ return p;
+ }
+}
+
+static int
+match_class(int c, int cl)
+{
+ int res;
+ switch (tolower(cl)) {
+ case 'a':
+ res = isalpha(c);
+ break;
+ case 'c':
+ res = iscntrl(c);
+ break;
+ case 'd':
+ res = isdigit(c);
+ break;
+ case 'g':
+ res = isgraph(c);
+ break;
+ case 'l':
+ res = islower(c);
+ break;
+ case 'p':
+ res = ispunct(c);
+ break;
+ case 's':
+ res = isspace(c);
+ break;
+ case 'u':
+ res = isupper(c);
+ break;
+ case 'w':
+ res = isalnum(c);
+ break;
+ case 'x':
+ res = isxdigit(c);
+ break;
+ default:
+ return (cl == c);
+ }
+ return (islower(cl) ? res : !res);
+}
+
+static int
+matchbracketclass(int c, const char *p, const char *ec)
+{
+ int sig = 1;
+ if (*(p + 1) == '^') {
+ sig = 0;
+ /* skip the '^' */
+ p++;
+ }
+ while (++p < ec) {
+ if (*p == L_ESC) {
+ p++;
+ if (match_class(c, uchar(*p)))
+ return sig;
+ } else if ((*(p + 1) == '-') && (p + 2 < ec)) {
+ p += 2;
+ if (uchar(*(p - 2)) <= c && c <= uchar(*p))
+ return sig;
+ } else if (uchar(*p) == c)
+ return sig;
+ }
+ return !sig;
+}
+
+static int
+singlematch(struct match_state *ms, const char *s, const char *p,
+ const char *ep)
+{
+ if (s >= ms->src_end)
+ return 0;
+ else {
+ int c = uchar(*s);
+ switch (*p) {
+ case '.':
+ /* matches any char */
+ return (1);
+ case L_ESC:
+ return match_class(c, uchar(*(p + 1)));
+ case '[':
+ return matchbracketclass(c, p, ep - 1);
+ default:
+ return (uchar(*p) == c);
+ }
+ }
+}
+
+static const char *
+matchbalance(struct match_state *ms, const char *s, const char *p)
+{
+ if (p >= ms->p_end - 1) {
+ match_error(ms,
+ "malformed pattern (missing arguments to '%b')");
+ return (NULL);
+ }
+ if (*s != *p)
+ return (NULL);
+ else {
+ int b = *p;
+ int e = *(p + 1);
+ int cont = 1;
+ while (++s < ms->src_end) {
+ if (*s == e) {
+ if (--cont == 0)
+ return s + 1;
+ } else if (*s == b)
+ cont++;
+ }
+ }
+
+ /* string ends out of balance */
+ return (NULL);
+}
+
+static const char *
+max_expand(struct match_state *ms, const char *s, const char *p, const char *ep)
+{
+ ptrdiff_t i = 0;
+ /* counts maximum expand for item */
+ while (singlematch(ms, s + i, p, ep))
+ i++;
+ /* keeps trying to match with the maximum repetitions */
+ while (i >= 0) {
+ const char *res = match(ms, (s + i), ep + 1);
+ if (res)
+ return res;
+ /* else didn't match; reduce 1 repetition to try again */
+ i--;
+ }
+ return NULL;
+}
+
+static const char *
+min_expand(struct match_state *ms, const char *s, const char *p, const char *ep)
+{
+ for (;;) {
+ const char *res = match(ms, s, ep + 1);
+ if (res != NULL)
+ return res;
+ else if (singlematch(ms, s, p, ep))
+ s++; /* try with one more repetition */
+ else
+ return NULL;
+ }
+}
+
+static const char *
+start_capture(struct match_state *ms, const char *s, const char *p, int what)
+{
+ const char *res;
+
+ int level = ms->level;
+ if (level >= ms->maxcaptures) {
+ match_error(ms, "too many captures");
+ return (NULL);
+ }
+ ms->capture[level].init = s;
+ ms->capture[level].len = what;
+ ms->level = level + 1;
+ /* undo capture if match failed */
+ if ((res = match(ms, s, p)) == NULL)
+ ms->level--;
+ return res;
+}
+
+static const char *
+end_capture(struct match_state *ms, const char *s, const char *p)
+{
+ int l = capture_to_close(ms);
+ const char *res;
+ if (l == -1)
+ return NULL;
+ /* close capture */
+ ms->capture[l].len = s - ms->capture[l].init;
+ /* undo capture if match failed */
+ if ((res = match(ms, s, p)) == NULL)
+ ms->capture[l].len = CAP_UNFINISHED;
+ return res;
+}
+
+static const char *
+match_capture(struct match_state *ms, const char *s, int l)
+{
+ size_t len;
+ l = check_capture(ms, l);
+ if (l == -1)
+ return NULL;
+ len = ms->capture[l].len;
+ if ((size_t) (ms->src_end - s) >= len &&
+ memcmp(ms->capture[l].init, s, len) == 0)
+ return s + len;
+ else
+ return NULL;
+}
+
+static const char *
+match(struct match_state *ms, const char *s, const char *p)
+{
+ const char *ep, *res;
+ char previous;
+
+ if (ms->matchdepth-- == 0) {
+ match_error(ms, "pattern too complex");
+ return (NULL);
+ }
+
+ /* using goto's to optimize tail recursion */
+ init:
+ /* end of pattern? */
+ if (p != ms->p_end) {
+ switch (*p) {
+ case '(':
+ /* start capture */
+ if (*(p + 1) == ')')
+ /* position capture? */
+ s = start_capture(ms, s, p + 2, CAP_POSITION);
+ else
+ s = start_capture(ms, s, p + 1, CAP_UNFINISHED);
+ break;
+ case ')':
+ /* end capture */
+ s = end_capture(ms, s, p + 1);
+ break;
+ case '$':
+ /* is the '$' the last char in pattern? */
+ if ((p + 1) != ms->p_end) {
+ /* no; go to default */
+ goto dflt;
+ }
+ /* check end of string */
+ s = (s == ms->src_end) ? s : NULL;
+ break;
+ case L_ESC:
+ /* escaped sequences not in the format class[*+?-]? */
+ switch (*(p + 1)) {
+ case 'b':
+ /* balanced string? */
+ s = matchbalance(ms, s, p + 2);
+ if (s != NULL) {
+ p += 4;
+ /* return match(ms, s, p + 4); */
+ goto init;
+ } /* else fail (s == NULL) */
+ break;
+ case 'f':
+ /* frontier? */
+ p += 2;
+ if (*p != '[') {
+ match_error(ms, "missing '['"
+ " after '%f' in pattern");
+ break;
+ }
+ /* points to what is next */
+ ep = classend(ms, p);
+ if (ms->error != NULL)
+ break;
+ previous =
+ (s == ms->src_init) ? '\0' : *(s - 1);
+ if (!matchbracketclass(uchar(previous),
+ p, ep - 1) &&
+ matchbracketclass(uchar(*s),
+ p, ep - 1)) {
+ p = ep;
+ /* return match(ms, s, ep); */
+ goto init;
+ }
+ /* match failed */
+ s = NULL;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ /* capture results (%0-%9)? */
+ s = match_capture(ms, s, uchar(*(p + 1)));
+ if (s != NULL) {
+ p += 2;
+ /* return match(ms, s, p + 2) */
+ goto init;
+ }
+ break;
+ default:
+ goto dflt;
+ }
+ break;
+ default:
+
+ /* pattern class plus optional suffix */
+ dflt:
+ /* points to optional suffix */
+ ep = classend(ms, p);
+ if (ms->error != NULL)
+ break;
+
+ /* does not match at least once? */
+ if (!singlematch(ms, s, p, ep)) {
+ if (ms->repetitioncounter-- == 0) {
+ match_error(ms, "max repetition items");
+ s = NULL; /* fail */
+ /* accept empty? */
+ } else if
+ (*ep == '*' || *ep == '?' || *ep == '-') {
+ p = ep + 1;
+ /* return match(ms, s, ep + 1); */
+ goto init;
+ } else {
+ /* '+' or no suffix */
+ s = NULL; /* fail */
+ }
+ } else {
+ /* matched once */
+ /* handle optional suffix */
+ switch (*ep) {
+ case '?':
+ /* optional */
+ if ((res =
+ match(ms, s + 1, ep + 1)) != NULL)
+ s = res;
+ else {
+ /*
+ * else return
+ * match(ms, s, ep + 1);
+ */
+ p = ep + 1;
+ goto init;
+ }
+ break;
+ case '+':
+ /* 1 or more repetitions */
+ s++; /* 1 match already done */
+ /* FALLTHROUGH */
+ case '*':
+ /* 0 or more repetitions */
+ s = max_expand(ms, s, p, ep);
+ break;
+ case '-':
+ /* 0 or more repetitions (minimum) */
+ s = min_expand(ms, s, p, ep);
+ break;
+ default:
+ /* no suffix */
+ s++;
+ p = ep;
+ /* return match(ms, s + 1, ep); */
+ goto init;
+ }
+ }
+ break;
+ }
+ }
+ ms->matchdepth++;
+ return s;
+}
+
+static const char *
+lmemfind(const char *s1, size_t l1,
+ const char *s2, size_t l2)
+{
+ const char *init;
+
+ if (l2 == 0) {
+ /* empty strings are everywhere */
+ return (s1);
+ } else if (l2 > l1) {
+ /* avoids a negative 'l1' */
+ return (NULL);
+ } else {
+ /*
+ * to search for a '*s2' inside 's1'
+ * - 1st char will be checked by 'memchr'
+ * - 's2' cannot be found after that
+ */
+ l2--;
+ l1 = l1 - l2;
+ while (l1 > 0 &&
+ (init = (const char *)memchr(s1, *s2, l1)) != NULL) {
+ /* 1st char is already checked */
+ init++;
+ if (memcmp(init, s2 + 1, l2) == 0)
+ return init - 1;
+ else {
+ /* correct 'l1' and 's1' to try again */
+ l1 -= init - s1;
+ s1 = init;
+ }
+ }
+ /* not found */
+ return (NULL);
+ }
+}
+
+static int
+push_onecapture(struct match_state *ms, int i, const char *s,
+ const char *e, struct str_find *sm)
+{
+ if (i >= ms->level) {
+ if (i == 0 || ms->level == 0) {
+ /* add whole match */
+ sm->sm_so = (off_t)(s - ms->src_init);
+ sm->sm_eo = (off_t)(e - s) + sm->sm_so;
+ } else
+ return match_error(ms, "invalid capture index");
+ } else {
+ ptrdiff_t l = ms->capture[i].len;
+ if (l == CAP_UNFINISHED)
+ return match_error(ms, "unfinished capture");
+ sm->sm_so = ms->capture[i].init - ms->src_init;
+ sm->sm_eo = sm->sm_so + l;
+ }
+ sm->sm_eo = sm->sm_eo < sm->sm_so ? sm->sm_so : sm->sm_eo;
+ return (0);
+}
+
+static int
+push_captures(struct match_state *ms, const char *s, const char *e,
+ struct str_find *sm, size_t nsm)
+{
+ unsigned int i;
+ unsigned int nlevels = (ms->level <= 0 && s) ? 1 : ms->level;
+
+ if (nlevels > nsm)
+ nlevels = nsm;
+ for (i = 0; i < nlevels; i++)
+ if (push_onecapture(ms, i, s, e, sm + i) == -1)
+ break;
+
+ /* number of strings pushed */
+ return (nlevels);
+}
+
+/* check whether pattern has no special characters */
+static int
+nospecials(const char *p, size_t l)
+{
+ size_t upto = 0;
+
+ do {
+ if (strpbrk(p + upto, SPECIALS)) {
+ /* pattern has a special character */
+ return 0;
+ }
+ /* may have more after \0 */
+ upto += strlen(p + upto) + 1;
+ } while (upto <= l);
+
+ /* no special chars found */
+ return (1);
+}
+
+static int
+str_find_aux(struct match_state *ms, const char *pattern, const char *string,
+ struct str_find *sm, size_t nsm, off_t init)
+{
+ size_t ls = strlen(string);
+ size_t lp = strlen(pattern);
+ const char *s = string;
+ const char *p = pattern;
+ const char *s1, *s2;
+ int anchor, i;
+
+ if (init < 0)
+ init = 0;
+ else if (init > (off_t)ls)
+ return match_error(ms, "starting after string's end");
+ s1 = s + init;
+
+ if (nospecials(p, lp)) {
+ /* do a plain search */
+ s2 = lmemfind(s1, ls - (size_t)init, p, lp);
+ if (s2 != NULL) {
+ i = 0;
+ sm[i].sm_so = 0;
+ sm[i].sm_eo = ls;
+ if (nsm > 1) {
+ i++;
+ sm[i].sm_so = s2 - s;
+ sm[i].sm_eo = (s2 - s) + lp;
+ }
+ return (i + 1);
+ }
+ return (0);
+ }
+
+ anchor = (*p == '^');
+ if (anchor) {
+ p++;
+ lp--; /* skip anchor character */
+ }
+ ms->maxcaptures = (nsm > MAXCAPTURES ? MAXCAPTURES : nsm) - 1;
+ ms->matchdepth = MAXCCALLS;
+ ms->repetitioncounter = MAXREPETITION;
+ ms->src_init = s;
+ ms->src_end = s + ls;
+ ms->p_end = p + lp;
+ do {
+ const char *res;
+ ms->level = 0;
+ if ((res = match(ms, s1, p)) != NULL) {
+ sm->sm_so = 0;
+ sm->sm_eo = ls;
+ return push_captures(ms, s1, res, sm + 1, nsm - 1) + 1;
+
+ } else if (ms->error != NULL) {
+ return 0;
+ }
+ } while (s1++ < ms->src_end && !anchor);
+
+ return 0;
+}
+
+int
+str_find(const char *string, const char *pattern, struct str_find *sm,
+ size_t nsm, const char **errstr)
+{
+ struct match_state ms;
+ int ret;
+
+ memset(&ms, 0, sizeof(ms));
+ memset(sm, 0, nsm * sizeof(*sm));
+
+ ret = str_find_aux(&ms, pattern, string, sm, nsm, 0);
+ if (ms.error != NULL) {
+ /* Return 0 on error and store the error string */
+ *errstr = ms.error;
+ ret = 0;
+ } else
+ *errstr = NULL;
+
+ return (ret);
+}
+
+int
+str_match(const char *string, const char *pattern, struct str_match *m,
+ const char **errstr)
+{
+ struct str_find sm[MAXCAPTURES];
+ struct match_state ms;
+ int ret, i;
+ size_t len, nsm;
+
+ nsm = MAXCAPTURES;
+ memset(&ms, 0, sizeof(ms));
+ memset(sm, 0, sizeof(sm));
+ memset(m, 0, sizeof(*m));
+
+ ret = str_find_aux(&ms, pattern, string, sm, nsm, 0);
+ if (ret == 0 || ms.error != NULL) {
+ /* Return -1 on error and store the error string */
+ *errstr = ms.error;
+ return (-1);
+ }
+
+ if ((m->sm_match = calloc(ret, sizeof(char *))) == NULL) {
+ *errstr = strerror(errno);
+ return (-1);
+ }
+ m->sm_nmatch = ret;
+
+ for (i = 0; i < ret; i++) {
+ if (sm[i].sm_so > sm[i].sm_eo)
+ continue;
+ len = sm[i].sm_eo - sm[i].sm_so;
+ if ((m->sm_match[i] = strndup(string +
+ sm[i].sm_so, len)) == NULL) {
+ *errstr = strerror(errno);
+ str_match_free(m);
+ return (-1);
+ }
+ }
+
+ *errstr = NULL;
+ return (0);
+}
+
+void
+str_match_free(struct str_match *m)
+{
+ unsigned int i = 0;
+ for (i = 0; i < m->sm_nmatch; i++)
+ free(m->sm_match[i]);
+ free(m->sm_match);
+ m->sm_nmatch = 0;
+}
diff --git a/httpd/patterns.h b/httpd/patterns.h
new file mode 100644
index 0000000..74790b5
--- /dev/null
+++ b/httpd/patterns.h
@@ -0,0 +1,47 @@
+/* $OpenBSD: patterns.h,v 1.2 2015/06/26 17:26:29 semarie Exp $ */
+
+/*
+ * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef PATTERNS_H
+#define PATTERNS_H
+
+#include <sys/types.h>
+#include <sys/cdefs.h>
+
+#define MAXCAPTURES 32 /* Max no. of allowed captures in pattern */
+#define MAXCCALLS 200 /* Max recusion depth in pattern matching */
+#define MAXREPETITION 0xfffff /* Max for repetition items */
+
+struct str_find {
+ off_t sm_so; /* start offset of match */
+ off_t sm_eo; /* end offset of match */
+};
+
+struct str_match {
+ char **sm_match; /* allocated array of matched strings */
+ unsigned int sm_nmatch; /* number of elements in array */
+};
+
+__BEGIN_DECLS
+int str_find(const char *, const char *, struct str_find *, size_t,
+ const char **);
+int str_match(const char *, const char *, struct str_match *,
+ const char **);
+void str_match_free(struct str_match *);
+__END_DECLS
+
+#endif /* PATTERNS_H */
diff --git a/httpd/server.c b/httpd/server.c
index ca67a47..86d0dca 100644
--- a/httpd/server.c
+++ b/httpd/server.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: server.c,v 1.63 2015/04/23 16:59:28 florian Exp $ */
+/* $OpenBSD: server.c,v 1.70 2015/07/16 16:29:25 florian Exp $ */
/*
* Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
@@ -41,6 +41,7 @@
#include <event.h>
#include <imsg.h>
#include <tls.h>
+#include <vis.h>
#include "httpd.h"
@@ -312,20 +313,31 @@ server_purge(struct server *srv)
void
serverconfig_free(struct server_config *srv_conf)
{
+ free(srv_conf->auth);
free(srv_conf->return_uri);
free(srv_conf->tls_cert_file);
- free(srv_conf->tls_cert);
free(srv_conf->tls_key_file);
- free(srv_conf->tls_key);
+
+ if (srv_conf->tls_cert != NULL) {
+ explicit_bzero(srv_conf->tls_cert, srv_conf->tls_cert_len);
+ free(srv_conf->tls_cert);
+ }
+
+ if (srv_conf->tls_key != NULL) {
+ explicit_bzero(srv_conf->tls_key, srv_conf->tls_key_len);
+ free(srv_conf->tls_key);
+ }
}
void
serverconfig_reset(struct server_config *srv_conf)
{
- srv_conf->tls_cert_file = srv_conf->tls_key_file = NULL;
- srv_conf->tls_cert = srv_conf->tls_key = NULL;
- srv_conf->return_uri = NULL;
srv_conf->auth = NULL;
+ srv_conf->return_uri = NULL;
+ srv_conf->tls_cert = NULL;
+ srv_conf->tls_cert_file = NULL;
+ srv_conf->tls_key = NULL;
+ srv_conf->tls_key_file = NULL;
}
struct server *
@@ -571,6 +583,11 @@ server_tls_readcb(int fd, short event, void *arg)
goto err;
}
+ if (len == 0) {
+ what |= EVBUFFER_EOF;
+ goto err;
+ }
+
if (evbuffer_add(bufev->input, rbuf, len) == -1) {
what |= EVBUFFER_ERROR;
goto err;
@@ -704,7 +721,7 @@ server_input(struct client *clt)
/* Adjust write watermark to the socket buffer output size */
bufferevent_setwatermark(clt->clt_bev, EV_WRITE,
- clt->clt_sndbufsiz, 0);
+ SERVER_MIN_PREFETCHED * clt->clt_sndbufsiz, 0);
/* Read at most amount of data that fits in one fcgi record. */
bufferevent_setwatermark(clt->clt_bev, EV_READ, 0, FCGI_CONTENT_SIZE);
@@ -729,6 +746,10 @@ server_write(struct bufferevent *bev, void *arg)
goto done;
bufferevent_enable(bev, EV_READ);
+
+ if (clt->clt_srvbev && !(clt->clt_srvbev->enabled & EV_READ))
+ bufferevent_enable(clt->clt_srvbev, EV_READ);
+
return;
done:
(*bev->errorcb)(bev, EVBUFFER_WRITE|EVBUFFER_EOF, bev->cbarg);
@@ -769,6 +790,11 @@ server_read(struct bufferevent *bev, void *arg)
goto fail;
if (clt->clt_done)
goto done;
+
+ if (EVBUFFER_LENGTH(EVBUFFER_OUTPUT(clt->clt_bev)) > (size_t)
+ SERVER_MAX_PREFETCH * clt->clt_sndbufsiz)
+ bufferevent_disable(bev, EV_READ);
+
return;
done:
(*bev->errorcb)(bev, EVBUFFER_READ|EVBUFFER_EOF, bev->cbarg);
@@ -919,7 +945,7 @@ server_accept(int fd, short event, void *arg)
close(s);
free(clt);
/*
- * the client struct was not completly set up, but still
+ * the client struct was not completely set up, but still
* counted as an inflight client. account for this.
*/
server_inflight_dec(NULL, __func__);
@@ -954,6 +980,7 @@ server_accept_tls(int fd, short event, void *arg)
} else if (ret != 0) {
log_warnx("%s: TLS accept failed - %s", __func__,
tls_error(srv->srv_tls_ctx));
+ server_close(clt, "TLS accept failed");
return;
}
@@ -1020,7 +1047,7 @@ server_log(struct client *clt, const char *msg)
{
char ibuf[HOST_NAME_MAX+1], obuf[HOST_NAME_MAX+1];
struct server_config *srv_conf = clt->clt_srv_conf;
- char *ptr = NULL;
+ char *ptr = NULL, *vmsg = NULL;
int debug_cmd = -1;
extern int verbose;
@@ -1050,13 +1077,14 @@ server_log(struct client *clt, const char *msg)
if (EVBUFFER_LENGTH(clt->clt_log) &&
evbuffer_add_printf(clt->clt_log, "\n") != -1)
ptr = evbuffer_readline(clt->clt_log);
+ (void)stravis(&vmsg, msg, HTTPD_LOGVIS);
server_sendlog(srv_conf, debug_cmd, "server %s, "
"client %d (%d active), %s:%u -> %s, "
"%s%s%s", srv_conf->name, clt->clt_id, server_clients,
- ibuf, ntohs(clt->clt_port), obuf, msg,
+ ibuf, ntohs(clt->clt_port), obuf, vmsg == NULL ? "" : vmsg,
ptr == NULL ? "" : ",", ptr == NULL ? "" : ptr);
- if (ptr != NULL)
- free(ptr);
+ free(vmsg);
+ free(ptr);
}
}
@@ -1118,6 +1146,9 @@ server_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
case IMSG_CFG_SERVER:
config_getserver(env, imsg);
break;
+ case IMSG_CFG_TLS:
+ config_gettls(env, imsg);
+ break;
case IMSG_CFG_DONE:
config_getcfg(env, imsg);
break;
diff --git a/httpd/server_fcgi.c b/httpd/server_fcgi.c
index b627dc3..ce0ccc8 100644
--- a/httpd/server_fcgi.c
+++ b/httpd/server_fcgi.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: server_fcgi.c,v 1.53 2015/03/26 09:01:51 florian Exp $ */
+/* $OpenBSD: server_fcgi.c,v 1.54 2015/06/09 08:50:52 jung Exp $ */
/*
* Copyright (c) 2014 Florian Obser <florian@openbsd.org>
@@ -32,6 +32,7 @@
#include <time.h>
#include <ctype.h>
#include <event.h>
+#include <unistd.h>
#include "httpd.h"
#include "http.h"
@@ -153,7 +154,9 @@ server_fcgi(struct httpd *env, struct client *clt)
goto fail;
}
+ close(clt->clt_fd);
clt->clt_fd = fd;
+
if (clt->clt_srvbev != NULL)
bufferevent_free(clt->clt_srvbev);
diff --git a/httpd/server_file.c b/httpd/server_file.c
index 52e50ff..16b47d0 100644
--- a/httpd/server_file.c
+++ b/httpd/server_file.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: server_file.c,v 1.54 2015/05/05 11:10:13 florian Exp $ */
+/* $OpenBSD: server_file.c,v 1.55 2015/07/16 19:05:28 reyk Exp $ */
/*
* Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
@@ -43,20 +43,20 @@ struct range {
off_t end;
};
-int server_file_access(struct httpd *, struct client *,
+int server_file_access(struct httpd *, struct client *,
char *, size_t);
-int server_file_request(struct httpd *, struct client *,
+int server_file_request(struct httpd *, struct client *,
char *, struct stat *);
-int server_partial_file_request(struct httpd *, struct client *,
+int server_partial_file_request(struct httpd *, struct client *,
char *, struct stat *, char *);
-int server_file_index(struct httpd *, struct client *,
+int server_file_index(struct httpd *, struct client *,
struct stat *);
int server_file_modified_since(struct http_descriptor *,
struct stat *);
-int server_file_method(struct client *);
-int parse_range_spec(char *, size_t, struct range *);
-struct range *parse_range(char *, size_t, int *);
-int buffer_add_range(int, struct evbuffer *, struct range *);
+int server_file_method(struct client *);
+int parse_range_spec(char *, size_t, struct range *);
+struct range *parse_range(char *, size_t, int *);
+int buffer_add_range(int, struct evbuffer *, struct range *);
int
server_file_access(struct httpd *env, struct client *clt,
@@ -296,9 +296,9 @@ server_partial_file_request(struct httpd *env, struct client *clt, char *path,
struct range *range;
struct evbuffer *evb = NULL;
size_t content_length;
- int code = 500, fd = -1, i, nranges, ret;
+ int code = 500, fd = -1, i, nranges, ret;
uint32_t boundary;
- char content_range[64];
+ char content_range[64];
const char *errstr = NULL;
/* Ignore range request for methods other than GET */
@@ -325,7 +325,7 @@ server_partial_file_request(struct httpd *env, struct client *clt, char *path,
if (nranges == 1) {
(void)snprintf(content_range, sizeof(content_range),
- "bytes %lld-%lld/%lld", range->start, range->end,
+ "bytes %lld-%lld/%lld", range->start, range->end,
st->st_size);
if (kv_add(&resp->http_headers, "Content-Range",
content_range) == NULL)
@@ -347,8 +347,8 @@ server_partial_file_request(struct httpd *env, struct client *clt, char *path,
content_length += i;
if ((i = evbuffer_add_printf(evb,
"Content-Type: %s/%s\r\n",
- media == NULL ? "application" : media->media_type,
- media == NULL ?
+ media == NULL ? "application" : media->media_type,
+ media == NULL ?
"octet-stream" : media->media_subtype)) == -1)
goto abort;
@@ -374,10 +374,10 @@ server_partial_file_request(struct httpd *env, struct client *clt, char *path,
/* prepare multipart/byteranges media type */
(void)strlcpy(multipart_media.media_type, "multipart",
- sizeof(multipart_media.media_type));
+ sizeof(multipart_media.media_type));
(void)snprintf(multipart_media.media_subtype,
- sizeof(multipart_media.media_subtype),
- "byteranges; boundary=%ud", boundary);
+ sizeof(multipart_media.media_subtype),
+ "byteranges; boundary=%ud", boundary);
media = &multipart_media;
}
@@ -656,7 +656,7 @@ struct range *
parse_range(char *str, size_t file_sz, int *nranges)
{
static struct range ranges[MAX_RANGES];
- int i = 0;
+ int i = 0;
char *p, *q;
/* Extract range unit */
@@ -692,7 +692,7 @@ parse_range(char *str, size_t file_sz, int *nranges)
int
parse_range_spec(char *str, size_t size, struct range *r)
{
- size_t start_str_len, end_str_len;
+ size_t start_str_len, end_str_len;
char *p, *start_str, *end_str;
const char *errstr;
diff --git a/httpd/server_http.c b/httpd/server_http.c
index 7b65a5e..9a6609e 100644
--- a/httpd/server_http.c
+++ b/httpd/server_http.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: server_http.c,v 1.79 2015/05/03 18:39:58 florian Exp $ */
+/* $OpenBSD: server_http.c,v 1.89 2015/07/16 19:05:28 reyk Exp $ */
/*
* Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
@@ -29,14 +29,17 @@
#include <string.h>
#include <unistd.h>
#include <limits.h>
+#include <fnmatch.h>
#include <stdio.h>
#include <time.h>
#include <resolv.h>
#include <event.h>
-#include <fnmatch.h>
+#include <ctype.h>
+#include <vis.h>
#include "httpd.h"
#include "http.h"
+#include "patterns.h"
static int server_httpmethod_cmp(const void *, const void *);
static int server_httperror_cmp(const void *, const void *);
@@ -633,6 +636,7 @@ server_reset_http(struct client *clt)
clt->clt_remote_user = NULL;
clt->clt_bev->readcb = server_read_http;
clt->clt_srv_conf = &srv->srv_conf;
+ str_match_free(&clt->clt_srv_match);
}
ssize_t
@@ -738,7 +742,7 @@ server_abort_http(struct client *clt, u_int code, const char *msg)
const char *httperr = NULL, *style;
char *httpmsg, *body = NULL, *extraheader = NULL;
char tmbuf[32], hbuf[128];
- char buf[IBUF_READ_SIZE], *ptr = NULL;
+ char buf[IBUF_READ_SIZE];
int bodylen;
if (code == 0) {
@@ -770,16 +774,13 @@ server_abort_http(struct client *clt, u_int code, const char *msg)
if (msg == NULL)
break;
memset(buf, 0, sizeof(buf));
- if ((ptr = server_expand_http(clt, msg,
- buf, sizeof(buf))) == NULL)
- goto done;
- if ((ptr = url_encode(ptr)) == NULL)
+ if (server_expand_http(clt, msg, buf, sizeof(buf)) == NULL)
goto done;
- if (asprintf(&extraheader, "Location: %s\r\n", ptr) == -1) {
+ if (asprintf(&extraheader, "Location: %s\r\n", buf) == -1) {
code = 500;
extraheader = NULL;
}
- msg = ptr;
+ msg = buf;
break;
case 401:
if (asprintf(&extraheader,
@@ -858,7 +859,6 @@ server_abort_http(struct client *clt, u_int code, const char *msg)
server_close(clt, httpmsg);
free(httpmsg);
}
- free(ptr);
}
void
@@ -877,6 +877,8 @@ server_close_http(struct client *clt)
clt->clt_descresp = NULL;
free(clt->clt_remote_user);
clt->clt_remote_user = NULL;
+
+ str_match_free(&clt->clt_srv_match);
}
char *
@@ -885,20 +887,56 @@ server_expand_http(struct client *clt, const char *val, char *buf,
{
struct http_descriptor *desc = clt->clt_descreq;
struct server_config *srv_conf = clt->clt_srv_conf;
- char ibuf[128], *str;
+ char ibuf[128], *str, *path, *query;
+ const char *errstr = NULL, *p;
+ size_t size;
+ int n, ret;
if (strlcpy(buf, val, len) >= len)
return (NULL);
+ /* Find previously matched substrings by index */
+ for (p = val; clt->clt_srv_match.sm_nmatch &&
+ (p = strstr(p, "%")) != NULL; p++) {
+ if (!isdigit(*(p + 1)))
+ continue;
+
+ /* Copy number, leading '%' char and add trailing \0 */
+ size = strspn(p + 1, "0123456789") + 2;
+ if (size >= sizeof(ibuf))
+ return (NULL);
+ (void)strlcpy(ibuf, p, size);
+ n = strtonum(ibuf + 1, 0,
+ clt->clt_srv_match.sm_nmatch - 1, &errstr);
+ if (errstr != NULL)
+ return (NULL);
+
+ /* Expand variable with matched value */
+ if ((str = url_encode(clt->clt_srv_match.sm_match[n])) == NULL)
+ return (NULL);
+ ret = expand_string(buf, len, ibuf, str);
+ free(str);
+ if (ret != 0)
+ return (NULL);
+ }
if (strstr(val, "$DOCUMENT_URI") != NULL) {
- if (expand_string(buf, len, "$DOCUMENT_URI",
- desc->http_path) != 0)
+ if ((path = url_encode(desc->http_path)) == NULL)
+ return (NULL);
+ ret = expand_string(buf, len, "$DOCUMENT_URI", path);
+ free(path);
+ if (ret != 0)
return (NULL);
}
if (strstr(val, "$QUERY_STRING") != NULL) {
- if (expand_string(buf, len, "$QUERY_STRING",
- desc->http_query == NULL ? "" :
- desc->http_query) != 0)
+ if (desc->http_query == NULL) {
+ ret = expand_string(buf, len, "$QUERY_STRING", "");
+ } else {
+ if ((query = url_encode(desc->http_query)) == NULL)
+ return (NULL);
+ ret = expand_string(buf, len, "$QUERY_STRING", query);
+ free(query);
+ }
+ if (ret != 0)
return (NULL);
}
if (strstr(val, "$REMOTE_") != NULL) {
@@ -919,27 +957,39 @@ server_expand_http(struct client *clt, const char *val, char *buf,
}
if (strstr(val, "$REMOTE_USER") != NULL) {
if ((srv_conf->flags & SRVFLAG_AUTH) &&
- clt->clt_remote_user != NULL)
- str = clt->clt_remote_user;
- else
- str = "";
- if (expand_string(buf, len,
- "$REMOTE_USER", str) != 0)
+ clt->clt_remote_user != NULL) {
+ if ((str = url_encode(clt->clt_remote_user))
+ == NULL)
+ return (NULL);
+ } else
+ str = strdup("");
+ ret = expand_string(buf, len, "$REMOTE_USER", str);
+ free(str);
+ if (ret != 0)
return (NULL);
}
}
if (strstr(val, "$REQUEST_URI") != NULL) {
+ if ((path = url_encode(desc->http_path)) == NULL)
+ return (NULL);
if (desc->http_query == NULL) {
- if ((str = strdup(desc->http_path)) == NULL)
+ str = path;
+ } else {
+ if ((query = url_encode(desc->http_query)) == NULL) {
+ free(path);
+ return (NULL);
+ }
+ ret = asprintf(&str, "%s?%s", path, query);
+ free(path);
+ free(query);
+ if (ret == -1)
return (NULL);
- } else if (asprintf(&str, "%s?%s",
- desc->http_path, desc->http_query) == -1)
- return (NULL);
- if (expand_string(buf, len, "$REQUEST_URI", str) != 0) {
- free(str);
- return (NULL);
}
+
+ ret = expand_string(buf, len, "$REQUEST_URI", str);
free(str);
+ if (ret != 0)
+ return (NULL);
}
if (strstr(val, "$SERVER_") != NULL) {
if (strstr(val, "$SERVER_ADDR") != NULL) {
@@ -958,8 +1008,12 @@ server_expand_http(struct client *clt, const char *val, char *buf,
return (NULL);
}
if (strstr(val, "$SERVER_NAME") != NULL) {
- if (expand_string(buf, len,
- "$SERVER_NAME", srv_conf->name) != 0)
+ if ((str = url_encode(srv_conf->name))
+ == NULL)
+ return (NULL);
+ ret = expand_string(buf, len, "$SERVER_NAME", str);
+ free(str);
+ if (ret != 0)
return (NULL);
}
}
@@ -977,8 +1031,10 @@ server_response(struct httpd *httpd, struct client *clt)
struct server *srv = clt->clt_srv;
struct server_config *srv_conf = &srv->srv_conf;
struct kv *kv, key, *host;
- int portval = -1;
+ struct str_find sm;
+ int portval = -1, ret;
char *hostval;
+ const char *errstr = NULL;
/* Canonicalize the request path */
if (desc->http_path == NULL ||
@@ -1038,9 +1094,17 @@ server_response(struct httpd *httpd, struct client *clt)
hostname);
}
#endif
- if ((srv_conf->flags & SRVFLAG_LOCATION) == 0 &&
- fnmatch(srv_conf->name, hostname,
- FNM_CASEFOLD) == 0 &&
+ if (srv_conf->flags & SRVFLAG_LOCATION)
+ continue;
+ else if (srv_conf->flags & SRVFLAG_SERVER_MATCH) {
+ str_find(hostname, srv_conf->name,
+ &sm, 1, &errstr);
+ ret = errstr == NULL ? 0 : -1;
+ } else {
+ ret = fnmatch(srv_conf->name,
+ hostname, FNM_CASEFOLD);
+ }
+ if (ret == 0 &&
(portval == -1 ||
(portval != -1 && portval == srv_conf->port))) {
/* Replace host configuration */
@@ -1110,6 +1174,8 @@ server_getlocation(struct client *clt, const char *path)
{
struct server *srv = clt->clt_srv;
struct server_config *srv_conf = clt->clt_srv_conf, *location;
+ const char *errstr = NULL;
+ int ret;
/* Now search for the location */
TAILQ_FOREACH(location, &srv->srv_hosts, entry) {
@@ -1120,11 +1186,20 @@ server_getlocation(struct client *clt, const char *path)
}
#endif
if ((location->flags & SRVFLAG_LOCATION) &&
- location->parent_id == srv_conf->parent_id &&
- fnmatch(location->location, path, FNM_CASEFOLD) == 0) {
- /* Replace host configuration */
- clt->clt_srv_conf = srv_conf = location;
- break;
+ location->parent_id == srv_conf->parent_id) {
+ errstr = NULL;
+ if (location->flags & SRVFLAG_LOCATION_MATCH) {
+ ret = str_match(path, location->location,
+ &clt->clt_srv_match, &errstr);
+ } else {
+ ret = fnmatch(location->location,
+ path, FNM_CASEFOLD);
+ }
+ if (ret == 0 && errstr == NULL) {
+ /* Replace host configuration */
+ clt->clt_srv_conf = srv_conf = location;
+ break;
+ }
}
}
@@ -1133,7 +1208,7 @@ server_getlocation(struct client *clt, const char *path)
int
server_response_http(struct client *clt, u_int code,
- struct media_type *media, size_t size, time_t mtime)
+ struct media_type *media, off_t size, time_t mtime)
{
struct http_descriptor *desc = clt->clt_descreq;
struct http_descriptor *resp = clt->clt_descresp;
@@ -1174,7 +1249,7 @@ server_response_http(struct client *clt, u_int code,
/* Set content length, if specified */
if ((cl =
kv_add(&resp->http_headers, "Content-Length", NULL)) == NULL ||
- kv_set(cl, "%ld", size) == -1)
+ kv_set(cl, "%lld", (long long)size) == -1)
return (-1);
/* Set last modification time */
@@ -1352,6 +1427,13 @@ server_log_http(struct client *clt, u_int code, size_t len)
struct tm *tm;
struct server_config *srv_conf;
struct http_descriptor *desc;
+ int ret = -1;
+ char *user = NULL;
+ char *path = NULL;
+ char *query = NULL;
+ char *version = NULL;
+ char *referrer_v = NULL;
+ char *agent_v = NULL;
if ((srv_conf = clt->clt_srv_conf) == NULL)
return (-1);
@@ -1380,18 +1462,34 @@ server_log_http(struct client *clt, u_int code, size_t len)
*/
switch (srv_conf->logformat) {
case LOG_FORMAT_COMMON:
- if (evbuffer_add_printf(clt->clt_log,
+ /* Use vis to encode input values from the header */
+ if (clt->clt_remote_user &&
+ stravis(&user, clt->clt_remote_user, HTTPD_LOGVIS) == -1)
+ goto done;
+ if (desc->http_version &&
+ stravis(&version, desc->http_version, HTTPD_LOGVIS) == -1)
+ goto done;
+
+ /* The following should be URL-encoded */
+ if (desc->http_path &&
+ (path = url_encode(desc->http_path)) == NULL)
+ goto done;
+ if (desc->http_query &&
+ (query = url_encode(desc->http_query)) == NULL)
+ goto done;
+
+ ret = evbuffer_add_printf(clt->clt_log,
"%s %s - %s [%s] \"%s %s%s%s%s%s\" %03d %zu\n",
srv_conf->name, ip, clt->clt_remote_user == NULL ? "-" :
- clt->clt_remote_user, tstamp,
+ user, tstamp,
server_httpmethod_byid(desc->http_method),
- desc->http_path == NULL ? "" : desc->http_path,
+ desc->http_path == NULL ? "" : path,
desc->http_query == NULL ? "" : "?",
- desc->http_query == NULL ? "" : desc->http_query,
+ desc->http_query == NULL ? "" : query,
desc->http_version == NULL ? "" : " ",
- desc->http_version == NULL ? "" : desc->http_version,
- code, len) == -1)
- return (-1);
+ desc->http_version == NULL ? "" : version,
+ code, len);
+
break;
case LOG_FORMAT_COMBINED:
@@ -1405,29 +1503,64 @@ server_log_http(struct client *clt, u_int code, size_t len)
agent->kv_value == NULL)
agent = NULL;
- if (evbuffer_add_printf(clt->clt_log,
+ /* Use vis to encode input values from the header */
+ if (clt->clt_remote_user &&
+ stravis(&user, clt->clt_remote_user, HTTPD_LOGVIS) == -1)
+ goto done;
+ if (desc->http_version &&
+ stravis(&version, desc->http_version, HTTPD_LOGVIS) == -1)
+ goto done;
+ if (agent &&
+ stravis(&agent_v, agent->kv_value, HTTPD_LOGVIS) == -1)
+ goto done;
+
+ /* The following should be URL-encoded */
+ if (desc->http_path &&
+ (path = url_encode(desc->http_path)) == NULL)
+ goto done;
+ if (desc->http_query &&
+ (query = url_encode(desc->http_query)) == NULL)
+ goto done;
+ if (referrer &&
+ (referrer_v = url_encode(referrer->kv_value)) == NULL)
+ goto done;
+
+ ret = evbuffer_add_printf(clt->clt_log,
"%s %s - %s [%s] \"%s %s%s%s%s%s\""
" %03d %zu \"%s\" \"%s\"\n",
srv_conf->name, ip, clt->clt_remote_user == NULL ? "-" :
- clt->clt_remote_user, tstamp,
+ user, tstamp,
server_httpmethod_byid(desc->http_method),
- desc->http_path == NULL ? "" : desc->http_path,
+ desc->http_path == NULL ? "" : path,
desc->http_query == NULL ? "" : "?",
- desc->http_query == NULL ? "" : desc->http_query,
+ desc->http_query == NULL ? "" : query,
desc->http_version == NULL ? "" : " ",
- desc->http_version == NULL ? "" : desc->http_version,
+ desc->http_version == NULL ? "" : version,
code, len,
- referrer == NULL ? "" : referrer->kv_value,
- agent == NULL ? "" : agent->kv_value) == -1)
- return (-1);
+ referrer == NULL ? "" : referrer_v,
+ agent == NULL ? "" : agent_v);
+
break;
case LOG_FORMAT_CONNECTION:
- if (evbuffer_add_printf(clt->clt_log, " [%s]",
- desc->http_path == NULL ? "" : desc->http_path) == -1)
- return (-1);
+ /* URL-encode the path */
+ if (desc->http_path &&
+ (path = url_encode(desc->http_path)) == NULL)
+ goto done;
+
+ ret = evbuffer_add_printf(clt->clt_log, " [%s]",
+ desc->http_path == NULL ? "" : path);
+
break;
}
- return (0);
+done:
+ free(user);
+ free(path);
+ free(query);
+ free(version);
+ free(referrer_v);
+ free(agent_v);
+
+ return (ret);
}
diff --git a/regress/Makefile b/regress/Makefile
new file mode 100644
index 0000000..964e6d5
--- /dev/null
+++ b/regress/Makefile
@@ -0,0 +1,5 @@
+# $OpenBSD: Makefile,v 1.2 2015/07/16 16:35:57 reyk Exp $
+
+SUBDIR += patterns tests
+
+.include <bsd.subdir.mk>
diff --git a/regress/patterns/Makefile b/regress/patterns/Makefile
new file mode 100644
index 0000000..fe1b348
--- /dev/null
+++ b/regress/patterns/Makefile
@@ -0,0 +1,32 @@
+# $OpenBSD: Makefile,v 1.2 2015/06/23 19:33:06 reyk Exp $
+
+HTTPDSRC = ${.CURDIR}/../../../../usr.sbin/httpd
+
+.PATH: ${HTTPDSRC}
+
+REGRESS_TARGETS= test-patterns
+
+CLEANFILES += patterns-tester
+
+#LUA?= lua53
+.ifdef LUA
+REGRESS_TARGETS+= test-patterns-lua
+.endif
+
+patterns-tester: patterns-tester.c patterns.c patterns.h
+ ${CC} -o $@ ${CFLAGS} ${.CURDIR}/patterns-tester.c ${HTTPDSRC}/patterns.c -I${HTTPDSRC}
+
+test-patterns: patterns-tester test-patterns.out test-patterns.in
+ cat ${.CURDIR}/test-patterns.in | grep -v '^#' | \
+ while IFS=' ' read string pattern comments ; do \
+ ./patterns-tester "$${string}" "$${pattern}" 2>&1 || true; \
+ done | diff -I 'OpenBSD' -u ${.CURDIR}/test-patterns.out -
+
+test-patterns-lua: patterns-tester.lua test-patterns-lua.out test-patterns.in
+ cat ${.CURDIR}/test-patterns.in | grep -v '^#' | \
+ while IFS=' ' read string pattern comments ; do \
+ ${LUA} ${.CURDIR}/patterns-tester.lua "$${string}" "$${pattern}" 2>&1 || true; \
+ done | sed "s/.*\/patterns\-tester\.lua/X_PATTERNS_TESTER_X/g" | \
+ diff -I 'OpenBSD' -u ${.CURDIR}/test-patterns-lua.out -
+
+.include <bsd.regress.mk>
diff --git a/regress/patterns/patterns-tester.c b/regress/patterns/patterns-tester.c
new file mode 100644
index 0000000..54f1456
--- /dev/null
+++ b/regress/patterns/patterns-tester.c
@@ -0,0 +1,98 @@
+/* $OpenBSD: patterns-tester.c,v 1.1 2015/06/23 18:03:09 semarie Exp $ */
+/*
+ * Copyright (c) 2015 Sebastien Marie <semarie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <err.h>
+#include <string.h>
+
+#include "patterns.h"
+
+extern char * malloc_options;
+
+static void read_string(char *, size_t);
+static void read_string_stop(void);
+
+static void
+read_string(char *buf, size_t len)
+{
+ size_t i;
+
+ /* init */
+ bzero(buf, len);
+
+ /* read */
+ if (fgets(buf, len, stdin) == NULL)
+ err(1, "fgets");
+
+ /* strip '\n' */
+ i = strnlen(buf, len);
+ if (i != 0)
+ buf[i-1] = '\0';
+}
+
+static void
+read_string_stop()
+{
+ if (getchar() != EOF)
+ errx(1, "read_string_stop: too many input");
+}
+
+int
+main(int argc, char *argv[])
+{
+ char string[1024];
+ char pattern[1024];
+ struct str_match m;
+ const char *errstr = NULL;
+ int ret;
+ size_t i;
+
+ /* configure malloc */
+ malloc_options = "S";
+
+ /* read testcase */
+ if (argc != 3) {
+ /* from stdin (useful for afl) */
+ read_string(string, sizeof(string));
+ read_string(pattern, sizeof(pattern));
+ read_string_stop();
+ } else {
+ /* from arguments */
+ strlcpy(string, argv[1], sizeof(string));
+ strlcpy(pattern, argv[2], sizeof(pattern));
+ }
+
+ /* print testcase */
+ printf("string='%s'\n", string);
+ printf("pattern='%s'\n", pattern);
+
+ /* test it ! */
+ ret = str_match(string, pattern, &m, &errstr);
+ if (errstr != NULL)
+ errx(1, "str_match: %s", errstr);
+
+ /* print result */
+ printf("ret=%d num=%d\n", ret, m.sm_nmatch);
+ for (i=0; i<m.sm_nmatch; i++) {
+ printf("%ld: %s\n", i, m.sm_match[i]);
+ }
+
+ str_match_free(&m);
+
+ return (EXIT_SUCCESS);
+}
diff --git a/regress/patterns/patterns-tester.lua b/regress/patterns/patterns-tester.lua
new file mode 100644
index 0000000..5bc476a
--- /dev/null
+++ b/regress/patterns/patterns-tester.lua
@@ -0,0 +1,3 @@
+-- $OpenBSD: patterns-tester.lua,v 1.1 2015/06/23 18:03:09 semarie Exp $
+print(string.format("string='%s'\npattern='%s'", arg[1], arg[2]));
+print(string.match(arg[1], arg[2]));
diff --git a/regress/patterns/test-patterns-lua.out b/regress/patterns/test-patterns-lua.out
new file mode 100644
index 0000000..b154925
--- /dev/null
+++ b/regress/patterns/test-patterns-lua.out
@@ -0,0 +1,127 @@
+string='/page/51'
+pattern='^/(%a+)/(%d+)$'
+page 51
+string='/Apage/51'
+pattern='/[^%d][%w%u][^%c]+()[%d]+'
+9
+string='/^page/51'
+pattern='/^(.a.e)/(.)'
+page 5
+string='/page/page-51'
+pattern='/(.*)/%1-(%d+)'
+page 51
+string='/page/[51]'
+pattern='/page/(%b[])'
+[51]
+string=':-]'
+pattern=']+'
+]
+string=':-)'
+pattern='[)]+'
+)
+string='/page/51'
+pattern='$^'
+nil
+string='1234567890'
+pattern='([2-5]-)'
+
+string='****'
+pattern='^**$'
+****
+string='xxxx'
+pattern='^x*$'
+xxxx
+string='/page/51'
+pattern='no-%d-match'
+nil
+string='/page/page-51'
+pattern='/(.*)/%9-(%d+)'
+X_PATTERNS_TESTER_X:3: invalid capture index %9
+stack traceback:
+ [C]: in function 'string.match'
+X_PATTERNS_TESTER_X:3: in main chunk
+ [C]: in ?
+string=':-)'
+pattern=')+'
+X_PATTERNS_TESTER_X:3: invalid pattern capture
+stack traceback:
+ [C]: in function 'string.match'
+X_PATTERNS_TESTER_X:3: in main chunk
+ [C]: in ?
+string='/page/51'
+pattern='/page/51('
+X_PATTERNS_TESTER_X:3: unfinished capture
+stack traceback:
+ [C]: in function 'string.match'
+X_PATTERNS_TESTER_X:3: in main chunk
+ [C]: in ?
+string='/page/51'
+pattern='/page/51%'
+X_PATTERNS_TESTER_X:3: malformed pattern (ends with '%')
+stack traceback:
+ [C]: in function 'string.match'
+X_PATTERNS_TESTER_X:3: in main chunk
+ [C]: in ?
+string='/page/51'
+pattern='/page/[51'
+X_PATTERNS_TESTER_X:3: malformed pattern (missing ']')
+stack traceback:
+ [C]: in function 'string.match'
+X_PATTERNS_TESTER_X:3: in main chunk
+ [C]: in ?
+string='/page/(51)'
+pattern='/page/%b('
+X_PATTERNS_TESTER_X:3: malformed pattern (missing arguments to '%b')
+stack traceback:
+ [C]: in function 'string.match'
+X_PATTERNS_TESTER_X:3: in main chunk
+ [C]: in ?
+string='/page/51'
+pattern='()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()'
+X_PATTERNS_TESTER_X:3: too many captures
+stack traceback:
+ [C]: in function 'string.match'
+X_PATTERNS_TESTER_X:3: in main chunk
+ [C]: in ?
+string='/page/51'
+pattern='/page/%f'
+X_PATTERNS_TESTER_X:3: missing '[' after '%f' in pattern
+stack traceback:
+ [C]: in function 'string.match'
+X_PATTERNS_TESTER_X:3: in main chunk
+ [C]: in ?
+string='/page/51'
+pattern='/page%f/51'
+X_PATTERNS_TESTER_X:3: missing '[' after '%f' in pattern
+stack traceback:
+ [C]: in function 'string.match'
+X_PATTERNS_TESTER_X:3: in main chunk
+ [C]: in ?
+string='q*********************************'
+pattern='*************************************q'
+X_PATTERNS_TESTER_X:3: max repetition items
+stack traceback:
+ [C]: in function 'string.match'
+X_PATTERNS_TESTER_X:3: in main chunk
+ [C]: in ?
+string='q+++++++++++++++++++++++++++++++++'
+pattern='+++++++++++++++++++++++++++++++++++++q'
+X_PATTERNS_TESTER_X:3: max repetition items
+stack traceback:
+ [C]: in function 'string.match'
+X_PATTERNS_TESTER_X:3: in main chunk
+ [C]: in ?
+string='q---------------------------------'
+pattern='-------------------------------------q'
+X_PATTERNS_TESTER_X:3: max repetition items
+stack traceback:
+ [C]: in function 'string.match'
+X_PATTERNS_TESTER_X:3: in main chunk
+ [C]: in ?
+string='q?????????????????????????????????'
+pattern='?????????????????????????????????????q'
+X_PATTERNS_TESTER_X:3: max repetition items
+stack traceback:
+ [C]: in function 'string.match'
+X_PATTERNS_TESTER_X:3: in main chunk
+ [C]: in ?
diff --git a/regress/patterns/test-patterns.in b/regress/patterns/test-patterns.in
new file mode 100644
index 0000000..a988d81
--- /dev/null
+++ b/regress/patterns/test-patterns.in
@@ -0,0 +1,27 @@
+# $OpenBSD: test-patterns.in,v 1.1 2015/06/23 18:03:09 semarie Exp $
+# string pattern comments
+/page/51 ^/(%a+)/(%d+)$
+/Apage/51 /[^%d][%w%u][^%c]+()[%d]+
+/^page/51 /^(.a.e)/(.)
+/page/page-51 /(.*)/%1-(%d+)
+/page/[51] /page/(%b[])
+:-] ]+
+:-) [)]+
+/page/51 $^
+1234567890 ([2-5]-)
+**** ^**$ equiv '[*]*'
+xxxx ^x*$ same as before
+/page/51 no-%d-match no match
+/page/page-51 /(.*)/%9-(%d+) invalid capture index
+:-) )+ invalid pattern capture
+/page/51 /page/51( unfinished capture
+/page/51 /page/51% malformed pattern (ends with '%')
+/page/51 /page/[51 malformed pattern (missing ']')
+/page/(51) /page/%b( malformed pattern (missing arguments to '%b')
+/page/51 ()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()() too many captures
+/page/51 /page/%f missing '[' after '%f' in pattern
+/page/51 /page%f/51 missing '[' after '%f' in pattern
+q********************************* *************************************q max repetition items
+q+++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++q max repetition items
+q--------------------------------- -------------------------------------q max repetition items
+q????????????????????????????????? ?????????????????????????????????????q max repetition items
diff --git a/regress/patterns/test-patterns.out b/regress/patterns/test-patterns.out
new file mode 100644
index 0000000..9c56279
--- /dev/null
+++ b/regress/patterns/test-patterns.out
@@ -0,0 +1,99 @@
+# $OpenBSD: test-patterns.out,v 1.2 2015/06/26 10:09:42 semarie Exp $
+string='/page/51'
+pattern='^/(%a+)/(%d+)$'
+ret=0 num=3
+0: /page/51
+1: page
+2: 51
+string='/Apage/51'
+pattern='/[^%d][%w%u][^%c]+()[%d]+'
+ret=0 num=2
+0: /Apage/51
+1:
+string='/^page/51'
+pattern='/^(.a.e)/(.)'
+ret=0 num=3
+0: /^page/51
+1: page
+2: 5
+string='/page/page-51'
+pattern='/(.*)/%1-(%d+)'
+ret=0 num=3
+0: /page/page-51
+1: page
+2: 51
+string='/page/[51]'
+pattern='/page/(%b[])'
+ret=0 num=2
+0: /page/[51]
+1: [51]
+string=':-]'
+pattern=']+'
+ret=0 num=2
+0: :-]
+1: ]
+string=':-)'
+pattern='[)]+'
+ret=0 num=2
+0: :-)
+1: )
+string='/page/51'
+pattern='$^'
+ret=-1 num=0
+string='1234567890'
+pattern='([2-5]-)'
+ret=0 num=2
+0: 1234567890
+1:
+string='****'
+pattern='^**$'
+ret=0 num=2
+0: ****
+1: ****
+string='xxxx'
+pattern='^x*$'
+ret=0 num=2
+0: xxxx
+1: xxxx
+string='/page/51'
+pattern='no-%d-match'
+ret=-1 num=0
+patterns-tester: str_match: invalid capture index
+string='/page/page-51'
+pattern='/(.*)/%9-(%d+)'
+patterns-tester: str_match: invalid pattern capture
+string=':-)'
+pattern=')+'
+patterns-tester: str_match: unfinished capture
+string='/page/51'
+pattern='/page/51('
+patterns-tester: str_match: malformed pattern (ends with '%')
+string='/page/51'
+pattern='/page/51%'
+patterns-tester: str_match: malformed pattern (missing ']')
+string='/page/51'
+pattern='/page/[51'
+patterns-tester: str_match: malformed pattern (missing arguments to '%b')
+string='/page/(51)'
+pattern='/page/%b('
+patterns-tester: str_match: too many captures
+string='/page/51'
+pattern='()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()'
+patterns-tester: str_match: missing '[' after '%f' in pattern
+string='/page/51'
+pattern='/page/%f'
+patterns-tester: str_match: missing '[' after '%f' in pattern
+string='/page/51'
+pattern='/page%f/51'
+patterns-tester: str_match: max repetition items
+string='q*********************************'
+pattern='*************************************q'
+patterns-tester: str_match: max repetition items
+string='q+++++++++++++++++++++++++++++++++'
+pattern='+++++++++++++++++++++++++++++++++++++q'
+patterns-tester: str_match: max repetition items
+string='q---------------------------------'
+pattern='-------------------------------------q'
+patterns-tester: str_match: max repetition items
+string='q?????????????????????????????????'
+pattern='?????????????????????????????????????q'
diff --git a/regress/tests/Client.pm b/regress/tests/Client.pm
new file mode 100644
index 0000000..9c27296
--- /dev/null
+++ b/regress/tests/Client.pm
@@ -0,0 +1,75 @@
+# $OpenBSD: Client.pm,v 1.1 2015/07/16 16:35:57 reyk Exp $
+
+# Copyright (c) 2010-2015 Alexander Bluhm <bluhm@openbsd.org>
+# Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use strict;
+use warnings;
+
+package Client;
+use parent 'Proc';
+use Carp;
+use Socket;
+use Socket6;
+use IO::Socket;
+use IO::Socket::INET6;
+use IO::Socket::SSL;
+
+sub new {
+ my $class = shift;
+ my %args = @_;
+ $args{chroot} ||= ".";
+ $args{logfile} ||= $args{chroot}."/client.log";
+ $args{up} ||= "Connected";
+ $args{timefile} //= "time.log";
+ my $self = Proc::new($class, %args);
+ $self->{connectdomain}
+ or croak "$class connect domain not given";
+ $self->{connectaddr}
+ or croak "$class connect addr not given";
+ $self->{connectport}
+ or croak "$class connect port not given";
+ return $self;
+}
+
+sub child {
+ my $self = shift;
+
+ # in case we redo the connect, shutdown the old one
+ shutdown(\*STDOUT, SHUT_WR);
+ delete $self->{cs};
+
+ $SSL_ERROR = "";
+ my $iosocket = $self->{tls} ? "IO::Socket::SSL" : "IO::Socket::INET6";
+ my $cs = $iosocket->new(
+ Proto => "tcp",
+ Domain => $self->{connectdomain},
+ PeerAddr => $self->{connectaddr},
+ PeerPort => $self->{connectport},
+ SSL_verify_mode => SSL_VERIFY_NONE,
+ ) or die ref($self), " $iosocket socket connect failed: $!,$SSL_ERROR";
+ print STDERR "connect sock: ",$cs->sockhost()," ",$cs->sockport(),"\n";
+ print STDERR "connect peer: ",$cs->peerhost()," ",$cs->peerport(),"\n";
+ if ($self->{tls}) {
+ print STDERR "tls version: ",$cs->get_sslversion(),"\n";
+ print STDERR "tls cipher: ",$cs->get_cipher(),"\n";
+ print STDERR "tls peer certificate:\n",
+ $cs->dump_peer_certificate();
+ }
+
+ *STDIN = *STDOUT = $self->{cs} = $cs;
+}
+
+1;
diff --git a/regress/tests/Httpd.pm b/regress/tests/Httpd.pm
new file mode 100644
index 0000000..d5b9df0
--- /dev/null
+++ b/regress/tests/Httpd.pm
@@ -0,0 +1,93 @@
+# $OpenBSD: Httpd.pm,v 1.1 2015/07/16 16:35:57 reyk Exp $
+
+# Copyright (c) 2010-2015 Alexander Bluhm <bluhm@openbsd.org>
+# Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use strict;
+use warnings;
+
+package Httpd;
+use parent 'Proc';
+use Carp;
+use File::Basename;
+
+sub new {
+ my $class = shift;
+ my %args = @_;
+ $args{chroot} ||= ".";
+ $args{logfile} ||= $args{chroot}."/httpd.log";
+ $args{up} ||= $args{dryrun} || "server_launch: ";
+ $args{down} ||= $args{dryrun} ? "httpd.conf:" : "parent terminating";
+ $args{func} = sub { Carp::confess "$class func may not be called" };
+ $args{conffile} ||= "httpd.conf";
+ my $self = Proc::new($class, %args);
+ ref($self->{http}) eq 'ARRAY'
+ or $self->{http} = [ split("\n", $self->{http} || "") ];
+ $self->{listenaddr}
+ or croak "$class listen addr not given";
+ $self->{listenport}
+ or croak "$class listen port not given";
+
+ my $test = basename($self->{testfile} || "");
+ # tls does not allow a too long session id, so truncate it
+ substr($test, 25, length($test) - 25, "") if length($test) > 25;
+ open(my $fh, '>', $self->{conffile})
+ or die ref($self), " conf file $self->{conffile} create failed: $!";
+
+ # substitute variables in config file
+ my $curdir = dirname($0) || ".";
+ my $connectport = $self->{connectport};
+ my $connectaddr = $self->{connectaddr};
+ my $listenaddr = $self->{listenaddr};
+ my $listenport = $self->{listenport};
+
+ print $fh "prefork 1\n"; # only crashes of first child are observed
+ print $fh "chroot \"".$args{chroot}."\"\n";
+ print $fh "logdir \"".$args{chroot}."\"\n";
+
+ my @http = @{$self->{http}};
+ print $fh "server \"www.$test.local\" {";
+ my $tls = $self->{listentls} ? "tls " : "";
+ print $fh "\n\tlisten on $self->{listenaddr} ".
+ "${tls}port $self->{listenport}" unless grep { /^listen / } @http;
+ # substitute variables in config file
+ foreach (@http) {
+ s/(\$[a-z]+)/$1/eeg;
+ }
+ print $fh map { "\n\t$_" } @http;
+ if ($self->{listentls}) {
+ print $fh "\n";
+ print $fh "\ttls certificate \"".$args{chroot}."/server.crt\"\n";
+ print $fh "\ttls key \"".$args{chroot}."/server.key\"";
+ }
+ print $fh "\n\troot \"/\"";
+ print $fh "\n\tlog style combined";
+ print $fh "\n}\n";
+
+ return $self;
+}
+
+sub child {
+ my $self = shift;
+ my @sudo = $ENV{SUDO} ? $ENV{SUDO} : ();
+ my @ktrace = $ENV{KTRACE} ? ($ENV{KTRACE}, "-i") : ();
+ my $httpd = $ENV{HTTPD} ? $ENV{HTTPD} : "httpd";
+ my @cmd = (@sudo, @ktrace, $httpd, "-dvv", "-f", $self->{conffile});
+ print STDERR "execute: @cmd\n";
+ exec @cmd;
+ die ref($self), " exec '@cmd' failed: $!";
+}
+
+1;
diff --git a/regress/tests/LICENSE b/regress/tests/LICENSE
new file mode 100644
index 0000000..8f60827
--- /dev/null
+++ b/regress/tests/LICENSE
@@ -0,0 +1,14 @@
+# Copyright (c) 2010-2015 Alexander Bluhm <bluhm@openbsd.org>
+# Copyright (c) 2014,2015 Reyk Floeter <reyk@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/regress/tests/Makefile b/regress/tests/Makefile
new file mode 100644
index 0000000..5298c7c
--- /dev/null
+++ b/regress/tests/Makefile
@@ -0,0 +1,105 @@
+# $OpenBSD: Makefile,v 1.2 2015/07/16 17:00:41 reyk Exp $
+
+# The following ports must be installed for the regression tests:
+# p5-IO-Socket-INET6 object interface for AF_INET and AF_INET6 domain sockets
+# p5-Socket6 Perl defines relating to AF_INET6 sockets
+# p5-IO-Socket-SSL perl interface to SSL sockets
+#
+# Check wether all required perl packages are installed. If some
+# are missing print a warning and skip the tests, but do not fail.
+
+PERL_REQUIRE != perl -Mstrict -Mwarnings -e ' \
+ eval { require IO::Socket::INET6 } or print $@; \
+ eval { require Socket6 } or print $@; \
+ eval { require IO::Socket::SSL } or print $@; \
+'
+.if ! empty (PERL_REQUIRE)
+regress:
+ @echo "${PERL_REQUIRE}"
+ @echo install these perl packages for additional tests
+.endif
+
+# Fill out these variables if you want to test httpd with
+# the httpd process running on a remote machine. You have to specify
+# a local and remote ip address for the tcp connections. To control
+# the remote machine you need a hostname for ssh to log in. All the
+# test files must be in the same directory local and remote.
+
+LOCAL_ADDR ?=
+REMOTE_ADDR ?=
+REMOTE_SSH ?=
+
+# Automatically generate regress targets from test cases in directory.
+
+ARGS != cd ${.CURDIR} && ls args-*.pl
+TARGETS ?= ${ARGS}
+REGRESS_TARGETS = ${TARGETS:S/^/run-regress-/}
+CLEANFILES += *.log httpd.conf ktrace.out stamp-*
+CLEANFILES += *.pem *.req *.crt *.key *.srl md5-*
+
+HTDOCS = 512 1048576 1073741824
+HTDOCS_MD5 = ${HTDOCS:S/^/${.OBJDIR}\/md5-/}
+HTDOCS_SPARSE = yes
+CLEANFILES += ${HTDOCS}
+
+# Set variables so that make runs with and without obj directory.
+# Only do that if necessary to keep visible output short.
+
+.if ${.CURDIR} == ${.OBJDIR}
+PERLINC =
+PERLPATH =
+.else
+PERLINC = -I${.CURDIR}
+PERLPATH = ${.CURDIR}/
+.endif
+
+# The arg tests take a perl hash with arguments controlling the
+# test parameters. Generally they consist of client, httpd, server.
+
+.for a in ${ARGS}
+run-regress-$a: $a ${HTDOCS_MD5}
+ @echo '\n======== $@ ========'
+ time SUDO=${SUDO} KTRACE=${KTRACE} HTTPD=${HTTPD} perl ${PERLINC} ${PERLPATH}httpd.pl ${.OBJDIR} ${PERLPATH}$a
+.endfor
+
+# htdocs
+
+.for d in ${HTDOCS}
+${.OBJDIR}/$d:
+ @echo '\n======== file: $d ========'
+.if (${HTDOCS_SPARSE} != "yes")
+ @dd if=/dev/arandom of=$@ count=$$(($d / 512)) bs=512
+.else
+ @dd of=$@ seek=$$(($d / 512)) bs=512 count=0 status=none
+.endif
+
+${.OBJDIR}/md5-$d: ${.OBJDIR}/$d
+ @md5 -q ${.OBJDIR}/$d > $@
+.endfor
+
+# create certificates for TLS
+
+ca.crt:
+ openssl req -batch -new -subj /L=OpenBSD/O=httpd-regress/OU=ca/CN=root/ -nodes -newkey rsa -keyout ca.key -x509 -out ca.crt
+
+server.req:
+ openssl req -batch -new -subj /L=OpenBSD/O=httpd-regress/OU=server/CN=localhost/ -nodes -newkey rsa -keyout server.key -out server.req
+
+server.crt: ca.crt server.req
+ openssl x509 -CAcreateserial -CAkey ca.key -CA ca.crt -req -in server.req -out server.crt
+
+${REGRESS_TARGETS:M*tls*} ${REGRESS_TARGETS:M*https*}: server.crt
+
+# make perl syntax check for all args files
+
+.PHONY: syntax
+
+syntax: stamp-syntax
+
+stamp-syntax: ${ARGS}
+.for a in ${ARGS}
+ @perl -c ${PERLPATH}$a
+.endfor
+ @date >$@
+
+.include <bsd.regress.mk>
diff --git a/regress/tests/Proc.pm b/regress/tests/Proc.pm
new file mode 100644
index 0000000..8f46012
--- /dev/null
+++ b/regress/tests/Proc.pm
@@ -0,0 +1,200 @@
+# $OpenBSD: Proc.pm,v 1.1 2015/07/16 16:35:57 reyk Exp $
+
+# Copyright (c) 2010-2014 Alexander Bluhm <bluhm@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use strict;
+use warnings;
+
+package Proc;
+use Carp;
+use Errno;
+use File::Basename;
+use IO::File;
+use POSIX;
+use Time::HiRes qw(time alarm sleep);
+
+my %CHILDREN;
+
+sub kill_children {
+ my @pids = @_ ? @_ : keys %CHILDREN
+ or return;
+ my @perms;
+ foreach my $pid (@pids) {
+ if (kill(TERM => $pid) != 1 and $!{EPERM}) {
+ push @perms, $pid;
+ }
+ }
+ if (my $sudo = $ENV{SUDO} and @perms) {
+ local $?; # do not modify during END block
+ my @cmd = ($sudo, '/bin/kill', '-TERM', @perms);
+ system(@cmd);
+ }
+ delete @CHILDREN{@pids};
+}
+
+BEGIN {
+ $SIG{TERM} = $SIG{INT} = sub {
+ my $sig = shift;
+ kill_children();
+ $SIG{TERM} = $SIG{INT} = 'DEFAULT';
+ POSIX::raise($sig);
+ };
+}
+
+END {
+ kill_children();
+ $SIG{TERM} = $SIG{INT} = 'DEFAULT';
+}
+
+sub new {
+ my $class = shift;
+ my $self = { @_ };
+ $self->{down} ||= "Shutdown";
+ $self->{func} && ref($self->{func}) eq 'CODE'
+ or croak "$class func not given";
+ $self->{logfile}
+ or croak "$class log file not given";
+ open(my $fh, '>', $self->{logfile})
+ or die "$class log file $self->{logfile} create failed: $!";
+ $fh->autoflush;
+ $self->{log} = $fh;
+ return bless $self, $class;
+}
+
+sub run {
+ my $self = shift;
+
+ pipe(my $reader, my $writer)
+ or die ref($self), " pipe to child failed: $!";
+ defined(my $pid = fork())
+ or die ref($self), " fork child failed: $!";
+ if ($pid) {
+ $CHILDREN{$pid} = 1;
+ $self->{pid} = $pid;
+ close($reader);
+ $self->{pipe} = $writer;
+ return $self;
+ }
+ %CHILDREN = ();
+ $SIG{TERM} = $SIG{INT} = 'DEFAULT';
+ $SIG{__DIE__} = sub {
+ die @_ if $^S;
+ warn @_;
+ IO::Handle::flush(\*STDERR);
+ POSIX::_exit(255);
+ };
+ open(STDERR, '>&', $self->{log})
+ or die ref($self), " dup STDERR failed: $!";
+ close($writer);
+ open(STDIN, '<&', $reader)
+ or die ref($self), " dup STDIN failed: $!";
+ close($reader);
+
+ do {
+ $self->child();
+ print STDERR $self->{up}, "\n";
+ $self->{begin} = time();
+ $self->{func}->($self);
+ } while ($self->{redo});
+ $self->{end} = time();
+ print STDERR "Shutdown", "\n";
+ if ($self->{timefile}) {
+ open(my $fh, '>>', $self->{timefile})
+ or die ref($self), " open $self->{timefile} failed: $!";
+ printf $fh "time='%s' duration='%.10g' ".
+ "test='%s'\n",
+ scalar(localtime(time())), $self->{end} - $self->{begin},
+ basename($self->{testfile});
+ }
+
+ IO::Handle::flush(\*STDOUT);
+ IO::Handle::flush(\*STDERR);
+ POSIX::_exit(0);
+}
+
+sub wait {
+ my $self = shift;
+ my $flags = shift;
+
+ my $pid = $self->{pid}
+ or croak ref($self), " no child pid";
+ my $kid = waitpid($pid, $flags);
+ if ($kid > 0) {
+ my $status = $?;
+ my $code;
+ $code = "exit: ". WEXITSTATUS($?) if WIFEXITED($?);
+ $code = "signal: ". WTERMSIG($?) if WIFSIGNALED($?);
+ $code = "stop: ". WSTOPSIG($?) if WIFSTOPPED($?);
+ delete $CHILDREN{$pid} if WIFEXITED($?) || WIFSIGNALED($?);
+ return wantarray ? ($kid, $status, $code) : $kid;
+ }
+ return $kid;
+}
+
+sub loggrep {
+ my $self = shift;
+ my($regex, $timeout) = @_;
+
+ my $end = time() + $timeout if $timeout;
+
+ do {
+ my($kid, $status, $code) = $self->wait(WNOHANG);
+ if ($kid > 0 && $status != 0 && !$self->{dryrun}) {
+ # child terminated with failure
+ die ref($self), " child status: $status $code";
+ }
+ open(my $fh, '<', $self->{logfile})
+ or die ref($self), " log file open failed: $!";
+ my @match = grep { /$regex/ } <$fh>;
+ return wantarray ? @match : $match[0] if @match;
+ close($fh);
+ # pattern not found
+ if ($kid == 0) {
+ # child still running, wait for log data
+ sleep .1;
+ } else {
+ # child terminated, no new log data possible
+ return;
+ }
+ } while ($timeout and time() < $end);
+
+ return;
+}
+
+sub up {
+ my $self = shift;
+ my $timeout = shift || 10;
+ $self->loggrep(qr/$self->{up}/, $timeout)
+ or croak ref($self), " no '$self->{up}' in $self->{logfile} ".
+ "after $timeout seconds";
+ return $self;
+}
+
+sub down {
+ my $self = shift;
+ my $timeout = shift || 300;
+ $self->loggrep(qr/$self->{down}/, $timeout)
+ or croak ref($self), " no '$self->{down}' in $self->{logfile} ".
+ "after $timeout seconds";
+ return $self;
+}
+
+sub kill_child {
+ my $self = shift;
+ kill_children($self->{pid});
+ return $self;
+}
+
+1;
diff --git a/regress/tests/README b/regress/tests/README
new file mode 100644
index 0000000..c9c7836
--- /dev/null
+++ b/regress/tests/README
@@ -0,0 +1,20 @@
+Run httpd regressions tests. The framework runs a client and a httpd.
+Each test creates a special httpd.conf and starts those two processes.
+All processes write log files that are checked for certain messages.
+The test arguments are kept in the args-*.pl files.
+
+SUDO=sudo
+As httpd needs root privileges either run the tests as root or set
+this variable and run make as a regular user. Only the code that
+requires it, is run as root.
+
+KTRACE=ktrace
+Set this variable if you want a ktrace output from httpd. Note that
+ktrace is invoked after sudo as sudo would disable it.
+
+HTTPD=/usr/src/usr.sbin/httpd/obj/httpd
+Start an alternative httpd program that is not in the path.
+
+HTDOCS_SPARSE=no
+Set to anything other than "yes" to create real test files instead of
+sparse files. This needs more than 1G of free disk space.
diff --git a/regress/tests/args-default.pl b/regress/tests/args-default.pl
new file mode 100644
index 0000000..3820fdd
--- /dev/null
+++ b/regress/tests/args-default.pl
@@ -0,0 +1,11 @@
+# test default values
+
+use strict;
+use warnings;
+
+our %args = (
+ len => 512,
+ md5 => path_md5("512")
+);
+
+1;
diff --git a/regress/tests/args-get-1048576.pl b/regress/tests/args-get-1048576.pl
new file mode 100644
index 0000000..9253aec
--- /dev/null
+++ b/regress/tests/args-get-1048576.pl
@@ -0,0 +1,15 @@
+use strict;
+use warnings;
+
+my $len = 1048576;
+our %args = (
+ client => {
+ path => "$len",
+ len => $len,
+ http_vers => [ "1.0" ],
+ },
+ len => 1048576,
+ md5 => path_md5("$len")
+);
+
+1;
diff --git a/regress/tests/args-get-1073741824.pl b/regress/tests/args-get-1073741824.pl
new file mode 100644
index 0000000..2b4c5f4
--- /dev/null
+++ b/regress/tests/args-get-1073741824.pl
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+
+my $len = 1073741824;
+my @lengths = ($len, $len);
+our %args = (
+ client => {
+ path => "$len",
+ http_vers => [ "1.0" ],
+ lengths => \@lengths,
+ },
+ md5 => path_md5("$len"),
+ lengths => \@lengths,
+);
+
+1;
diff --git a/regress/tests/args-get-512.pl b/regress/tests/args-get-512.pl
new file mode 100644
index 0000000..20e92c4
--- /dev/null
+++ b/regress/tests/args-get-512.pl
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+
+my $len = 512;
+my @lengths = ($len, $len, $len);
+our %args = (
+ client => {
+ path => "$len",
+ http_vers => [ "1.0" ],
+ lengths => \@lengths,
+ },
+ md5 => path_md5("$len"),
+ lengths => \@lengths,
+);
+
+1;
diff --git a/regress/tests/args-get-slash.pl b/regress/tests/args-get-slash.pl
new file mode 100644
index 0000000..9406f4b
--- /dev/null
+++ b/regress/tests/args-get-slash.pl
@@ -0,0 +1,20 @@
+use strict;
+use warnings;
+
+our %args = (
+ client => {
+ func => sub {
+ my $self = shift;
+ print "GET /\r\n\r\n";
+ },
+ nocheck => 1
+ },
+ httpd => {
+ loggrep => {
+ qr/"GET \/" 500 0/ => 1,
+ },
+ },
+);
+
+1;
+
diff --git a/regress/tests/args-log-user-agent.pl b/regress/tests/args-log-user-agent.pl
new file mode 100644
index 0000000..a8dec17
--- /dev/null
+++ b/regress/tests/args-log-user-agent.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+
+our %args = (
+ client => {
+ header => {
+ "User-Agent" => "regress\t\n\nGET / HTTP/1.0\r\n"
+ }
+ },
+ httpd => {
+ loggrep => {
+ qr/\"regress\\t\\n\\nGET \/ HTTP\/1\.0\"/ => 1,
+ },
+ },
+);
+
+1;
diff --git a/regress/tests/args-tls-get-1073741824.pl b/regress/tests/args-tls-get-1073741824.pl
new file mode 100644
index 0000000..12af833
--- /dev/null
+++ b/regress/tests/args-tls-get-1073741824.pl
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+
+my $len = 1073741824;
+our %args = (
+ client => {
+ tls => 1,
+ path => "$len",
+ len => $len,
+ },
+ httpd => {
+ listentls => 1,
+ },
+ len => $len,
+ md5 => path_md5("$len"),
+);
+
+1;
diff --git a/regress/tests/args-tls.pl b/regress/tests/args-tls.pl
new file mode 100644
index 0000000..29321a2
--- /dev/null
+++ b/regress/tests/args-tls.pl
@@ -0,0 +1,18 @@
+# test https connection
+
+use strict;
+use warnings;
+
+our %args = (
+ client => {
+ tls => 1,
+ loggrep => 'Issuer.*/OU=ca/',
+ },
+ httpd => {
+ listentls => 1,
+ },
+ len => 512,
+ md5 => path_md5("512")
+);
+
+1;
diff --git a/regress/tests/funcs.pl b/regress/tests/funcs.pl
new file mode 100644
index 0000000..2738d41
--- /dev/null
+++ b/regress/tests/funcs.pl
@@ -0,0 +1,487 @@
+# $OpenBSD: funcs.pl,v 1.4 2015/07/16 18:50:09 reyk Exp $
+
+# Copyright (c) 2010-2015 Alexander Bluhm <bluhm@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use strict;
+use warnings;
+no warnings 'experimental::smartmatch';
+use feature 'switch';
+use Errno;
+use Digest::MD5;
+use Socket;
+use Socket6;
+use IO::Socket;
+use IO::Socket::INET6;
+
+sub find_ports {
+ my %args = @_;
+ my $num = delete $args{num} // 1;
+ my $domain = delete $args{domain} // AF_INET;
+ my $addr = delete $args{addr} // "127.0.0.1";
+
+ my @sockets = (1..$num);
+ foreach my $s (@sockets) {
+ $s = IO::Socket::INET6->new(
+ Proto => "tcp",
+ Domain => $domain,
+ $addr ? (LocalAddr => $addr) : (),
+ ) or die "find_ports: create and bind socket failed: $!";
+ }
+ my @ports = map { $_->sockport() } @sockets;
+
+ return @ports;
+}
+
+sub path_md5 {
+ my $name = shift;
+ my $val = `cat md5-$name`;
+}
+
+########################################################################
+# Client funcs
+########################################################################
+
+sub write_char {
+ my $self = shift;
+ my $len = shift // $self->{len} // 512;
+ my $sleep = $self->{sleep};
+
+ my $ctx = Digest::MD5->new();
+ my $char = '0';
+ for (my $i = 1; $i < $len; $i++) {
+ $ctx->add($char);
+ print $char
+ or die ref($self), " print failed: $!";
+ given ($char) {
+ when(/9/) { $char = 'A' }
+ when(/Z/) { $char = 'a' }
+ when(/z/) { $char = "\n" }
+ when(/\n/) { print STDERR "."; $char = '0' }
+ default { $char++ }
+ }
+ if ($self->{sleep}) {
+ IO::Handle::flush(\*STDOUT);
+ sleep $self->{sleep};
+ }
+ }
+ if ($len) {
+ $char = "\n";
+ $ctx->add($char);
+ print $char
+ or die ref($self), " print failed: $!";
+ print STDERR ".\n";
+ }
+ IO::Handle::flush(\*STDOUT);
+
+ print STDERR "LEN: ", $len, "\n";
+ print STDERR "MD5: ", $ctx->hexdigest, "\n";
+}
+
+sub http_client {
+ my $self = shift;
+
+ unless ($self->{lengths}) {
+ # only a single http request
+ my $len = shift // $self->{len} // 512;
+ my $cookie = $self->{cookie};
+ http_request($self, $len, "1.0", $cookie);
+ http_response($self, $len);
+ return;
+ }
+
+ $self->{http_vers} ||= ["1.1", "1.0"];
+ my $vers = $self->{http_vers}[0];
+ my @lengths = @{$self->{redo}{lengths} || $self->{lengths}};
+ my @cookies = @{$self->{redo}{cookies} || $self->{cookies} || []};
+ while (defined (my $len = shift @lengths)) {
+ my $cookie = shift @cookies || $self->{cookie};
+ eval {
+ http_request($self, $len, $vers, $cookie);
+ http_response($self, $len);
+ };
+ warn $@ if $@;
+ if (@lengths && ($@ || $vers eq "1.0")) {
+ # reconnect and redo the outstanding requests
+ $self->{redo} = {
+ lengths => \@lengths,
+ cookies => \@cookies,
+ };
+ return;
+ }
+ }
+ delete $self->{redo};
+ shift @{$self->{http_vers}};
+ if (@{$self->{http_vers}}) {
+ # run the tests again with other persistence
+ $self->{redo} = {
+ lengths => [@{$self->{lengths}}],
+ cookies => [@{$self->{cookies} || []}],
+ };
+ }
+}
+
+sub http_request {
+ my ($self, $len, $vers, $cookie) = @_;
+ my $method = $self->{method} || "GET";
+ my %header = %{$self->{header} || {}};
+
+ # encode the requested length or chunks into the url
+ my $path = ref($len) eq 'ARRAY' ? join("/", @$len) : $len;
+ # overwrite path with custom path
+ if (defined($self->{path})) {
+ $path = $self->{path};
+ }
+ my @request = ("$method /$path HTTP/$vers");
+ push @request, "Host: foo.bar" unless defined $header{Host};
+ if ($vers eq "1.1" && $method eq "PUT") {
+ if (ref($len) eq 'ARRAY') {
+ push @request, "Transfer-Encoding: chunked"
+ if !defined $header{'Transfer-Encoding'};
+ } else {
+ push @request, "Content-Length: $len"
+ if !defined $header{'Content-Length'};
+ }
+ }
+ foreach my $key (sort keys %header) {
+ my $val = $header{$key};
+ if (ref($val) eq 'ARRAY') {
+ push @request, "$key: $_"
+ foreach @{$val};
+ } else {
+ push @request, "$key: $val";
+ }
+ }
+ push @request, "Cookie: $cookie" if $cookie;
+ push @request, "";
+ print STDERR map { ">>> $_\n" } @request;
+ print map { "$_\r\n" } @request;
+ if ($method eq "PUT") {
+ if (ref($len) eq 'ARRAY') {
+ if ($vers eq "1.1") {
+ write_chunked($self, @$len);
+ } else {
+ write_char($self, $_) foreach (@$len);
+ }
+ } else {
+ write_char($self, $len);
+ }
+ }
+ IO::Handle::flush(\*STDOUT);
+ # XXX client shutdown seems to be broken in httpd
+ #shutdown(\*STDOUT, SHUT_WR)
+ # or die ref($self), " shutdown write failed: $!"
+ # if $vers ne "1.1";
+}
+
+sub http_response {
+ my ($self, $len) = @_;
+ my $method = $self->{method} || "GET";
+
+ my $vers;
+ my $chunked = 0;
+ {
+ local $/ = "\r\n";
+ local $_ = <STDIN>;
+ defined
+ or die ref($self), " missing http $len response";
+ chomp;
+ print STDERR "<<< $_\n";
+ m{^HTTP/(\d\.\d) 200 OK$}
+ or die ref($self), " http response not ok"
+ unless $self->{httpnok};
+ $vers = $1;
+ while (<STDIN>) {
+ chomp;
+ print STDERR "<<< $_\n";
+ last if /^$/;
+ if (/^Content-Length: (.*)/) {
+ if ($self->{httpnok}) {
+ $len = $1;
+ } else {
+ $1 == $len or die ref($self),
+ " bad content length $1";
+ }
+ }
+ if (/^Transfer-Encoding: chunked$/) {
+ $chunked = 1;
+ }
+ }
+ }
+ if ($chunked) {
+ read_chunked($self);
+ } else {
+ #$len = $vers eq "1.1" ? $len : undef;
+ read_char($self, $len)
+ if $method eq "GET";
+ }
+}
+
+sub read_chunked {
+ my $self = shift;
+
+ for (;;) {
+ my $len;
+ {
+ local $/ = "\r\n";
+ local $_ = <STDIN>;
+ defined or die ref($self), " missing chunk size";
+ chomp;
+ print STDERR "<<< $_\n";
+ /^[[:xdigit:]]+$/
+ or die ref($self), " chunk size not hex: $_";
+ $len = hex;
+ }
+ last unless $len > 0;
+ read_char($self, $len);
+ {
+ local $/ = "\r\n";
+ local $_ = <STDIN>;
+ defined or die ref($self), " missing chunk data end";
+ chomp;
+ print STDERR "<<< $_\n";
+ /^$/ or die ref($self), " no chunk data end: $_";
+ }
+ }
+ {
+ local $/ = "\r\n";
+ while (<STDIN>) {
+ chomp;
+ print STDERR "<<< $_\n";
+ last if /^$/;
+ }
+ defined or die ref($self), " missing chunk trailer";
+ }
+}
+
+sub errignore {
+ $SIG{PIPE} = 'IGNORE';
+ $SIG{__DIE__} = sub {
+ die @_ if $^S;
+ warn "Error ignored";
+ warn @_;
+ IO::Handle::flush(\*STDERR);
+ POSIX::_exit(0);
+ };
+}
+
+########################################################################
+# Server funcs
+########################################################################
+
+sub read_char {
+ my $self = shift;
+ my $max = shift // $self->{max};
+
+ my $ctx = Digest::MD5->new();
+ my $len = 0;
+ if (defined($max) && $max == 0) {
+ print STDERR "Max\n";
+ } else {
+ while ((my $r = sysread(STDIN, my $buf, POSIX::BUFSIZ))) {
+ my $pct;
+ $_ = $buf;
+ $len += $r;
+ $ctx->add($_);
+ $pct = ($len / $max) * 100.0;
+ printf(STDERR "%.2f%%\n", $pct);
+ if (defined($max) && $len >= $max) {
+ print STDERR "\nMax";
+ last;
+ }
+ }
+ print STDERR "\n";
+ }
+
+ print STDERR "LEN: ", $len, "\n";
+ print STDERR "MD5: ", $ctx->hexdigest, "\n";
+}
+
+sub http_server {
+ my $self = shift;
+ my %header = %{$self->{header} || { Server => "Perl/".$^V }};
+ my $cookie = $self->{cookie} || "";
+
+ my($method, $url, $vers);
+ do {
+ my $len;
+ {
+ local $/ = "\r\n";
+ local $_ = <STDIN>;
+ return unless defined $_;
+ chomp;
+ print STDERR "<<< $_\n";
+ ($method, $url, $vers) = m{^(\w+) (.*) HTTP/(1\.[01])$}
+ or die ref($self), " http request not ok";
+ $method =~ /^(GET|PUT)$/
+ or die ref($self), " unknown method: $method";
+ ($len, my @chunks) = $url =~ /(\d+)/g;
+ $len = [ $len, @chunks ] if @chunks;
+ while (<STDIN>) {
+ chomp;
+ print STDERR "<<< $_\n";
+ last if /^$/;
+ if ($method eq "PUT" &&
+ /^Content-Length: (.*)/) {
+ $1 == $len or die ref($self),
+ " bad content length $1";
+ }
+ $cookie ||= $1 if /^Cookie: (.*)/;
+ }
+ }
+ if ($method eq "PUT" ) {
+ if (ref($len) eq 'ARRAY') {
+ read_chunked($self);
+ } else {
+ read_char($self, $len);
+ }
+ }
+
+ my @response = ("HTTP/$vers 200 OK");
+ $len = defined($len) ? $len : scalar(split /|/,$url);
+ if ($vers eq "1.1" && $method eq "GET") {
+ if (ref($len) eq 'ARRAY') {
+ push @response, "Transfer-Encoding: chunked";
+ } else {
+ push @response, "Content-Length: $len";
+ }
+ }
+ foreach my $key (sort keys %header) {
+ my $val = $header{$key};
+ if (ref($val) eq 'ARRAY') {
+ push @response, "$key: $_"
+ foreach @{$val};
+ } else {
+ push @response, "$key: $val";
+ }
+ }
+ push @response, "Set-Cookie: $cookie" if $cookie;
+ push @response, "";
+
+ print STDERR map { ">>> $_\n" } @response;
+ print map { "$_\r\n" } @response;
+
+ if ($method eq "GET") {
+ if (ref($len) eq 'ARRAY') {
+ if ($vers eq "1.1") {
+ write_chunked($self, @$len);
+ } else {
+ write_char($self, $_) foreach (@$len);
+ }
+ } else {
+ write_char($self, $len);
+ }
+ }
+ IO::Handle::flush(\*STDOUT);
+ } while ($vers eq "1.1");
+ $self->{redo}-- if $self->{redo};
+}
+
+sub write_chunked {
+ my $self = shift;
+ my @chunks = @_;
+
+ foreach my $len (@chunks) {
+ printf STDERR ">>> %x\n", $len;
+ printf "%x\r\n", $len;
+ write_char($self, $len);
+ printf STDERR ">>> \n";
+ print "\r\n";
+ }
+ my @trailer = ("0", "X-Chunk-Trailer: @chunks", "");
+ print STDERR map { ">>> $_\n" } @trailer;
+ print map { "$_\r\n" } @trailer;
+}
+
+########################################################################
+# Script funcs
+########################################################################
+
+sub check_logs {
+ my ($c, $r, %args) = @_;
+
+ return if $args{nocheck};
+
+ check_len($c, $r, %args);
+ check_md5($c, $r, %args);
+ check_loggrep($c, $r, %args);
+ $r->loggrep("lost child")
+ and die "httpd lost child";
+}
+
+sub check_len {
+ my ($c, $r, %args) = @_;
+
+ $args{len} ||= 512 unless $args{lengths};
+
+ my @clen = $c->loggrep(qr/^LEN: /) or die "no client len"
+ unless $args{client}{nocheck};
+# !@clen
+# or die "client: @clen", "len mismatch";
+ !defined($args{len}) || !$clen[0] || $clen[0] eq "LEN: $args{len}\n"
+ or die "client: $clen[0]", "len $args{len} expected";
+ my @lengths = map { ref eq 'ARRAY' ? @$_ : $_ }
+ @{$args{lengths} || []};
+ foreach my $len (@lengths) {
+ unless ($args{client}{nocheck}) {
+ my $clen = shift @clen;
+ $clen eq "LEN: $len\n"
+ or die "client: $clen", "len $len expected";
+ }
+ }
+}
+
+sub check_md5 {
+ my ($c, $r, %args) = @_;
+
+ my @cmd5 = $c->loggrep(qr/^MD5: /) unless $args{client}{nocheck};
+ my @md5 = ref($args{md5}) eq 'ARRAY' ? @{$args{md5}} : $args{md5} || ()
+ or return;
+ foreach my $md5 (@md5) {
+ unless ($args{client}{nocheck}) {
+ my $cmd5 = shift @cmd5
+ or die "too few md5 in client log";
+ $cmd5 =~ /^MD5: ($md5)$/
+ or die "client: $cmd5", "md5 $md5 expected";
+ }
+ }
+ @cmd5 && ref($args{md5}) eq 'ARRAY'
+ and die "too many md5 in client log";
+}
+
+sub check_loggrep {
+ my ($c, $r, %args) = @_;
+
+ my %name2proc = (client => $c, httpd => $r);
+ foreach my $name (qw(client httpd)) {
+ my $p = $name2proc{$name} or next;
+ my $pattern = $args{$name}{loggrep} or next;
+ $pattern = [ $pattern ] unless ref($pattern) eq 'ARRAY';
+ foreach my $pat (@$pattern) {
+ if (ref($pat) eq 'HASH') {
+ while (my($re, $num) = each %$pat) {
+ my @matches = $p->loggrep($re);
+ @matches == $num
+ or die "$name matches '@matches': ",
+ "'$re' => $num";
+ }
+ } else {
+ $p->loggrep($pat)
+ or die "$name log missing pattern: '$pat'";
+ }
+ }
+ }
+}
+
+1;
diff --git a/regress/tests/httpd.pl b/regress/tests/httpd.pl
new file mode 100644
index 0000000..481a587
--- /dev/null
+++ b/regress/tests/httpd.pl
@@ -0,0 +1,73 @@
+#!/usr/bin/perl
+# $OpenBSD: httpd.pl,v 1.1 2015/07/16 16:35:57 reyk Exp $
+
+# Copyright (c) 2010-2015 Alexander Bluhm <bluhm@openbsd.org>
+# Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use strict;
+use warnings;
+use Socket;
+use Socket6;
+
+use Client;
+use Httpd;
+require 'funcs.pl';
+
+sub usage {
+ die "usage: httpd.pl chroot [test-args.pl]\n";
+}
+
+my $testfile;
+our %args;
+if (@ARGV and -f $ARGV[-1]) {
+ $testfile = pop;
+ do $testfile
+ or die "Do test file $testfile failed: ", $@ || $!;
+}
+@ARGV == 1 or usage();
+
+my $redo = $args{lengths} && @{$args{lengths}};
+$redo = 0 if $args{client}{http_vers}; # run only one persistent connection
+my($sport, $rport) = find_ports(num => 2);
+my $d = Httpd->new(
+ chroot => $ARGV[0],
+ listendomain => AF_INET,
+ listenaddr => "127.0.0.1",
+ listenport => $rport,
+ connectdomain => AF_INET,
+ connectaddr => "127.0.0.1",
+ connectport => $sport,
+ %{$args{httpd}},
+ testfile => $testfile,
+);
+my $c = Client->new(
+ chroot => $ARGV[0],
+ func => \&http_client,
+ connectdomain => AF_INET,
+ connectaddr => "127.0.0.1",
+ connectport => $rport,
+ %{$args{client}},
+ testfile => $testfile,
+) unless $args{client}{noclient};
+
+$d->run;
+$d->up;
+$c->run->up unless $args{client}{noclient};
+
+$c->down unless $args{client}{noclient};
+$d->kill_child;
+$d->down;
+
+check_logs($c, $d, %args);