Browse Source

mod_vhostdb* (dbi,mysql,pgsql,ldap) (fixes #485, fixes #1936, fixes #2297)

mod_vhostdb - vhost docroot lookups

backends:

mod_vhostdb_dbi
mod_vhostdb_ldap
mod_vhostdb_mysql  (now preferred over mod_mysql_vhost.c)
mod_vhostdb_pgsql

STATUS: experimental  (testing and feedback appreciated)

x-ref:
  "PostgreSQL virtual host support"
  https://redmine.lighttpd.net/issues/485
  "LDAP Virtual Host Definition Storage Integration"
  https://redmine.lighttpd.net/issues/1936
  "mod_dbi_vhost (patch included)"
  https://redmine.lighttpd.net/issues/2297
personal/stbuehler/mod-csrf
Glenn Strauss 4 years ago
parent
commit
2f83aac9fb
  1. 17
      SConstruct
  2. 101
      configure.ac
  3. 50
      src/CMakeLists.txt
  4. 54
      src/Makefile.am
  5. 10
      src/SConscript
  6. 30
      src/http_vhostdb.c
  7. 18
      src/http_vhostdb.h
  8. 238
      src/mod_vhostdb.c
  9. 324
      src/mod_vhostdb_dbi.c
  10. 512
      src/mod_vhostdb_ldap.c
  11. 288
      src/mod_vhostdb_mysql.c
  12. 266
      src/mod_vhostdb_pgsql.c
  13. 10
      src/server.c

17
SConstruct

@ -115,6 +115,8 @@ vars.AddVariables(
('sbindir', 'binary directory', '${prefix}/sbin'),
('libdir', 'library directory', '${prefix}/lib'),
PackageVariable('with_mysql', 'enable mysql support', 'no'),
PackageVariable('with_pgsql', 'enable pgsql support', 'no'),
PackageVariable('with_dbi', 'enable dbi support', 'no'),
PackageVariable('with_xml', 'enable xml support', 'no'),
PackageVariable('with_pcre', 'enable pcre support', 'yes'),
PathVariable('CC', 'path to the c-compiler', None),
@ -227,6 +229,7 @@ if 1:
checkTypes(autoconf, Split('pid_t size_t off_t'))
autoconf.env.Append( LIBSQLITE3 = '', LIBXML2 = '', LIBMYSQL = '', LIBZ = '',
LIBPGSQL = '', LIBDBI = '',
LIBBZ2 = '', LIBCRYPT = '', LIBMEMCACHED = '', LIBFCGI = '', LIBPCRE = '',
LIBLDAP = '', LIBLBER = '', LIBLUA = '', LIBDL = '', LIBUUID = '',
LIBKRB5 = '', LIBGSSAPI_KRB5 = '', LIBGDBM = '', LIBSSL = '', LIBCRYPTO = '')
@ -362,6 +365,20 @@ if env['with_mysql']:
env.Append(CPPFLAGS = [ '-DHAVE_MYSQL_H', '-DHAVE_LIBMYSQL' ], LIBMYSQL = 'mysqlclient')
env['LIBS'] = oldlib
if env['with_pgsql']:
pg_config = checkProgram(env, 'pgsql', 'pg_config')
oldlib = env['LIBS']
env['LIBS'] = []
env.ParseConfig(pg_config + ' --includedir --libdir')
env.Append(CPPFLAGS = [ '-DHAVE_PGSQL_H', '-DHAVE_LIBPGSQL' ], LIBPGSQL = 'pq')
env['LIBS'] = oldlib
#if autoconf.CheckLibWithHeader('pq', 'libpq-fe.h', 'C'):
# env.Append(CPPFLAGS = [ '-DHAVE_PGSQL_H', '-DHAVE_LIBPGSQL' ], LIBPGSQL = 'pq')
if env['with_dbi']:
if autoconf.CheckLibWithHeader('dbi', 'dbi/dbi.h', 'C'):
env.Append(CPPFLAGS = [ '-DHAVE_DBI_H', '-DHAVE_LIBDBI' ], LIBDBI = 'dbi')
if re.compile("cygwin|mingw|midipix").search(env['PLATFORM']):
env.Append(COMMON_LIB = 'bin')
elif re.compile("darwin|aix").search(env['PLATFORM']):

101
configure.ac

@ -231,6 +231,88 @@ AM_CONDITIONAL(BUILD_WITH_MYSQL, test ! $WITH_MYSQL = no)
AC_SUBST(MYSQL_LIBS)
AC_SUBST(MYSQL_INCLUDE)
dnl Checks for database.
PGSQL_INCLUDE=""
PGSQL_LIBS=""
AC_MSG_CHECKING(for PgSQL support)
AC_ARG_WITH(pgsql,
AC_HELP_STRING([--with-pgsql@<:@=PATH@:>@],[Include PgSQL support. PATH is the path to 'pg_config']),
[WITH_PGSQL=$withval],[WITH_PGSQL=no])
if test "$WITH_PGSQL" != "no"; then
AC_MSG_RESULT(yes)
if test "$WITH_PGSQL" = "yes"; then
AC_PATH_PROG(PGSQL_CONFIG, pg_config)
else
PGSQL_CONFIG=$WITH_PGSQL
fi
if test "$PGSQL_CONFIG" = ""; then
AC_MSG_ERROR(pg_config is not found)
fi
if test \! -x $PGSQL_CONFIG; then
AC_MSG_ERROR(pg_config not exists or not executable, use --with-pgsql=path-to-pg_config)
fi
PGSQL_INCLUDE="-I`$PGSQL_CONFIG --includedir`"
PGSQL_LIBS="-L`$PGSQL_CONFIG --libdir` -lpq"
AC_MSG_CHECKING(for PgSQL includes at)
AC_MSG_RESULT($PGSQL_INCLUDE)
AC_MSG_CHECKING(for PgSQL libraries at)
AC_MSG_RESULT($PGSQL_LIBS)
AC_DEFINE([HAVE_PGSQL], [1], [pgsql support])
else
AC_MSG_RESULT(no)
fi
AM_CONDITIONAL(BUILD_WITH_PGSQL, test ! $WITH_PGSQL = no)
AC_SUBST(PGSQL_LIBS)
AC_SUBST(PGSQL_INCLUDE)
dnl Checks for libdbi library
DBI_INCLUDE=""
DBI_LIBS=""
AC_MSG_CHECKING(for LibDBI support)
AC_ARG_WITH(dbi,
AC_HELP_STRING([--with-dbi@<:@=PATH@:>@],[Include DBI support in PATH/include/dbi.h and PATH/lib]),
[WITH_DBI=$withval],[WITH_DBI=no])
if test "$WITH_DBI" != "no"; then
AC_MSG_RESULT(yes)
if test "$WITH_DBI" != "yes"; then
DBI_CFLAGS="-I$WITH_LIBDBI/include"
DBI_LIBS="-L$WITH_LIBDBI/lib -ldbi"
else
AC_CHECK_HEADERS([dbi/dbi.h],[
AC_CHECK_LIB([dbi], [dbi_version], [
DBI_CFLAGS=""
DBI_LIBS="-ldbi"
],[
AC_MSG_ERROR([LibDBI not found])
]
)],[
AC_MSG_ERROR([LibDBI not found])
]
)
fi
AC_DEFINE([HAVE_DBI], [1], [LibDBI support])
else
AC_MSG_RESULT(no)
fi
AM_CONDITIONAL(BUILD_WITH_DBI, test ! $WITH_DBI = no)
AC_SUBST(DBI_LIBS)
AC_SUBST(DBI_CFLAGS)
dnl Check for LDAP
AC_MSG_CHECKING(for LDAP support)
AC_ARG_WITH(ldap, AC_HELP_STRING([--with-ldap],[enable LDAP support]),
@ -921,6 +1003,7 @@ AC_OUTPUT
do_build="mod_cgi mod_fastcgi mod_extforward mod_proxy mod_evhost mod_simple_vhost mod_access mod_alias mod_setenv mod_usertrack mod_auth mod_authn_file mod_status mod_accesslog"
do_build="$do_build mod_rrdtool mod_secdownload mod_expire mod_compress mod_dirlisting mod_indexfile mod_userdir mod_webdav mod_staticfile mod_scgi mod_flv_streaming mod_ssi mod_deflate"
do_build="$do_build mod_vhostdb"
plugins="mod_rewrite mod_redirect"
features="regex-conditionals"
@ -941,13 +1024,27 @@ else
fi
fi
plugins="mod_authn_mysql mod_mysql_vhost"
plugins="mod_authn_mysql mod_mysql_vhost mod_vhostdb_mysql"
if test ! "x$MYSQL_LIBS" = x; then
do_build="$do_build $plugins"
else
no_build="$no_build $plugins"
fi
plugins="mod_vhostdb_pgsql"
if test ! "x$PGSQL_LIBS" = x; then
do_build="$do_build $plugins"
else
no_build="$no_build $plugins"
fi
plugins="mod_vhostdb_dbi"
if test ! "x$DBI_LIBS" = x; then
do_build="$do_build $plugins"
else
no_build="$no_build $plugins"
fi
plugins="mod_cml mod_magnet"
if test ! "x$LUA_LIBS" = x; then
do_build="$do_build $plugins"
@ -997,7 +1094,7 @@ else
no_build="$no_build $plugins"
fi
plugins="mod_authn_ldap"
plugins="mod_authn_ldap mod_vhostdb_ldap"
if test ! "x$LDAP_LIB" = x; then
do_build="$do_build $plugins"
else

50
src/CMakeLists.txt

@ -14,19 +14,19 @@ include(LighttpdMacros)
add_definitions(-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGE_FILES)
option(WITH_XATTR "with xattr-support for the stat-cache [default: off]")
option(WITH_MYSQL "with mysql-support for the mod_sql_vhost [default: off]")
# option(WITH_POSTGRESQL "with postgress-support for the mod_sql_vhost [default: off]")
option(WITH_MYSQL "with mysql-support for mod_vhostdb_mysql [default: off]")
option(WITH_PGSQL "with postgres-support for mod_vhostdb_pgsql [default: off]")
option(WITH_DBI "with dbi-support for mod_vhostdb_dbi [default: off]")
option(WITH_OPENSSL "with openssl-support [default: off]")
option(WITH_PCRE "with regex support [default: on]" ON)
option(WITH_WEBDAV_PROPS "with property-support for mod_webdav [default: off]")
option(WITH_WEBDAV_LOCKS "locks in webdav [default: off]")
option(WITH_BZIP "with bzip2-support for mod_compress [default: off]")
option(WITH_ZLIB "with deflate-support for mod_compress [default: on]" ON)
option(WITH_KRB5 "with Kerberos5-support for the mod_auth [default: off]")
option(WITH_LDAP "with LDAP-support for the mod_auth [default: off]")
option(WITH_KRB5 "with Kerberos5-support for mod_auth [default: off]")
option(WITH_LDAP "with LDAP-support for mod_auth mod_vhostdb_ldap [default: off]")
option(WITH_LUA "with lua 5.1 for mod_magnet [default: off]")
# option(WITH_VALGRIND "with internal support for valgrind [default: off]")
# option(WITH_KERBEROS5 "use Kerberos5 support with OpenSSL [default: off]")
option(WITH_FAM "fam/gamin for reducing number of stat() calls [default: off]")
option(WITH_GDBM "gdbm storage for mod_trigger_b4_dl [default: off]")
option(WITH_MEMCACHED "memcached storage for mod_trigger_b4_dl [default: off]")
@ -248,6 +248,28 @@ else()
unset(HAVE_MYSQL)
endif()
if(WITH_PGSQL)
xconfig(pg_config PGSQL_INCDIR PGSQL_LIBDIR PGSQL_LDFLAGS PGSQL_CFLAGS)
check_include_files(libpq-fe.h HAVE_PGSQL_H)
if(HAVE_PGSQL_H)
check_library_exists(pq PQsetdbLogin "" HAVE_PGSQL)
endif()
else()
unset(HAVE_PGSQL_H)
unset(HAVE_PGSQL)
endif()
if(WITH_DBI)
check_include_files(dbi/dbi.h HAVE_DBI_H)
if(HAVE_DBI_H)
check_library_exists(dbi dbi_conn_connect "" HAVE_DBI)
endif()
else()
unset(HAVE_DBI_H)
unset(HAVE_DBI)
endif()
if(WITH_OPENSSL)
if(APPLE)
set(CMAKE_REQUIRED_INCLUDES /opt/local/include)
@ -530,6 +552,7 @@ set(COMMON_SRC
configfile-glue.c
http-header-glue.c
http_auth.c
http_vhostdb.c
splaytree.c
rand.c
status_counter.c safe_memclear.c
@ -605,6 +628,7 @@ add_and_install_library(mod_status mod_status.c)
add_and_install_library(mod_uploadprogress mod_uploadprogress.c)
add_and_install_library(mod_userdir mod_userdir.c)
add_and_install_library(mod_usertrack mod_usertrack.c)
add_and_install_library(mod_vhostdb mod_vhostdb.c)
add_and_install_library(mod_webdav mod_webdav.c)
add_executable(test_buffer
@ -668,6 +692,8 @@ endif()
if(HAVE_MYSQL_H AND HAVE_MYSQL)
add_and_install_library(mod_mysql_vhost "mod_mysql_vhost.c")
target_link_libraries(mod_mysql_vhost mysqlclient)
add_and_install_library(mod_vhostdb_mysql "mod_vhostdb_mysql.c")
target_link_libraries(mod_vhostdb_mysql mysqlclient)
include_directories(/usr/include/mysql)
add_and_install_library(mod_authn_mysql "mod_authn_mysql.c")
@ -678,6 +704,16 @@ if(HAVE_MYSQL_H AND HAVE_MYSQL)
target_link_libraries(mod_authn_mysql ${L_MOD_AUTHN_MYSQL} mysqlclient)
endif()
if(HAVE_PGSQL_H AND HAVE_PGSQL)
add_and_install_library(mod_vhostdb_pgsql "mod_vhostdb_pgsql.c")
target_link_libraries(mod_vhostdb_pgsql pq)
endif()
if(HAVE_DBI_H AND HAVE_DBI)
add_and_install_library(mod_vhostdb_dbi "mod_vhostdb_dbi.c")
target_link_libraries(mod_vhostdb_dbi dbi)
endif()
set(L_MOD_WEBDAV)
if(HAVE_SQLITE3_H)
set(L_MOD_WEBDAV ${L_MOD_WEBDAV} sqlite3)
@ -707,9 +743,11 @@ if(WITH_KRB5)
endif()
if(WITH_LDAP)
add_and_install_library(mod_authn_ldap "mod_authn_ldap.c")
set(L_MOD_AUTHN_LDAP ${L_MOD_AUTHN_LDAP} ldap lber)
add_and_install_library(mod_authn_ldap "mod_authn_ldap.c")
target_link_libraries(mod_authn_ldap ${L_MOD_AUTHN_LDAP})
add_and_install_library(mod_vhostdb_ldap "mod_vhostdb_ldap.c")
target_link_libraries(mod_vhostdb_ldap ${L_MOD_AUTHN_LDAP})
endif()
if(HAVE_ZLIB_H)

54
src/Makefile.am

@ -72,6 +72,7 @@ common_src=base64.c buffer.c log.c \
configfile-glue.c \
http-header-glue.c \
http_auth.c \
http_vhostdb.c \
rand.c \
splaytree.c status_counter.c \
safe_memclear.c
@ -150,6 +151,18 @@ mod_trigger_b4_dl_la_LDFLAGS = $(common_module_ldflags)
mod_trigger_b4_dl_la_LIBADD = $(GDBM_LIB) $(MEMCACHED_LIB) $(PCRE_LIB) $(common_libadd)
endif
lib_LTLIBRARIES += mod_vhostdb.la
mod_vhostdb_la_SOURCES = mod_vhostdb.c
mod_vhostdb_la_LDFLAGS = $(common_module_ldflags)
mod_vhostdb_la_LIBADD = $(common_libadd)
if BUILD_WITH_LDAP
lib_LTLIBRARIES += mod_vhostdb_ldap.la
mod_vhostdb_ldap_la_SOURCES = mod_vhostdb_ldap.c
mod_vhostdb_ldap_la_LDFLAGS = $(common_module_ldflags)
mod_vhostdb_ldap_la_LIBADD = $(LDAP_LIB) $(LBER_LIB) $(common_libadd)
endif
if BUILD_WITH_MYSQL
lib_LTLIBRARIES += mod_mysql_vhost.la
mod_mysql_vhost_la_SOURCES = mod_mysql_vhost.c
@ -158,6 +171,30 @@ mod_mysql_vhost_la_LIBADD = $(MYSQL_LIBS) $(common_libadd)
mod_mysql_vhost_la_CPPFLAGS = $(MYSQL_INCLUDE)
endif
if BUILD_WITH_MYSQL
lib_LTLIBRARIES += mod_vhostdb_mysql.la
mod_vhostdb_mysql_la_SOURCES = mod_vhostdb_mysql.c
mod_vhostdb_mysql_la_LDFLAGS = $(common_module_ldflags)
mod_vhostdb_mysql_la_LIBADD = $(MYSQL_LIBS) $(common_libadd)
mod_vhostdb_mysql_la_CPPFLAGS = $(MYSQL_INCLUDE)
endif
if BUILD_WITH_PGSQL
lib_LTLIBRARIES += mod_vhostdb_pgsql.la
mod_vhostdb_pgsql_la_SOURCES = mod_vhostdb_pgsql.c
mod_vhostdb_pgsql_la_LDFLAGS = $(common_module_ldflags)
mod_vhostdb_pgsql_la_LIBADD = $(PGSQL_LIBS) $(common_libadd)
mod_vhostdb_pgsql_la_CPPFLAGS = $(PGSQL_INCLUDE)
endif
if BUILD_WITH_DBI
lib_LTLIBRARIES += mod_vhostdb_dbi.la
mod_vhostdb_dbi_la_SOURCES = mod_vhostdb_dbi.c
mod_vhostdb_dbi_la_LDFLAGS = $(common_module_ldflags)
mod_vhostdb_dbi_la_LIBADD = $(DBI_LIBS) $(common_libadd)
mod_vhostdb_dbi_la_CPPFLAGS = $(DBI_CFLAGS)
endif
lib_LTLIBRARIES += mod_cgi.la
mod_cgi_la_SOURCES = mod_cgi.c
mod_cgi_la_LDFLAGS = $(common_module_ldflags)
@ -336,7 +373,7 @@ mod_uploadprogress_la_LIBADD = $(common_libadd)
hdr = server.h base64.h buffer.h network.h log.h keyvalue.h \
response.h request.h fastcgi.h chunk.h \
first.h settings.h http_chunk.h \
md5.h http_auth.h stream.h \
md5.h http_auth.h http_vhostdb.h stream.h \
fdevent.h connections.h base.h stat_cache.h \
plugin.h \
etag.h joblist.h array.h vector.h crc32.h \
@ -387,6 +424,7 @@ lighttpd_SOURCES = \
mod_uploadprogress.c \
mod_userdir.c \
mod_usertrack.c \
mod_vhostdb.c \
mod_webdav.c
lighttpd_CPPFLAGS = \
-DLIGHTTPD_STATIC \
@ -415,14 +453,24 @@ lighttpd_SOURCES += mod_authn_gssapi.c
lighttpd_LDADD += $(KRB5_LIB)
endif
if BUILD_WITH_LDAP
lighttpd_SOURCES += mod_authn_ldap.c
lighttpd_SOURCES += mod_authn_ldap.c mod_vhostdb_ldap.c
lighttpd_LDADD += $(LDAP_LIB) $(LBER_LIB)
endif
if BUILD_WITH_MYSQL
lighttpd_SOURCES += mod_authn_mysql.c mod_mysql_vhost.c
lighttpd_SOURCES += mod_authn_mysql.c mod_mysql_vhost.c mod_vhostdb_mysql.c
lighttpd_CPPFLAGS += $(MYSQL_INCLUDE)
lighttpd_LDADD += $(MYSQL_LIBS)
endif
if BUILD_WITH_PGSQL
lighttpd_SOURCES += mod_vhostdb_pgsql.c
lighttpd_CPPFLAGS += $(PGSQL_INCLUDE)
lighttpd_LDADD += $(PGSQL_LIBS)
endif
if BUILD_WITH_DBI
lighttpd_SOURCES += mod_vhostdb_dbi.c
lighttpd_CPPFLAGS += $(DBI_CFLAGS)
lighttpd_LDADD += $(DBI_LIBS)
endif
if BUILD_WITH_OPENSSL
lighttpd_SOURCES += mod_openssl.c
lighttpd_LDADD += $(SSL_LIB)

10
src/SConscript

@ -66,6 +66,7 @@ common_src = Split("base64.c buffer.c log.c \
configfile-glue.c \
http-header-glue.c \
http_auth.c \
http_vhostdb.c \
splaytree.c \
rand.c \
status_counter.c safe_memclear.c \
@ -122,6 +123,7 @@ modules = {
'mod_evasive' : { 'src' : [ 'mod_evasive.c' ] },
'mod_ssi' : { 'src' : [ 'mod_ssi_exprparser.c', 'mod_ssi_expr.c', 'mod_ssi.c' ] },
'mod_flv_streaming' : { 'src' : [ 'mod_flv_streaming.c' ] },
'mod_vhostdb' : { 'src' : [ 'mod_vhostdb.c' ] },
}
if env['with_geoip']:
@ -132,6 +134,7 @@ if env['with_krb5']:
if env['with_ldap']:
modules['mod_authn_ldap'] = { 'src' : [ 'mod_authn_ldap.c' ], 'lib' : [ env['LIBLDAP'], env['LIBLBER'] ] }
modules['mod_vhostdb_ldap'] = { 'src' : [ 'mod_vhostdb_ldap.c' ], 'lib' : [ env['LIBLDAP'], env['LIBLBER'] ] }
if env['with_lua']:
modules['mod_magnet'] = { 'src' : [ 'mod_magnet.c', 'mod_magnet_cache.c' ], 'lib' : [ env['LIBLUA'] ] }
@ -146,6 +149,13 @@ if env['with_pcre'] and (env['with_memcached'] or env['with_gdbm']):
if env['with_mysql']:
modules['mod_authn_mysql'] = { 'src' : [ 'mod_authn_mysql.c' ], 'lib' : [ env['LIBCRYPT'], env['LIBMYSQL'] ] }
modules['mod_mysql_vhost'] = { 'src' : [ 'mod_mysql_vhost.c' ], 'lib' : [ env['LIBMYSQL'] ] }
modules['mod_vhostdb_mysql'] = { 'src' : [ 'mod_vhostdb_mysql.c' ], 'lib' : [ env['LIBMYSQL'] ] }
if env['with_pgsql']:
modules['mod_vhostdb_pgsql'] = { 'src' : [ 'mod_vhostdb_pgsql.c' ], 'lib' : [ env['LIBPGSQL'] ] }
if env['with_dbi']:
modules['mod_vhostdb_dbi'] = { 'src' : [ 'mod_vhostdb_dbi.c' ], 'lib' : [ env['LIBDBI'] ] }
if env['with_openssl']:
modules['mod_openssl'] = { 'src' : [ 'mod_openssl.c' ], 'lib' : [ env['LIBSSL'], env['LIBCRYPTO'] ] }

30
src/http_vhostdb.c

@ -0,0 +1,30 @@
#include "first.h"
#include "http_vhostdb.h"
#include <string.h>
static http_vhostdb_backend_t http_vhostdb_backends[8];
const http_vhostdb_backend_t * http_vhostdb_backend_get (const buffer *name)
{
int i = 0;
while (NULL != http_vhostdb_backends[i].name
&& 0 != strcmp(http_vhostdb_backends[i].name, name->ptr)) {
++i;
}
return (NULL != http_vhostdb_backends[i].name)
? http_vhostdb_backends+i
: NULL;
}
void http_vhostdb_backend_set (const http_vhostdb_backend_t *backend)
{
unsigned int i = 0;
while (NULL != http_vhostdb_backends[i].name) ++i;
/*(must resize http_vhostdb_backends[] if too many different backends)*/
force_assert(
i < (sizeof(http_vhostdb_backends)/sizeof(http_vhostdb_backend_t))-1);
memcpy(http_vhostdb_backends+i, backend, sizeof(http_vhostdb_backend_t));
}

18
src/http_vhostdb.h

@ -0,0 +1,18 @@
#ifndef _HTTP_VHOST_H_
#define _HTTP_VHOST_H_
#include "first.h"
#include "base.h"
struct http_vhostdb_backend_t;
typedef struct http_vhostdb_backend_t {
const char *name;
int(*query)(server *srv, connection *con, void *p_d, buffer *result);
void *p_d;
} http_vhostdb_backend_t;
const http_vhostdb_backend_t * http_vhostdb_backend_get (const buffer *name);
void http_vhostdb_backend_set (const http_vhostdb_backend_t *backend);
#endif

238
src/mod_vhostdb.c

@ -0,0 +1,238 @@
#include "first.h"
#include "plugin.h"
#include "http_vhostdb.h"
#include "log.h"
#include "stat_cache.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
/**
* vhostdb framework
*/
typedef struct {
buffer *vhostdb_backend_conf;
/* generated */
const http_vhostdb_backend_t *vhostdb_backend;
} plugin_config;
typedef struct {
PLUGIN_DATA;
plugin_config **config_storage;
plugin_config conf;
buffer *tmp_buf;
} plugin_data;
INIT_FUNC(mod_vhostdb_init) {
plugin_data *p = calloc(1, sizeof(*p));
p->tmp_buf = buffer_init();
return p;
}
FREE_FUNC(mod_vhostdb_free) {
plugin_data *p = p_d;
if (!p) return HANDLER_GO_ON;
if (p->config_storage) {
size_t i;
for (i = 0; i < srv->config_context->used; i++) {
plugin_config *s = p->config_storage[i];
if (NULL == s) continue;
buffer_free(s->vhostdb_backend_conf);
free(s);
}
free(p->config_storage);
}
free(p->tmp_buf);
free(p);
UNUSED(srv);
return HANDLER_GO_ON;
}
SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) {
plugin_data *p = p_d;
config_values_t cv[] = {
{ "vhostdb.backend", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
};
p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
for (size_t i = 0; i < srv->config_context->used; ++i) {
data_config const *config = (data_config const*)srv->config_context->data[i];
plugin_config *s = calloc(1, sizeof(plugin_config));
s->vhostdb_backend_conf = buffer_init();
cv[0].destination = s->vhostdb_backend_conf;
p->config_storage[i] = s;
if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
return HANDLER_ERROR;
}
if (!buffer_string_is_empty(s->vhostdb_backend_conf)) {
s->vhostdb_backend =
http_vhostdb_backend_get(s->vhostdb_backend_conf);
if (NULL == s->vhostdb_backend) {
log_error_write(srv, __FILE__, __LINE__, "sb",
"vhostdb.backend not supported:",
s->vhostdb_backend_conf);
return HANDLER_ERROR;
}
}
}
return HANDLER_GO_ON;
}
#define PATCH(x) \
p->conf.x = s->x;
static int mod_vhostdb_patch_connection(server *srv, connection *con, plugin_data *p) {
plugin_config *s = p->config_storage[0];
PATCH(vhostdb_backend);
/* skip the first, the global context */
for (size_t i = 1; i < srv->config_context->used; ++i) {
data_config *dc = (data_config *)srv->config_context->data[i];
s = p->config_storage[i];
/* condition didn't match */
if (!config_check_cond(srv, con, dc)) continue;
/* merge config */
for (size_t j = 0; j < dc->value->used; ++j) {
data_unset *du = dc->value->data[j];
if (buffer_is_equal_string(du->key, CONST_STR_LEN("vhostdb.backend"))) {
PATCH(vhostdb_backend);
}
}
}
return 0;
}
#undef PATCH
typedef struct {
buffer *server_name;
buffer *document_root;
} vhostdb_entry;
static vhostdb_entry * vhostdb_entry_init (void)
{
vhostdb_entry *ve = calloc(1, sizeof(*ve));
ve->server_name = buffer_init();
ve->document_root = buffer_init();
return ve;
}
static void vhostdb_entry_free (vhostdb_entry *ve)
{
buffer_free(ve->server_name);
buffer_free(ve->document_root);
free(ve);
}
CONNECTION_FUNC(mod_vhostdb_handle_connection_close) {
plugin_data *p = p_d;
vhostdb_entry *ve;
if ((ve = con->plugin_ctx[p->id])) {
con->plugin_ctx[p->id] = NULL;
vhostdb_entry_free(ve);
}
UNUSED(srv);
return HANDLER_GO_ON;
}
static handler_t mod_vhostdb_error_500 (connection *con)
{
con->http_status = 500; /* Internal Server Error */
con->mode = DIRECT;
return HANDLER_FINISHED;
}
static handler_t mod_vhostdb_found (connection *con, vhostdb_entry *ve)
{
/* fix virtual server and docroot */
buffer_copy_buffer(con->server_name, ve->server_name);
buffer_copy_buffer(con->physical.doc_root, ve->document_root);
return HANDLER_GO_ON;
}
CONNECTION_FUNC(mod_vhostdb_handle_docroot) {
plugin_data *p = p_d;
vhostdb_entry *ve;
const http_vhostdb_backend_t *backend;
buffer *b;
stat_cache_entry *sce;
/* no host specified? */
if (buffer_string_is_empty(con->uri.authority)) return HANDLER_GO_ON;
/* XXX: future: implement larger, managed cache
* of database responses (positive and negative) */
/* check if cached this connection */
ve = con->plugin_ctx[p->id];
if (ve && buffer_is_equal(ve->server_name, con->uri.authority)) {
return mod_vhostdb_found(con, ve); /* HANDLER_GO_ON */
}
mod_vhostdb_patch_connection(srv, con, p);
if (!p->conf.vhostdb_backend) return HANDLER_GO_ON;
b = p->tmp_buf;
backend = p->conf.vhostdb_backend;
if (0 != backend->query(srv, con, backend->p_d, b)) {
return mod_vhostdb_error_500(con); /* HANDLER_FINISHED */
}
if (buffer_string_is_empty(b)) {
/* no such virtual host */
return HANDLER_GO_ON;
}
/* sanity check that really is a directory */
buffer_append_slash(b);
if (HANDLER_ERROR == stat_cache_get_entry(srv, con, b, &sce)) {
log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), b);
return mod_vhostdb_error_500(con); /* HANDLER_FINISHED */
}
if (!S_ISDIR(sce->st.st_mode)) {
log_error_write(srv, __FILE__, __LINE__, "sb", "Not a directory", b);
return mod_vhostdb_error_500(con); /* HANDLER_FINISHED */
}
/* cache the data */
if (!ve) con->plugin_ctx[p->id] = ve = vhostdb_entry_init();
buffer_copy_buffer(ve->server_name, con->uri.authority);
buffer_copy_buffer(ve->document_root, b);
return mod_vhostdb_found(con, ve); /* HANDLER_GO_ON */
}
int mod_vhostdb_plugin_init(plugin *p);
int mod_vhostdb_plugin_init(plugin *p) {
p->version = LIGHTTPD_VERSION_ID;
p->name = buffer_init_string("vhostdb");
p->init = mod_vhostdb_init;
p->cleanup = mod_vhostdb_free;
p->set_defaults = mod_vhostdb_set_defaults;
p->handle_docroot = mod_vhostdb_handle_docroot;
p->connection_reset = mod_vhostdb_handle_connection_close;
p->data = NULL;
return 0;
}

324
src/mod_vhostdb_dbi.c

@ -0,0 +1,324 @@
#include "first.h"
#include <dbi/dbi.h>
#include <string.h>
#include <stdlib.h>
#include "base.h"
#include "http_vhostdb.h"
#include "log.h"
#include "plugin.h"
/*
* virtual host plugin using DBI for domain to directory lookups
*
* e.g.
* vhostdb.dbi = ( "sql" => "SELECT docroot FROM vhosts WHERE host='?'"
* "dbtype" => "sqlite3",
* "dbname" => "mydb.sqlite",
* "sqlite_dbdir" => "/path/to/sqlite/dbs/" )
*/
typedef struct {
dbi_conn dbconn;
dbi_inst dbinst;
buffer *sqlquery;
server *srv;
short reconnect_count;
} vhostdb_config;
typedef struct {
void *vdata;
array *options;
} plugin_config;
typedef struct {
PLUGIN_DATA;
plugin_config **config_storage;
plugin_config conf;
} plugin_data;
/* used to reconnect to the database when we get disconnected */
static void mod_vhostdb_dbi_error_callback (dbi_conn dbconn, void *vdata)
{
vhostdb_config *dbconf = (vhostdb_config *)vdata;
const char *errormsg = NULL;
/*assert(dbconf->dbconn == dbconn);*/
while (++dbconf->reconnect_count <= 3) { /* retry */
if (0 == dbi_conn_connect(dbconn)) {
fd_close_on_exec(dbi_conn_get_socket(dbconn));
return;
}
}
dbi_conn_error(dbconn, &errormsg);
log_error_write(dbconf->srv, __FILE__, __LINE__, "ss",
"dbi_conn_connect():", errormsg);
}
static void mod_vhostdb_dbconf_free (void *vdata)
{
vhostdb_config *dbconf = (vhostdb_config *)vdata;
if (!dbconf) return;
dbi_conn_close(dbconf->dbconn);
dbi_shutdown_r(dbconf->dbinst);
free(dbconf);
}
static int mod_vhostdb_dbconf_setup (server *srv, array *opts, void **vdata)
{
buffer *sqlquery = NULL;
const buffer *dbtype=NULL, *dbname=NULL;
for (size_t i = 0; i < opts->used; ++i) {
const data_string *ds = (data_string *)opts->data[i];
if (ds->type == TYPE_STRING) {
if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("sql"))) {
sqlquery = ds->value;
} else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("dbname"))) {
dbname = ds->value;
} else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("dbtype"))) {
dbtype = ds->value;
}
}
}
/* required:
* - sql (sql query)
* - dbtype
* - dbname
*
* optional:
* - username, some databases don't require this (sqlite)
* - password, default: empty
* - socket, default: database type default
* - hostname, if set overrides socket
* - port, default: database default
* - encoding, default: database default
*/
if (!buffer_string_is_empty(sqlquery)
&& !buffer_is_empty(dbname) && !buffer_is_empty(dbtype)) {
/* create/initialise database */
vhostdb_config *dbconf;
dbi_inst dbinst = NULL;
dbi_conn dbconn;
if (dbi_initialize_r(NULL, &dbinst) < 1) {
log_error_write(srv, __FILE__, __LINE__, "s",
"dbi_initialize_r() failed. "
"Do you have the DBD for this db type installed?");
return -1;
}
dbconn = dbi_conn_new_r(dbtype->ptr, dbinst);
if (NULL == dbconn) {
log_error_write(srv, __FILE__, __LINE__, "s",
"dbi_conn_new_r() failed. "
"Do you have the DBD for this db type installed?");
dbi_shutdown_r(dbinst);
return -1;
}
/* set options */
for (size_t j = 0; j < opts->used; ++j) {
data_unset *du = opts->data[j];
const buffer *opt = du->key;
if (!buffer_string_is_empty(opt)) {
if (du->type == TYPE_INTEGER) {
data_integer *di = (data_integer *)du;
dbi_conn_set_option_numeric(dbconn, opt->ptr, di->value);
} else if (du->type == TYPE_STRING) {
data_string *ds = (data_string *)du;
if (ds->value != sqlquery && ds->value != dbtype) {
dbi_conn_set_option(dbconn, opt->ptr, ds->value->ptr);
}
}
}
}
dbconf = (vhostdb_config *)calloc(1, sizeof(*dbconf));
dbconf->dbinst = dbinst;
dbconf->dbconn = dbconn;
dbconf->sqlquery = sqlquery;
dbconf->srv = srv;
dbconf->reconnect_count = 0;
*vdata = dbconf;
/* used to automatically reconnect to the database */
dbi_conn_error_handler(dbconn, mod_vhostdb_dbi_error_callback, dbconf);
/* connect to database */
mod_vhostdb_dbi_error_callback(dbconn, dbconf);
if (dbconf->reconnect_count >= 3) return -1;
}
return 0;
}
static void mod_vhostdb_patch_connection (server *srv, connection *con, plugin_data *p);
static int mod_vhostdb_dbi_query(server *srv, connection *con, void *p_d, buffer *docroot)
{
plugin_data *p = (plugin_data *)p_d;
vhostdb_config *dbconf;
dbi_result result;
unsigned long long nrows;
int retry_count = 0;
/*(reuse buffer for sql query before generating docroot result)*/
buffer *sqlquery = docroot;
buffer_string_set_length(sqlquery, 0); /*(also resets docroot (alias))*/
mod_vhostdb_patch_connection(srv, con, p);
if (NULL == p->conf.vdata) return 0; /*(after resetting docroot)*/
dbconf = (vhostdb_config *)p->conf.vdata;
for (char *b = dbconf->sqlquery->ptr, *d; *b; b = d+1) {
if (NULL != (d = strchr(b, '?'))) {
/* escape the uri.authority */
char *esc = NULL;
size_t len = dbi_conn_escape_string_copy(dbconf->dbconn, con->uri.authority->ptr, &esc);
buffer_append_string_len(sqlquery, b, (size_t)(d - b));
buffer_append_string_len(sqlquery, esc, len);
free(esc);
if (0 == len) return -1;
} else {
d = dbconf->sqlquery->ptr + buffer_string_length(dbconf->sqlquery);
buffer_append_string_len(sqlquery, b, (size_t)(d - b));
break;
}
}
/* reset our reconnect-attempt counter, this is a new query. */
dbconf->reconnect_count = 0;
do {
result = dbi_conn_query(dbconf->dbconn, sqlquery->ptr);
} while (!result && ++retry_count < 2);
buffer_string_set_length(docroot, 0); /*(reset buffer to store result)*/
if (!result) {
const char *errmsg;
dbi_conn_error(dbconf->dbconn, &errmsg);
log_error_write(srv, __FILE__, __LINE__, "s", errmsg);
return -1;
}
nrows = dbi_result_get_numrows(result);
if (nrows && nrows != DBI_ROW_ERROR && dbi_result_next_row(result)) {
buffer_copy_string(docroot, dbi_result_get_string_idx(result, 1));
} /* else no such virtual host */
dbi_result_free(result);
return 0;
}
INIT_FUNC(mod_vhostdb_init) {
static http_vhostdb_backend_t http_vhostdb_backend_dbi =
{ "dbi", mod_vhostdb_dbi_query, NULL };
plugin_data *p = calloc(1, sizeof(*p));
/* register http_vhostdb_backend_dbi */
http_vhostdb_backend_dbi.p_d = p;
http_vhostdb_backend_set(&http_vhostdb_backend_dbi);
return p;
}
FREE_FUNC(mod_vhostdb_cleanup) {
plugin_data *p = p_d;
if (!p) return HANDLER_GO_ON;
if (p->config_storage) {
for (size_t i = 0; i < srv->config_context->used; i++) {
plugin_config *s = p->config_storage[i];
if (!s) continue;
mod_vhostdb_dbconf_free(s->vdata);
array_free(s->options);
free(s);
}
free(p->config_storage);
}
free(p);
UNUSED(srv);
return HANDLER_GO_ON;
}
SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) {
plugin_data *p = p_d;
config_values_t cv[] = {
{ "vhostdb.dbi", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
};
p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
for (size_t i = 0; i < srv->config_context->used; ++i) {
data_config const *config = (data_config const*)srv->config_context->data[i];
plugin_config *s = calloc(1, sizeof(plugin_config));
s->options = array_init();
cv[0].destination = s->options;
p->config_storage[i] = s;
if (config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
return HANDLER_ERROR;
}
if (s->options->used
&& 0 != mod_vhostdb_dbconf_setup(srv, s->options, &s->vdata)) {
return HANDLER_ERROR;
}
}
return HANDLER_GO_ON;
}
#define PATCH(x) \
p->conf.x = s->x;
static void mod_vhostdb_patch_connection (server *srv, connection *con, plugin_data *p)
{
plugin_config *s = p->config_storage[0];
PATCH(vdata);
/* skip the first, the global context */
for (size_t i = 1; i < srv->config_context->used; ++i) {
data_config *dc = (data_config *)srv->config_context->data[i];
s = p->config_storage[i];
/* condition didn't match */
if (!config_check_cond(srv, con, dc)) continue;
/* merge config */
for (size_t j = 0; j < dc->value->used; ++j) {
data_unset *du = dc->value->data[j];
if (buffer_is_equal_string(du->key, CONST_STR_LEN("vhostdb.dbi"))) {
PATCH(vdata);
}
}
}
}
#undef PATCH
/* this function is called at dlopen() time and inits the callbacks */
int mod_vhostdb_dbi_plugin_init (plugin *p);
int mod_vhostdb_dbi_plugin_init (plugin *p)
{
p->version = LIGHTTPD_VERSION_ID;
p->name = buffer_init_string("vhostdb_dbi");
p->init = mod_vhostdb_init;
p->cleanup = mod_vhostdb_cleanup;
p->set_defaults = mod_vhostdb_set_defaults;
return 0;
}

512
src/mod_vhostdb_ldap.c

</
@ -0,0 +1,512 @@
#include "first.h"
#include <ldap.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include "base.h"
#include "http_vhostdb.h"
#include "log.h"
#include "plugin.h"
/*
* virtual host plugin using LDAP for domain to directory lookups
*/
typedef struct {
LDAP *ldap;
buffer *filter;
const char *attr;
const char *host;
const char *basedn;
const char *binddn;
const char *bindpw;
const char *cafile;
unsigned short starttls;
} vhostdb_config;
typedef struct {
void *vdata;
array *options;
} plugin_config;
typedef struct {
PLUGIN_DATA;
plugin_config **config_storage;
plugin_config conf;
} plugin_data;
static void mod_vhostdb_dbconf_free (void *vdata)
{
vhostdb_config *dbconf = (vhostdb_config *)vdata;
if (!dbconf) return;
if (NULL != dbconf->ldap) ldap_unbind_ext_s(dbconf->ldap, NULL, NULL);
free(dbconf);
}
static int mod_vhostdb_dbconf_setup (server *srv, array *opts, void **vdata)
{
buffer *filter = NULL;
const char *attr = "documentRoot";
const char *basedn=NULL,*binddn=NULL,*bindpw=NULL,*host=NULL,*cafile=NULL;
unsigned short starttls = 0;
for (size_t i = 0; i < opts->used; ++i) {
const data_string *ds = (data_string *)opts->data[i];
if (ds->type == TYPE_STRING) {
if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("filter"))) {
filter = ds->value;
} else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("attr"))) {
if (!buffer_string_is_empty(ds->value)) attr = ds->value->ptr;
} else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("host"))) {
host = ds->value->ptr;
} else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("base-dn"))) {
if (!buffer_string_is_empty(ds->value)) basedn = ds->value->ptr;
} else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("bind-dn"))) {
if (!buffer_string_is_empty(ds->value)) binddn = ds->value->ptr;
} else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("bind-pw"))) {
bindpw = ds->value->ptr;
} else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("ca-file"))) {
if (!buffer_string_is_empty(ds->value)) cafile = ds->value->ptr;
} else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("starttls"))) {
starttls = !buffer_is_equal_string(ds->value, CONST_STR_LEN("disable"))
&& !buffer_is_equal_string(ds->value, CONST_STR_LEN("0"));
}
}
}
/* required:
* - host
* - filter (LDAP query)
* - base-dn
*
* optional:
* - attr (LDAP attribute with docroot; default "documentRoot")
* - bind-dn
* - bind-pw
* - ca-file
* - starttls
*/
if (!buffer_string_is_empty(filter) && NULL != host && NULL != basedn) {
vhostdb_config *dbconf;
if (NULL == strchr(filter->ptr, '?')) {
log_error_write(srv, __FILE__, __LINE__, "s",
"ldap: filter is missing a replace-operator '?'");
return -1;
}
/* openldap sets FD_CLOEXEC on database socket descriptors
* (still race between creation of socket and fcntl FD_CLOEXEC)
* (YMMV with other LDAP client libraries) */
dbconf = (vhostdb_config *)calloc(1, sizeof(*dbconf));
dbconf->ldap = NULL;
dbconf->filter = filter;
dbconf->attr = attr;
dbconf->host = host;
dbconf->basedn = basedn;
dbconf->binddn = binddn;
dbconf->bindpw = bindpw;
dbconf->cafile = cafile;
dbconf->starttls = starttls;
*vdata = dbconf;
}
return 0;
}
/*
* Note: a large portion of the LDAP code is copied verbatim from mod_authn_ldap
* with only changes being use of vhostdb_config instead of plugin_config struct
* and (const char *) strings in vhostdb_config instead of (buffer *).
*/
static void mod_authn_ldap_err(server *srv, const char *file, unsigned long line, const char *fn, int err)
{
log_error_write(srv,file,line,"sSss","ldap:",fn,":",ldap_err2string(err));
}
static void mod_authn_ldap_opt_err(server *srv, const char *file, unsigned long line, const char *fn, LDAP *ld)
{
int err;
ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &err);
mod_authn_ldap_err(srv, file, line, fn, err);
}
static void mod_authn_append_ldap_filter_escape(buffer * const filter, const buffer * const raw) {
/* [RFC4515] 3. String Search Filter Definition
*
* [...]
*
* The <valueencoding> rule ensures that the entire filter string is a
* valid UTF-8 string and provides that the octets that represent the
* ASCII characters "*" (ASCII 0x2a), "(" (ASCII 0x28), ")" (ASCII
* 0x29), "\" (ASCII 0x5c), and NUL (ASCII 0x00) are represented as a
* backslash "\" (ASCII 0x5c) followed by the two hexadecimal digits
* representing the value of the encoded octet.
*
* [...]
*
* As indicated by the <valueencoding> rule, implementations MUST escape
* all octets greater than 0x7F that are not part of a valid UTF-8
* encoding sequence when they generate a string representation of a
* search filter. Implementations SHOULD accept as input strings that
* are not valid UTF-8 strings. This is necessary because RFC 2254 did
* not clearly define the term "string representation" (and in
* particular did not mention that the string representation of an LDAP
* search filter is a string of UTF-8-encoded Unicode characters).
*
*
* https://www.ldap.com/ldap-filters
* Although not required, you may escape any other characters that you want
* in the assertion value (or substring component) of a filter. This may be
* accomplished by prefixing the hexadecimal representation of each byte of
* the UTF-8 encoding of the character to escape with a backslash character.
*/
const char * const b = raw->ptr;
const size_t rlen = buffer_string_length(raw);
for (size_t i = 0; i < rlen; ++i) {
size_t len = i;
char *f;
do {
/* encode all UTF-8 chars with high bit set
* (instead of validating UTF-8 and escaping only invalid UTF-8) */
if (((unsigned char *)b)[len] > 0x7f)
break;
switch (b[len]) {
default:
continue;
case '\0': case '(': case ')': case '*': case '\\':
break;
}
break;
} while (++len < rlen);
len -= i;
if (len) {
buffer_append_string_len(filter, b+i, len);
if ((i += len) == rlen) break;
}
/* escape * ( ) \ NUL ('\0') (and all UTF-8 chars with high bit set) */
buffer_string_prepare_append(filter, 3);
f = filter->ptr + buffer_string_length(filter);
f[0] = '\\';
f[1] = "0123456789abcdef"[(((unsigned char *)b)[i] >> 4) & 0xf];
f[2] = "0123456789abcdef"[(((unsigned char *)b)[i] ) & 0xf];
buffer_commit(filter, 3);
}
}
static LDAP * mod_authn_ldap_host_init(server *srv, vhostdb_config *s) {
LDAP *ld;
int ret;
ld = ldap_init(s->host, LDAP_PORT);
if (NULL == ld) {
log_error_write(srv, __FILE__, __LINE__, "sss", "ldap:", "ldap_init():",
strerror(errno));
return NULL;
}
ret = LDAP_VERSION3;
ret = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ret);
if (LDAP_OPT_SUCCESS != ret) {
mod_authn_ldap_err(srv, __FILE__, __LINE__, "ldap_set_options()", ret);
ldap_memfree(ld);
return NULL;
}
if (s->starttls) {
/* if no CA file is given, it is ok, as we will use encryption
* if the server requires a CAfile it will tell us */
if (s->cafile) {
ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, s->cafile);
if (LDAP_OPT_SUCCESS != ret) {
mod_authn_ldap_err(srv, __FILE__, __LINE__,
"ldap_set_option(LDAP_OPT_X_TLS_CACERTFILE)",
ret);
ldap_memfree(ld);
return NULL;
}
}
ret = ldap_start_tls_s(ld, NULL, NULL);
if (LDAP_OPT_SUCCESS != ret) {
mod_authn_ldap_err(srv,__FILE__,__LINE__,"ldap_start_tls_s()",ret);
ldap_memfree(ld);
return NULL;
}
}
return ld;
}
static int mod_authn_ldap_bind(server *srv, LDAP *ld, const char *dn, const char *pw) {
#if 0
struct berval creds;
int ret;
if (NULL != pw) {
*((const char **)&creds.bv_val) = pw; /*(cast away const)*/
creds.bv_len = strlen(pw);
} else {
creds.bv_val = NULL;
creds.bv_len = 0;
}
/* RFE: add functionality: LDAP_SASL_EXTERNAL (or GSS-SPNEGO, etc.) */
ret = ldap_sasl_bind_s(ld,dn,LDAP_SASL_SIMPLE,&creds,NULL,NULL,NULL);
if (ret != LDAP_SUCCESS) {
mod_authn_ldap_err(srv, __FILE__, __LINE__, "ldap_sasl_bind_s()", ret);
}
#else
int ret = ldap_simple_bind_s(ld, dn, pw);
if (ret != LDAP_SUCCESS) {
mod_authn_ldap_err(srv, __FILE__, __LINE__, "ldap_simple_bind_s()",ret);
}
#endif
return ret;
}
static LDAPMessage * mod_authn_ldap_search(server *srv, vhostdb_config *s, char *base, char *filter) {
LDAPMessage *lm = NULL;
char *attrs[] = { LDAP_NO_ATTRS, NULL };
int ret;
/*
* 1. connect anonymously (if not already connected)
* (ldap connection is kept open unless connection-level error occurs)
* 2. issue search using filter
*/
if (s->ldap != NULL) {
ret = ldap_search_ext_s(s->ldap, base, LDAP_SCOPE_SUBTREE, filter,
attrs, 0, NULL, NULL, NULL, 0, &lm);
if (LDAP_SUCCESS == ret) {
return lm;
} else if (LDAP_SERVER_DOWN != ret) {
/* try again (or initial request);
* ldap lib sometimes fails for the first call but reconnects */
ret = ldap_search_ext_s(s->ldap, base, LDAP_SCOPE_SUBTREE, filter,
attrs, 0, NULL, NULL, NULL, 0, &lm);
if (LDAP_SUCCESS == ret) {
return lm;
}
}
ldap_unbind_ext_s(s->ldap, NULL, NULL);
}
s->ldap = mod_authn_ldap_host_init(srv, s);
if (NULL == s->ldap) {
return NULL;
}
ret = mod_authn_ldap_bind(srv, s->ldap, s->binddn, s->bindpw);
if (LDAP_SUCCESS != ret) {
ldap_memfree(s->ldap);
s->ldap = NULL;
return NULL;
}
ret = ldap_search_ext_s(s->ldap, base, LDAP_SCOPE_SUBTREE, filter,
attrs, 0, NULL, NULL, NULL, 0, &lm);
if (LDAP_SUCCESS != ret) {
log_error_write(srv, __FILE__, __LINE__, "sSss",
"ldap:", ldap_err2string(ret), "; filter:", filter);
ldap_unbind_ext_s(s->ldap, NULL, NULL);
s->ldap = NULL;
return NULL;
}
return lm;
}
static void mod_vhostdb_patch_connection (server *srv, connection *con, plugin_data *p);
static int mod_vhostdb_ldap_query(server *srv, connection *con, void *p_d, buffer *docroot)
{
plugin_data *p = (plugin_data *)p_d;
vhostdb_config *dbconf;
LDAP *ld;
LDAPMessage *lm, *first;
struct berval **vals;
int count;
char *basedn;
buffer *template;
/*(reuse buffer for ldap query before generating docroot result)*/
buffer *filter = docroot;
buffer_string_set_length(filter, 0); /*(also resets docroot (alias))*/
mod_vhostdb_patch_connection(srv, con, p);
if (NULL == p->conf.vdata) return 0; /*(after resetting docroot)*/
dbconf = (vhostdb_config *)p->conf.vdata;
template = dbconf->filter;
for (char *b = template->ptr, *d; *b; b = d+1) {
if (NULL != (d = strchr(b, '?'))) {
buffer_append_string_len(filter, b, (size_t)(d - b));
mod_authn_append_ldap_filter_escape(filter, con->uri.authority);
} else {
d = template->ptr + buffer_string_length(template);
buffer_append_string_len(filter, b, (size_t)(d - b));
break;
}
}
/* (cast away const for poor LDAP ldap_search_ext_s() prototype) */
*(const char **)&basedn = dbconf->basedn;
/* ldap_search (synchronous; blocking) */
lm = mod_authn_ldap_search(srv, dbconf, basedn, filter->ptr);
if (NULL == lm) {
return -1;
}
/*(must be after mod_authn_ldap_search(); might reconnect)*/
ld = dbconf->ldap;
count = ldap_count_entries(ld, lm);
if (count > 1) {
log_error_write(srv, __FILE__, __LINE__, "ssb",
"ldap:", "more than one record returned. "
"you might have to refine the filter:", filter);
}
buffer_string_set_length(docroot, 0); /*(reset buffer to store result)*/
if (0 == count) { /*(no entries found)*/
ldap_msgfree(lm);
return 0;
}
if (NULL == (first = ldap_first_entry(ld, lm))) {
mod_authn_ldap_opt_err(srv,__FILE__,__LINE__,"ldap_first_entry()",ld);
ldap_msgfree(lm);
return -1;
}
if (NULL == (vals = ldap_get_values_len(ld, first, dbconf->attr))) {
buffer_copy_string_len(docroot, vals[0]->bv_val, vals[0]->bv_len);
ldap_value_free_len(vals);
}
ldap_msgfree(lm);
return 0;
}
INIT_FUNC(mod_vhostdb_init) {
static http_vhostdb_backend_t http_vhostdb_backend_ldap =
{ "ldap", mod_vhostdb_ldap_query, NULL };
plugin_data *p = calloc(1, sizeof(*p));
/* register http_vhostdb_backend_ldap */
http_vhostdb_backend_ldap.p_d = p;
http_vhostdb_backend_set(&http_vhostdb_backend_ldap);
return p;
}
FREE_FUNC(mod_vhostdb_cleanup) {
plugin_data *p = p_d;
if (!p) return HANDLER_GO_ON;
if (p->config_storage) {
for (size_t i = 0; i < srv->config_context->used; i++) {
plugin_config *s = p->config_storage[i];
if (!s) continue;
mod_vhostdb_dbconf_free(s->vdata);
array_free(s->options);
free(s);
}
free(p->config_storage);
}
free(p);
UNUSED(srv);
return HANDLER_GO_ON;
}
SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) {