Commit 7006bbcc authored by Geoff Simmons's avatar Geoff Simmons

initial commit, implements and tests all methods except hosts.explain()

parents
Pipeline #104 skipped
((nil . ((indent-tabs-mode . t)))
(c-mode . ((c-file-style . "BSD"))))
Makefile
Makefile.in
.deps/
.libs/
*.o
*.lo
*.la
*~
*.[1-9]
/aclocal.m4
/autom4te.cache/
/compile
/config.guess
/config.h
/config.h.in
/config.log
/config.status
/config.sub
/configure
/depcomp
/install-sh
/libtool
/ltmain.sh
/missing
/stamp-h1
/m4/libtool.m4
/m4/lt*.m4
/src/vcc_if.c
/src/vcc_if.h
/src/vmod_*rst
CONTRIBUTING
============
To contribute code or documentation, submit a pull request at the
`source repository website
<https://code.uplex.de/uplex-varnish/libvmod-hoailona>`_.
If you have a problem or discover a bug, you can post an `issue
<https://code.uplex.de/uplex-varnish/libvmod-hoailona/issues>`_ at
the website. You can also write to <varnish-support@uplex.de>.
For developers
--------------
The build specifies C99 conformance, all compiler warnings turned on,
and all warnings considered errors (compiler options ``-std=c99
-Werror -Wall -Wextra``).
By default, ``CFLAGS`` is set to ``-g -O2``, so that symbols are
included in the shared library, and optimization is at level
``O2``. To change or disable these options, set ``CFLAGS`` explicitly
before calling ``make`` (it may be set to the empty string).
For development/debugging cycles, the ``configure`` option
``--enable-debugging`` is recommended (off by default). This will turn
off optimizations and function inlining, so that a debugger will step
through the code as expected.
To run the VTC test cases in ``src/test`` (as when ``make check`` is
invoked), you must have the VMOD ``blobcode`` installed.
* source repository: https://code.uplex.de/uplex-varnish/libvmod-hoailona
* VMOD blobcode: https://code.uplex.de/uplex-varnish/libvmod-blobcode
See LICENSE for details.
INSTALLATION
============
The VMOD is built against a Varnish installation, and the autotools
use ``pkg-config(1)`` to locate the necessary header files and other
resources. This sequence will install the VMOD::
> ./autogen.sh # for builds from the git repo
> ./configure
> make
> make check # to run unit tests in src/tests/*.vtc
> sudo make install
If you have installed Varnish in a non-standard directory, call
``autogen.sh`` and ``configure`` with the ``PKG_CONFIG_PATH``
environment variable pointing to the appropriate path. For example,
when varnishd configure was called with ``--prefix=$PREFIX``, use::
> PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig
> export PKG_CONFIG_PATH
By default, the vmod ``configure`` script installs the vmod in
the same directory as Varnish, determined via ``pkg-config(1)``. The
vmod installation directory can be overridden by passing the
``VMOD_DIR`` variable to ``configure``.
Other files such as the man-page are installed in the locations
determined by ``configure``, which inherits its default ``--prefix``
setting from Varnish.
Copyright (c) 2017 UPLEX Nils Goroll Systemoptimierung
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
ACLOCAL_AMFLAGS = -I m4 -I ${LIBVARNISHAPI_DATAROOTDIR}/aclocal
SUBDIRS = src
DISTCHECK_CONFIGURE_FLAGS = \
VMOD_DIR='$${libdir}/varnish/vmods'
EXTRA_DIST = README.rst LICENSE COPYING
doc_DATA = README.rst LICENSE COPYING
dist_man_MANS = vmod_hoailona.3
MAINTAINERCLEANFILES = $(dist_man_MANS)
vmod_hoailona.3: README.rst
README.rst: src/vmod_hoailona.man.rst
cp src/vmod_hoailona.man.rst README.rst
src/vmod_hoailona.man.rst:
make -C src vmod_hoailona.man.rst
%.1 %.2 %.3 %.4 %.5 %.6 %.7 %.8 %.9:
if HAVE_RST2MAN
${RST2MAN} $< $@
else
@echo "========================================"
@echo "You need rst2man installed to make dist"
@echo "========================================"
@false
endif
..
.. NB: This file is machine generated, DO NOT EDIT!
..
.. Edit vmod.vcc and run make instead
..
.. role:: ref(emphasis)
.. _vmod_hoailona(3):
=============
vmod_hoailona
=============
----------------------------------------
Akamai SecureHD Token Authorization VMOD
----------------------------------------
:Manual section: 3
SYNOPSIS
========
import hoailona [from "path"] ;
::
new OBJECT = ...
DESCRIPTION
===========
This Varnish Module (VMOD) ...
CONTENTS
========
* policy(PRIV_TASK, ENUM {OPEN,DENY,TOKEN}, DURATION, STRING, BLOB, INT)
* hosts()
* STRING version()
.. _obj_policy:
policy
------
::
new OBJ = policy(PRIV_TASK, ENUM {OPEN,DENY,TOKEN} type, DURATION ttl, STRING description=0, BLOB secret=0, INT start_offset=0)
.. _obj_hosts:
hosts
-----
::
new OBJ = hosts()
.. _func_hosts.add:
hosts.add
---------
::
VOID hosts.add(PRIV_TASK, STRING host, STRING policy, STRING path=0, STRING description=0)
# $Method adds are done
.. _func_hosts.policy:
hosts.policy
------------
::
INT hosts.policy(PRIV_TASK, STRING host, STRING path)
.. _func_hosts.token:
hosts.token
-----------
::
STRING hosts.token(PRIV_TASK, STRING acl=0, DURATION ttl=0, STRING data=0)
.. _func_hosts.secret:
hosts.secret
------------
::
BLOB hosts.secret(PRIV_TASK)
.. _func_hosts.explain:
hosts.explain
-------------
::
STRING hosts.explain(PRIV_TASK)
.. _func_version:
version
-------
::
STRING version()
Returns the version string for this VMOD.
Example::
std.log("Using VMOD hoailona version " + hoailona.version());
ERRORS
======
...
REQUIREMENTS
============
This VMOD requires Varnish ...
LIMITATIONS
===========
...
INSTALLATION
============
See ``INSTALL``.
SEE ALSO
========
* varnishd(1)
* vcl(7)
COPYRIGHT
=========
::
This document is licensed under the same conditions
as the libvmod-hoailona project. See LICENSE for details.
Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
#!/bin/sh
warn() {
echo "WARNING: $@" 1>&2
}
case `uname -s` in
Darwin)
LIBTOOLIZE=glibtoolize
;;
FreeBSD)
LIBTOOLIZE=libtoolize
;;
Linux)
LIBTOOLIZE=libtoolize
;;
SunOS)
LIBTOOLIZE=libtoolize
;;
*)
warn "unrecognized platform:" `uname -s`
LIBTOOLIZE=libtoolize
esac
automake_version=`automake --version | tr ' ' '\n' | egrep '^[0-9]\.[0-9a-z.-]+'`
if [ -z "$automake_version" ] ; then
warn "unable to determine automake version"
else
case $automake_version in
0.*|1.[0-8]|1.[0-8][.-]*)
warn "automake ($automake_version) detected; 1.9 or newer recommended"
;;
*)
;;
esac
fi
# check for varnishapi.m4 in custom paths
dataroot=$(pkg-config --variable=datarootdir varnishapi 2>/dev/null)
if [ -z "$dataroot" ] ; then
cat >&2 <<'EOF'
Package varnishapi was not found in the pkg-config search path.
Perhaps you should add the directory containing `varnishapi.pc'
to the PKG_CONFIG_PATH environment variable
EOF
exit 1
fi
set -ex
aclocal -I m4 -I ${dataroot}/aclocal
$LIBTOOLIZE --copy --force
autoheader
automake --add-missing --copy --foreign
autoconf
AC_PREREQ(2.59)
AC_COPYRIGHT([Copyright (c) 2017 UPLEX - Nils Goroll Systemoptimierung])
AC_INIT([libvmod-hoailona], [trunk])
AC_CONFIG_MACRO_DIR([m4])
m4_ifndef([VARNISH_VMOD_INCLUDES], AC_MSG_ERROR([Need varnish.m4 -- see README.rst]))
AC_CONFIG_SRCDIR(src/vmod_hoailona.vcc)
AM_CONFIG_HEADER(config.h)
AC_CANONICAL_SYSTEM
AC_LANG(C)
AM_INIT_AUTOMAKE([foreign])
AC_GNU_SOURCE
AC_PROG_CC
AC_PROG_CC_STDC
AC_PROG_CC_C99
if test "x$ac_cv_prog_cc_c99" = xno; then
AC_MSG_ERROR([Could not find a C99 compatible compiler])
fi
AC_PROG_CPP
AX_PTHREAD(,[AC_MSG_ERROR([Could not configure pthreads support])])
LIBS="$PTHREAD_LIBS $LIBS"
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
CC="$PTHREAD_CC"
LT_INIT([disable-static])
AC_PROG_INSTALL
AC_PROG_LIBTOOL
AC_PROG_MAKE_SET
# Check for rst utilities
AC_CHECK_PROGS(RST2MAN, [rst2man rst2man.py], "no")
if test "x$RST2MAN" = "xno"; then
AC_MSG_WARN([rst2man not found - not building man pages])
fi
AM_CONDITIONAL(HAVE_RST2MAN, [test "x$RST2MAN" != "xno"])
# Checks for header files.
AC_HEADER_STDC
# Checks for C sources
# Checks for libtool
# backwards compat with older pkg-config
# - pull in AC_DEFUN from pkg.m4
m4_ifndef([PKG_CHECK_VAR], [
# PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE,
# [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
# -------------------------------------------
# Retrieves the value of the pkg-config variable for the given module.
AC_DEFUN([PKG_CHECK_VAR],
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl
_PKG_CONFIG([$1], [variable="][$3]["], [$2])
AS_VAR_COPY([$1], [pkg_cv_][$1])
AS_VAR_IF([$1], [""], [$5], [$4])dnl
])# PKG_CHECK_VAR
])
PKG_CHECK_MODULES([libvarnishapi], [varnishapi])
PKG_CHECK_VAR([LIBVARNISHAPI_PREFIX], [varnishapi], [prefix])
PKG_CHECK_VAR([LIBVARNISHAPI_DATAROOTDIR], [varnishapi], [datarootdir])
PKG_CHECK_VAR([LIBVARNISHAPI_BINDIR], [varnishapi], [bindir])
PKG_CHECK_VAR([LIBVARNISHAPI_SBINDIR], [varnishapi], [sbindir])
AC_SUBST([LIBVARNISHAPI_DATAROOTDIR])
# Varnish include files tree
VARNISH_VMOD_INCLUDES
VARNISH_VMOD_DIR
VARNISH_VMODTOOL
# inherit the prefix from Varnish.
# acessing ac_ variable because AC_PREFIX_DEFAULT acts too early
ac_default_prefix=$LIBVARNISHAPI_PREFIX
AC_PATH_PROG([VARNISHTEST], [varnishtest], [],
[$LIBVARNISHAPI_BINDIR:$LIBVARNISHAPI_SBINDIR:$PATH])
AC_PATH_PROG([VARNISHD], [varnishd], [],
[$LIBVARNISHAPI_SBINDIR:$LIBVARNISHAPI_BINDIR:$PATH])
# --enable-stack-protector
AC_ARG_ENABLE(stack-protector,
AS_HELP_STRING([--enable-stack-protector],[enable stack protector (default is YES)]),
[],
[enable_stack_protector=yes])
if test "x$enable_stack_protector" != "xno"; then
AX_CHECK_COMPILE_FLAG([-fstack-protector],
AX_CHECK_LINK_FLAG([-fstack-protector],
[CFLAGS="${CFLAGS} -fstack-protector"], [], []),
[], [])
fi
# --enable-debugging
AC_ARG_ENABLE(debugging,
AS_HELP_STRING([--enable-debugging],[enable debugging (default is NO)]),
[],
[enable_debugging=no])
# AC_PROG_CC sets CFLAGS to '-g -O2' unless it is already set, so
# there's no need to add -g. Disable or change by explicitly setting
# CFLAGS. If this option is enabled, then -Og or -O0 becomes the last
# optimization option, and hence takes precedence.
if test "x$enable_debugging" != "xno"; then
CFLAGS="${CFLAGS} -fno-inline"
AX_CHECK_COMPILE_FLAG([-Og],
[CFLAGS="${CFLAGS} -Og"],
[CFLAGS="${CFLAGS} -O0"],
[])
fi
AC_CONFIG_FILES([
Makefile
src/Makefile
])
AC_OUTPUT
AM_CPPFLAGS = @VMOD_INCLUDES@ -Wall -Werror -Wextra -std=c99
vmoddir = @VMOD_DIR@
vmod_LTLIBRARIES = libvmod_hoailona.la
libvmod_hoailona_la_LDFLAGS = -module -export-dynamic -avoid-version -shared
libvmod_hoailona_la_SOURCES = \
vmod_hoailona.c \
vmod_hoailona.h \
vtree.h \
pattern.c
nodist_libvmod_hoailona_la_SOURCES = \
vcc_if.c \
vcc_if.h
vmod_hoailona.lo: vcc_if.c vcc_if.h
vcc_if.c: vcc_if.h
vcc_if.h vmod_hoailona.man.rst: @VMODTOOL@ $(top_srcdir)/src/vmod_hoailona.vcc
@VMODTOOL@ $(top_srcdir)/src/vmod_hoailona.vcc
VMOD_TESTS = $(top_srcdir)/src/tests/*.vtc
.PHONY: $(VMOD_TESTS)
$(top_srcdir)/src/tests/*.vtc: libvmod_hoailona.la
@VARNISHTEST@ -Dvarnishd=@VARNISHD@ -Dvmod_topbuild=$(abs_top_builddir) $@
check: $(VMOD_TESTS)
EXTRA_DIST = \
vmod_hoailona.vcc \
$(VMOD_TESTS)
CLEANFILES = \
$(builddir)/vcc_if.c \
$(builddir)/vcc_if.h \
$(builddir)/vmod_hoailona.rst \
$(builddir)/vmod_hoailona.man.rst
/*-
* Copyright 2016 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "vmod_hoailona.h"
#include "cache/cache.h"
static vre_t *chars = NULL, *dots = NULL, *stars = NULL, *meta = NULL;
/*
* This comparator implements the "specificness" relation between paths
* assigned to a host according the Akamai SecureHD world. It is used as
* the comparator for red-black trees assigned to hosts.
*
* ass_a < ass_b, and hence path_cmp() returns <0, iff the path assigned
* in ass_a is less specific than the path assigned in ass_b.
*
* Path A is less specific than path B if:
* A has fewer slashes than B,
* else if A has '...' and B does not
* else if A has more *'s than B
* else if A is shorter than B
* else if strcmp(A, B) < 0.
*
* VRB_FIND and VRB_INSERT are only called during vcl_init, so the
* comparator is not on the critical performance path.
*/
int
path_cmp(const struct assignment * restrict const ass_a,
const struct assignment * restrict const ass_b)
{
char *a = ass_a->pattern->path, *b = ass_b->pattern->path, *p;
size_t aslashes = 0, bslashes = 0, astars = 0, bstars = 0, alen = 0,
blen = 0, cmp = 0;
int adots = 0, bdots = 0;
p = a;
while (*p) {
if (*p == '/')
aslashes++;
else if (*p == '*')
astars++;
else if (!adots && *p == '.'
&& (adots = *(p + 1) == '.' && *(p + 2) == '.')) {
p += 3;
alen += 3;
continue;
}
alen++;
p++;
}
p = b;
while (*p) {
if (cmp == 0 && blen < alen)
cmp = a[blen] - b[blen];
if (*p == '/')
bslashes++;
else if (*p == '*')
bstars++;
else if (!bdots && *p == '.'
&& (bdots = *(p + 1) == '.' && *(p + 2) == '.')) {
p += 3;
blen += 3;
continue;
}
blen++;
p++;
}
if (aslashes != bslashes)
return bslashes - aslashes;
if (adots != bdots)
return adots - bdots;
if (astars != bstars)
return astars - bstars;
if (alen != blen)
return blen - alen;
return cmp;
}
VRB_GENERATE(assign_tree, assignment, entry, path_cmp)
void
validation_init(void)
{
VRT_re_init((void **)&chars,
"([^A-Za-z0-9 _\\-~.%:/\\[\\]@!$&()*+,;=]+)");
AN(chars);
VRT_re_init((void**)&dots, "([^/]\\.\\.\\.[^/]*|[^/]*\\.\\.\\.[^/])");
AN(dots);
VRT_re_init((void **)&stars, "(.?\\*{2,}.?)");
AN(stars);
VRT_re_init((void **)&meta, "([[\\]$()+])");
AN(meta);
}
const char *
valid(VRT_CTX, const char * restrict const path)
{
char *errmsg = NULL;
int r, ovector[6];
size_t len;
AN(path);
len = strlen(path);
memset(ovector, 0, sizeof(ovector));
r = VRE_exec(chars, path, len, 0, 0, ovector, 6, NULL);
if (r >= 0) {
if ((errmsg = WS_Printf(ctx->ws,
"invalid character(s) in pattern: %.*s",
ovector[3] - ovector[2],
path + ovector[2]))
== NULL)
return "invalid character(s) in pattern";
return errmsg;
}
memset(ovector, 0, sizeof(ovector));
r = VRE_exec(dots, path, len, 0, 0, ovector, 6, NULL);
if (r >= 0) {
if ((errmsg = WS_Printf(ctx->ws,
"... must only be used before and "
"after slashes: %.*s",
ovector[3] - ovector[2],
path + ovector[2]))
== NULL)
return "... must only be used before and after slashes";
return errmsg;
}
memset(ovector, 0, sizeof(ovector));
r = VRE_exec(stars, path, len, 0, 0, ovector, 6, NULL);
if (r >= 0) {
if ((errmsg = WS_Printf(ctx->ws,
"more than one *: %.*s",
ovector[3] - ovector[2],
path + ovector[2]))
== NULL)
return "more than one *";
return errmsg;
}
return NULL;
}
vre_t *
pattern2re(VRT_CTX, const char * restrict const path)
{
struct vsb *regex;
const char *esc, *p, *end, *regex_errstr;
char *snap;
int end_anchor = 1, regex_erroffset;
vre_t *re;
AN(path);
snap = WS_Snapshot(ctx->ws);
esc = VRT_regsub(ctx, 1, path, meta, "\\\1");
if (WS_Overflowed(ctx->ws)) {
WS_Reset(ctx->ws, snap);
return NULL;
}
regex = VSB_new_auto();
CHECK_OBJ_NOTNULL(regex, VSB_MAGIC);
p = esc;
end = esc + strlen(esc);
if (end - esc > 3 && esc[0] == '.' && esc[1] == '.' && esc[2] == '.') {
VSB_putc(regex, '.');
p = esc + 3;
}
else if (end - esc > 1)
VSB_putc(regex, '^');
while (*p) {
if (*p == '*') {
VSB_cat(regex, "[^/]+");
p++;
continue;
}
if (*p == '.') {
if (end - p >= 3 && p[1] == '.' && p[2] == '.') {
if (p[3] == '\0') {
end_anchor = 0;
break;
}
else {
VSB_cat(regex, ".+");
p += 3;
continue;
}
}
else {
VSB_cat(regex, "\\.");
p++;
continue;
}
}
else {
VSB_putc(regex, *p);
p++;
}
}
if (end_anchor)
VSB_putc(regex, '$');
VSB_finish(regex);
re = VRE_compile(VSB_data(regex), 0, &regex_errstr, &regex_erroffset);
if (ctx->vsl)
VSLb(ctx->vsl, SLT_VCL_Log, "Compiled %s for %s",
VSB_data(regex), path);
else
VSL(SLT_VCL_Log, 0, "Compiled %s for %s", VSB_data(regex),
path);
assert(re != NULL && regex_errstr == NULL);
VSB_destroy(&regex);
WS_Reset(ctx->ws, snap);
return re;
}
# looks like -*- vcl -*-
varnishtest "vcl.use and .discard, and version string"
server s1 {} -start
varnish v1 -vcl+backend {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
} -start
varnish v1 -vcl+backend {}
varnish v1 -cli "vcl.list"
varnish v1 -cli "vcl.use vcl1"
varnish v1 -cli "vcl.use vcl2"
varnish v1 -cli "vcl.use vcl1"
varnish v1 -cli "vcl.show vcl1"
varnish v1 -cli "vcl.use vcl2"
varnish v1 -cli "vcl.discard vcl1"
varnish v1 -cli "vcl.list"
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_recv {
return(synth(200));
}
sub vcl_synth {
set resp.http.x-version = hoailona.version();
if (!resp.http.x-version) {
set resp.status = 500;
}
return(deliver);
}
}
client c1 {
txreq -url "/"
rxresp
expect resp.status == 200
expect resp.http.x-version ~ "^.+$"
} -run
# looks like -*- vcl -*-
varnishtest "hosts.add()"
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new q = hoailona.policy(TOKEN, 2h);
new r = hoailona.policy(DENY, 3h);
new h = hoailona.hosts();
h.add("example.com", "q", "/*/...");
h.add(host="example.org", policy="p");
h.add("example.com", "r", ".../x/y");
h.add("varnish-cache.org", "r", "/x/y/./");
}
} -start
varnish v1 -errvcl {vmod hoailona error: host is empty in h.add()} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("", "p", "/foo/bar");
}
}
varnish v1 -errvcl {vmod hoailona error: policy is empty in h.add()} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("example.com", "", "/foo/bar");
}
}
varnish v1 -errvcl {vmod hoailona error: path is set but empty in h.add()} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("example.com", "p", "");
}
}
varnish v1 -errvcl {vmod hoailona error: path is set but empty in h.add()} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("example.com", "p", "");
}
}
varnish v1 -errvcl {vmod hoailona error: path ""<>?\^`| in h.add(): invalid character(s) in pattern: ""<>?\^`|} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("example.com", "p", {"""<>?\^`|"});
}
}
varnish v1 -errvcl {vmod hoailona error: path /x... in h.add(): ... must only be used before and after slashes: x...} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("example.com", "p", "/x...");
}
}
varnish v1 -errvcl {vmod hoailona error: path /.../...x in h.add(): ... must only be used before and after slashes: ...x} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("example.com", "p", "/.../...x");
}
}
varnish v1 -errvcl {vmod hoailona error: path /x/**/y in h.add(): more than one *: /**/} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("example.com", "p", "/x/**/y");
}
}
varnish v1 -errvcl {vmod hoailona error: No policy objects created before calling h.add()} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new h = hoailona.hosts();
h.add("example.com", "p", "/x/y");
}
}
varnish v1 -errvcl {vmod hoailona error: Policy object q not found in h.add()} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("example.com", "q", "/x/y");
}
}
varnish v1 -errvcl {vmod hoailona error: Policy p already set globally for host example.com in h.add()} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new q = hoailona.policy(TOKEN, 2h);
new h = hoailona.hosts();
h.add("example.com", "p");
h.add("example.com", "q");
}
}
varnish v1 -errvcl {vmod hoailona error: Path-specific policies already set for host example.com in h.add()} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new q = hoailona.policy(TOKEN, 2h);
new h = hoailona.hosts();
h.add("example.com", "p", "/foo/bar");
h.add("example.com", "q");
}
}
varnish v1 -errvcl {vmod hoailona error: Policy p already assigned for host example.com and path /foo/bar in h.add()} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new q = hoailona.policy(TOKEN, 2h);
new h = hoailona.hosts();
h.add("example.com", "p", "/foo/bar");
h.add("example.com", "q", "/foo/bar");
}
}
# The following assignments are legal since the paths don't compare as
# equal.
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new q = hoailona.policy(TOKEN, 2h);
new h = hoailona.hosts();
h.add("example.com", "p", "/foo/bar");
h.add("example.com", "q", "/bar/foo");
}
}
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new q = hoailona.policy(TOKEN, 2h);
new h = hoailona.hosts();
h.add("example.com", "p", "/foo/bar");
h.add("example.com", "q", "/foo/bar/baz");
}
}
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new q = hoailona.policy(TOKEN, 2h);
new h = hoailona.hosts();
h.add("example.com", "p", "/foo/bar");
h.add("example.com", "q", "/foo/b/r");
}
}
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new q = hoailona.policy(TOKEN, 2h);
new h = hoailona.hosts();
h.add("example.com", "p", "/foo/bar");
h.add("example.com", "q", "/foo/...");
}
}
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new q = hoailona.policy(TOKEN, 2h);
new h = hoailona.hosts();
h.add("example.com", "p", ".../bar");
h.add("example.com", "q", "/foo/bar");
}
}
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new q = hoailona.policy(TOKEN, 2h);
new h = hoailona.hosts();
h.add("example.com", "p", "/foo/bar");
h.add("example.com", "q", "/foo/*");
}
}
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new q = hoailona.policy(TOKEN, 2h);
new h = hoailona.hosts();
h.add("example.com", "p", "/foo/bar");
h.add("example.com", "q", "/foo/barz");
}
}
# Test valid hostnames
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("*example.com", "p");
h.add("EXAMPLE-EXAMPLE.EXAMPLE.COM", "p");
h.add("abcdefg-hijklmnop.qrstuv-wxyz.ABCDEFG-HIJKLMNOP.QRSTUV-WXYZ.0123-456.789", "p");
}
}
varnish v1 -errvcl {invalid hostname -example.com: may not begin with - or .} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("-example.com", "p");
}
}
varnish v1 -errvcl {invalid hostname .example.com: may not begin with - or .} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add(".example.com", "p");
}
}
varnish v1 -errvcl {invalid hostname *.*.example.com: illegal characters} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("*.*.example.com", "p");
}
}
varnish v1 -errvcl {invalid hostname example-%.com: illegal characters} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("example-%.com", "p");
}
}
varnish v1 -errvcl {invalid hostname example-ä.com: illegal characters} {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("example-ä.com", "p");
}
}
This diff is collapsed.
# looks like -*- vcl -*-
varnishtest "policy object constructor"
# VMOD blobcode must be installed
# Doesn't test much, just make sure nothing crashes
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
import blobcode;
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p1 = hoailona.policy(OPEN, 1h);
new p2 = hoailona.policy(TOKEN, 2h);
new p3 = hoailona.policy(DENY, 3h);
new p4 = hoailona.policy(TOKEN, 1h, description="policy p4");
new p5 = hoailona.policy(OPEN, 1h, start_offset= 0-10);
new p6 = hoailona.policy(DENY, 1h,
secret=blobcode.decode(encoded="foo"));
new p7 = hoailona.policy(TOKEN, 1h, "p7",
blobcode.decode(encoded="bar"), 0-30);
}
} -start
varnish v1 -vcl { backend b { .host = "${bad_ip}"; } }
# Runs fini
varnish v1 -cli "vcl.discard vcl1"
# looks like -*- vcl -*-
varnishtest "hosts.secret()"
# VMOD blobcode must be installed
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
import blobcode;
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(TOKEN, 2h,
secret=blobcode.decode(encoded="foo"));
new h = hoailona.hosts();
h.add("example.com", "p");
}
sub vcl_recv {
return(synth(200));
}
sub vcl_synth {
set resp.http.p1 = h.policy("example.com", "/foo/bar");
set resp.http.s1 = blobcode.encode(blob=h.secret());
}
} -start
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.p1 == "2"
expect resp.http.s1 == "foo"
} -run
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
import blobcode;
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new h = hoailona.hosts();
}
sub vcl_recv {
return(synth(200));
}
sub vcl_synth {
set resp.http.s1 = blobcode.encode(blob=h.secret());
}
}
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.s1 == ""
} -run
logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
expect 0 * Begin req
expect * = VCL_Error "^vmod hoailona error: h.secret.. called before h.policy..$"
expect * = End
} -run
# looks like -*- vcl -*-
varnishtest "hosts.token()"
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
import std;
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(TOKEN, 1h);
new h = hoailona.hosts();
h.add("example.com", "p");
}
sub vcl_recv {
return(synth(200));
}
sub vcl_synth {
set resp.http.p1 = h.policy("example.com", "/foo/bar");
set resp.http.t1 = h.token();
set resp.http.st = regsub(resp.http.t1, "^st=(\d+).+$", "\1");
set resp.http.exp = regsub(resp.http.t1, "^.+exp=(\d+)$",
"\1");
set resp.http.diff = std.integer(resp.http.exp, 0)
- std.integer(resp.http.st, 0);
}
} -start
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.p1 == "2"
expect resp.http.t1 ~ "^st=\\d+~exp=\\d+$"
expect resp.http.st ~ "^\\d+$"
expect resp.http.exp ~ "^\\d+$"
expect resp.http.diff == "3600"
} -run
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(TOKEN, 1h);
new h = hoailona.hosts();
h.add("example.com", "p");
}
sub vcl_recv {
return(synth(200));
}
sub vcl_synth {
set resp.http.p1 = h.policy("example.com", "/foo/bar");
set resp.http.t1 = h.token();
set resp.http.t2 = h.token("/*");
set resp.http.t3 = h.token(data="foo=bar");
set resp.http.t4 = h.token(data="foo=bar", acl="/*");
}
}
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.p1 == "2"
expect resp.http.t1 ~ "^st=\\d+~exp=\\d+$"
expect resp.http.t2 ~ "^st=\\d+~exp=\\d+~acl=/\\*$"
expect resp.http.t3 ~ "^st=\\d+~exp=\\d+~data=foo=bar$"
expect resp.http.t4 ~ "^st=\\d+~exp=\\d+~acl=/\\*~data=foo=bar$"
} -run
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
import std;
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(TOKEN, 1h);
new h = hoailona.hosts();
h.add("example.com", "p");
}
sub vcl_recv {
return(synth(200));
}
sub vcl_synth {
set resp.http.p1 = h.policy("example.com", "/foo/bar");
set resp.http.t1 = h.token(ttl=2h);
set resp.http.st = regsub(resp.http.t1, "^st=(\d+).+$", "\1");
set resp.http.exp = regsub(resp.http.t1, "^.+exp=(\d+)$",
"\1");
set resp.http.diff = std.integer(resp.http.exp, 0)
- std.integer(resp.http.st, 0);
}
}
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.p1 == "2"
expect resp.http.t1 ~ "^st=\\d+~exp=\\d+$"
expect resp.http.st ~ "^\\d+$"
expect resp.http.exp ~ "^\\d+$"
expect resp.http.diff == "7200"
} -run
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
import std;
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(TOKEN, 1h, start_offset=0-10);
new h = hoailona.hosts();
h.add("example.com", "p");
}
sub vcl_recv {
return(synth(200));
}
sub vcl_synth {
set resp.http.p1 = h.policy("example.com", "/foo/bar");
set resp.http.t1 = h.token();
set resp.http.st = regsub(resp.http.t1, "^st=(\d+).+$", "\1");
set resp.http.exp = regsub(resp.http.t1, "^.+exp=(\d+)$",
"\1");
set resp.http.diff1 = std.integer(resp.http.exp, 0)
- std.integer(resp.http.st, 0);
set resp.http.t2 = h.token(ttl=2h);
set resp.http.st = regsub(resp.http.t2, "^st=(\d+).+$", "\1");
set resp.http.exp = regsub(resp.http.t2, "^.+exp=(\d+)$",
"\1");
set resp.http.diff2 = std.integer(resp.http.exp, 0)
- std.integer(resp.http.st, 0);
}
}
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.p1 == "2"
expect resp.http.t1 ~ "^st=\\d+~exp=\\d+$"
expect resp.http.diff1 == "3610"
expect resp.http.t2 ~ "^st=\\d+~exp=\\d+$"
expect resp.http.diff2 == "7210"
} -run
# Usage
varnish v1 -vcl {
import hoailona from "${vmod_topbuild}/src/.libs/libvmod_hoailona.so";
import std;
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = hoailona.policy(TOKEN, 1h);
new q = hoailona.policy(OPEN, 1h);
new h = hoailona.hosts();
h.add("example.com", "p");
h.add("example.org", "q");
}
sub vcl_recv {
return(synth(200));
}
sub vcl_synth {
set resp.http.t0 = h.token();
# VCC does not accept a negative value for the DURATION,
# so we can't test the validation in the C code.
#
# set resp.http.p1 = h.policy("example.com", "/foo/bar");
# set resp.http.t1 = h.token(ttl=-1s);
set resp.http.p2 = h.policy("example.org", "/foo/bar");
set resp.http.t2 = h.token();
}
}
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.t0 == ""
# expect resp.http.p1 == "2"
# expect resp.http.t1 == ""
expect resp.http.p2 == "1"
expect resp.http.t2 == ""
} -run
logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
expect 0 * Begin req
expect * = VCL_Error "^vmod hoailona error: h.token.. called before h.policy..$"
expect * = VCL_Error "^vmod hoailona error: in h.token..: policy q does not specify a token$"
expect * = End
} -run
This diff is collapsed.
/*-
* Copyright 2017 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "vtree.h"
#include "vrt.h"
#include "vre.h"
enum policy_type {
DENY = 0,
OPEN = 1,
TOKEN = 2
};
struct pattern {
unsigned magic;
#define VMOD_HOAILONA_PATTERN_MAGIC 0x1876e01f
char *path;
vre_t *re;
};
struct vmod_hoailona_policy {
unsigned magic;
#define VMOD_HOAILONA_POLICY_MAGIC 0xf729cbfa
char *vcl_name;
char *description;
struct vmod_priv *secret;
VCL_DURATION ttl;
enum policy_type type;
VCL_INT start_offset;
};
struct assignment {
unsigned magic;
#define VMOD_HOAILONA_ASSIGNMENT_MAGIC 0x7523d6e8
VRB_ENTRY(assignment) entry;
struct pattern *pattern;
struct vmod_hoailona_policy *policy;
char *description;
};
VRB_HEAD(assign_tree, assignment);
int path_cmp(const struct assignment * restrict const ass_a,
const struct assignment * restrict const ass_b);
VRB_PROTOTYPE(assign_tree, assignment, entry, path_cmp)
void validation_init(void);
const char * valid(VRT_CTX, const char * restrict const path);
vre_t * pattern2re(VRT_CTX, const char * restrict const path);
#-
# This document is licensed under the same conditions
# as the libvmod-hoailona project. See LICENSE for details.
#
# Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
#
$Module hoailona 3 Akamai SecureHD Token Authorization VMOD
::
new OBJECT = ...
DESCRIPTION
===========
This Varnish Module (VMOD) ...
$Object policy(PRIV_TASK, ENUM {OPEN, DENY, TOKEN} type, DURATION ttl,
STRING description=0, BLOB secret=0, INT start_offset=0)
$Object hosts()
$Method VOID .add(PRIV_TASK, STRING host, STRING policy, STRING path=0,
STRING description=0)
# $Method adds are done
$Method INT .policy(PRIV_TASK, STRING host, STRING path)
$Method STRING .token(PRIV_TASK, STRING acl=0, DURATION ttl=0, STRING data=0)
$Method BLOB .secret(PRIV_TASK)
$Method STRING .explain(PRIV_TASK)
$Function STRING version()
Returns the version string for this VMOD.
Example::
std.log("Using VMOD hoailona version " + hoailona.version());
ERRORS
======
...
REQUIREMENTS
============
This VMOD requires Varnish ...
LIMITATIONS
===========
...
INSTALLATION
============
See ``INSTALL``.
SEE ALSO
========
* varnishd(1)
* vcl(7)
$Event event
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment