From 2f83aac9fb757e5676120d44a07c1c5be1cf83c0 Mon Sep 17 00:00:00 2001 From: Glenn Strauss Date: Wed, 18 Jan 2017 00:36:49 -0500 Subject: [PATCH] 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 --- SConstruct | 17 ++ configure.ac | 101 +++++++- src/CMakeLists.txt | 50 +++- src/Makefile.am | 54 ++++- src/SConscript | 10 + src/http_vhostdb.c | 30 +++ src/http_vhostdb.h | 18 ++ src/mod_vhostdb.c | 238 +++++++++++++++++++ src/mod_vhostdb_dbi.c | 324 +++++++++++++++++++++++++ src/mod_vhostdb_ldap.c | 512 ++++++++++++++++++++++++++++++++++++++++ src/mod_vhostdb_mysql.c | 288 ++++++++++++++++++++++ src/mod_vhostdb_pgsql.c | 266 +++++++++++++++++++++ src/server.c | 10 + 13 files changed, 1907 insertions(+), 11 deletions(-) create mode 100644 src/http_vhostdb.c create mode 100644 src/http_vhostdb.h create mode 100644 src/mod_vhostdb.c create mode 100644 src/mod_vhostdb_dbi.c create mode 100644 src/mod_vhostdb_ldap.c create mode 100644 src/mod_vhostdb_mysql.c create mode 100644 src/mod_vhostdb_pgsql.c diff --git a/SConstruct b/SConstruct index 015ac633..0d65d8b3 100644 --- a/SConstruct +++ b/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']): diff --git a/configure.ac b/configure.ac index 640671fd..3486002f 100644 --- a/configure.ac +++ b/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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7a1e4ff7..b37b192b 100644 --- a/src/CMakeLists.txt +++ b/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) diff --git a/src/Makefile.am b/src/Makefile.am index 7e2390a5..b8e2593f 100644 --- a/src/Makefile.am +++ b/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) diff --git a/src/SConscript b/src/SConscript index f7c5040c..1353f2b9 100644 --- a/src/SConscript +++ b/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'] ] } diff --git a/src/http_vhostdb.c b/src/http_vhostdb.c new file mode 100644 index 00000000..1751d1a6 --- /dev/null +++ b/src/http_vhostdb.c @@ -0,0 +1,30 @@ +#include "first.h" + +#include "http_vhostdb.h" + +#include + + +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)); +} diff --git a/src/http_vhostdb.h b/src/http_vhostdb.h new file mode 100644 index 00000000..90f74c5e --- /dev/null +++ b/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 diff --git a/src/mod_vhostdb.c b/src/mod_vhostdb.c new file mode 100644 index 00000000..016cf043 --- /dev/null +++ b/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 +#include +#include + +/** + * 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; +} diff --git a/src/mod_vhostdb_dbi.c b/src/mod_vhostdb_dbi.c new file mode 100644 index 00000000..f6790b4a --- /dev/null +++ b/src/mod_vhostdb_dbi.c @@ -0,0 +1,324 @@ +#include "first.h" + +#include + +#include +#include + +#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; +} diff --git a/src/mod_vhostdb_ldap.c b/src/mod_vhostdb_ldap.c new file mode 100644 index 00000000..4fbb29e7 --- /dev/null +++ b/src/mod_vhostdb_ldap.c @@ -0,0 +1,512 @@ +#include "first.h" + +#include + +#include +#include +#include + +#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 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 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) { + plugin_data *p = p_d; + + config_values_t cv[] = { + { "vhostdb.ldap", 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.ldap"))) { + PATCH(vdata); + } + } + } +} +#undef PATCH + +/* this function is called at dlopen() time and inits the callbacks */ +int mod_vhostdb_ldap_plugin_init (plugin *p); +int mod_vhostdb_ldap_plugin_init (plugin *p) +{ + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("vhostdb_ldap"); + + p->init = mod_vhostdb_init; + p->cleanup = mod_vhostdb_cleanup; + p->set_defaults = mod_vhostdb_set_defaults; + + return 0; +} diff --git a/src/mod_vhostdb_mysql.c b/src/mod_vhostdb_mysql.c new file mode 100644 index 00000000..5490cef6 --- /dev/null +++ b/src/mod_vhostdb_mysql.c @@ -0,0 +1,288 @@ +#include "first.h" + +#include + +#include +#include + +#include "base.h" +#include "http_vhostdb.h" +#include "log.h" +#include "plugin.h" + +/* + * virtual host plugin using MySQL for domain to directory lookups + */ + +typedef struct { + MYSQL *dbconn; + buffer *sqlquery; +} 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; + mysql_close(dbconf->dbconn); + free(dbconf); +} + +static int mod_vhostdb_dbconf_setup (server *srv, array *opts, void **vdata) +{ + buffer *sqlquery = NULL; + const char *dbname=NULL, *user=NULL, *pass=NULL, *host=NULL, *sock=NULL; + unsigned int port = 0; + + for (size_t i = 0; i < opts->used; ++i) { + const data_string *ds = (data_string *)opts->data[i]; + if (ds->type == TYPE_STRING && !buffer_string_is_empty(ds->value)) { + 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->ptr; + } else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("user"))) { + user = ds->value->ptr; + } else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("password"))) { + pass = 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("port"))) { + port = strtoul(ds->value->ptr, NULL, 10); + } else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("sock"))) { + sock = ds->value->ptr; + } + } + } + + /* required: + * - sql (sql query) + * - dbname + * - user + * + * optional: + * - password, default: empty + * - socket, default: mysql default + * - hostname, if set overrides socket + * - port, default: 3306 + */ + + if (!buffer_string_is_empty(sqlquery) + && dbname && *dbname && user && *user) { + vhostdb_config *dbconf; + MYSQL *dbconn = mysql_init(NULL); + if (NULL == dbconn) { + log_error_write(srv, __FILE__, __LINE__, "s", + "mysql_init() failed, exiting..."); + return -1; + } + + #if MYSQL_VERSION_ID >= 50013 + /* in mysql versions above 5.0.3 the reconnect flag is off by default */ + { + my_bool reconnect = 1; + mysql_options(dbconn, MYSQL_OPT_RECONNECT, &reconnect); + } + #endif + + /* CLIENT_MULTI_STATEMENTS first appeared in 4.1 */ + #if MYSQL_VERSION_ID < 40100 + #ifndef CLIENT_MULTI_STATEMENTS + #define CLIENT_MULTI_STATEMENTS 0 + #endif + #endif + if (!mysql_real_connect(dbconn, host, user, pass, dbname, port, sock, + CLIENT_MULTI_STATEMENTS)) { + log_error_write(srv, __FILE__, __LINE__, "s", + mysql_error(dbconn)); + mysql_close(dbconn); + return -1; + } + + fd_close_on_exec(dbconn->net.fd); + + dbconf = (vhostdb_config *)calloc(1, sizeof(*dbconf)); + dbconf->dbconn = dbconn; + dbconf->sqlquery = sqlquery; + *vdata = dbconf; + } + + return 0; +} + +static void mod_vhostdb_patch_connection (server *srv, connection *con, plugin_data *p); + +static int mod_vhostdb_mysql_query(server *srv, connection *con, void *p_d, buffer *docroot) +{ + plugin_data *p = (plugin_data *)p_d; + vhostdb_config *dbconf; + unsigned cols; + MYSQL_ROW row; + MYSQL_RES *result; + + /*(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 */ + unsigned long len; + buffer_append_string_len(sqlquery, b, (size_t)(d - b)); + buffer_string_prepare_append(sqlquery, buffer_string_length(con->uri.authority) * 2); + len = mysql_real_escape_string(dbconf->dbconn, + sqlquery->ptr + buffer_string_length(sqlquery), + CONST_BUF_LEN(con->uri.authority)); + if ((unsigned long)~0 == len) return -1; + buffer_commit(sqlquery, len); + } else { + d = dbconf->sqlquery->ptr + buffer_string_length(dbconf->sqlquery); + buffer_append_string_len(sqlquery, b, (size_t)(d - b)); + break; + } + } + + if (mysql_real_query(dbconf->dbconn, CONST_BUF_LEN(sqlquery))) { + log_error_write(srv, __FILE__, __LINE__, "s", + mysql_error(dbconf->dbconn)); + buffer_string_set_length(docroot, 0); /*(reset buffer; no result)*/ + return -1; + } + + buffer_string_set_length(docroot, 0); /*(reset buffer to store result)*/ + + result = mysql_store_result(dbconf->dbconn); + cols = mysql_num_fields(result); + row = mysql_fetch_row(result); + if (row && cols >= 1) { + buffer_copy_string(docroot, row[0]); + } /* else no such virtual host */ + + mysql_free_result(result); + #if MYSQL_VERSION_ID >= 40100 + while (0 == mysql_next_result(dbconf->dbconn)) ; + #endif + return 0; +} + + + + +INIT_FUNC(mod_vhostdb_init) { + static http_vhostdb_backend_t http_vhostdb_backend_mysql = + { "mysql", mod_vhostdb_mysql_query, NULL }; + plugin_data *p = calloc(1, sizeof(*p)); + + /* register http_vhostdb_backend_mysql */ + http_vhostdb_backend_mysql.p_d = p; + http_vhostdb_backend_set(&http_vhostdb_backend_mysql); + + 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.mysql", 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.mysql"))){ + PATCH(vdata); + } + } + } +} +#undef PATCH + +/* this function is called at dlopen() time and inits the callbacks */ +int mod_vhostdb_mysql_plugin_init (plugin *p); +int mod_vhostdb_mysql_plugin_init (plugin *p) +{ + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("vhostdb_mysql"); + + p->init = mod_vhostdb_init; + p->cleanup = mod_vhostdb_cleanup; + p->set_defaults = mod_vhostdb_set_defaults; + + return 0; +} diff --git a/src/mod_vhostdb_pgsql.c b/src/mod_vhostdb_pgsql.c new file mode 100644 index 00000000..8378cb5f --- /dev/null +++ b/src/mod_vhostdb_pgsql.c @@ -0,0 +1,266 @@ +#include "first.h" + +#include + +#include +#include + +#include "base.h" +#include "http_vhostdb.h" +#include "log.h" +#include "plugin.h" + +/* + * virtual host plugin using Postgres for domain to directory lookups + */ + +typedef struct { + PGconn *dbconn; + buffer *sqlquery; +} 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; + PQfinish(dbconf->dbconn); + free(dbconf); +} + +static int mod_vhostdb_dbconf_setup (server *srv, array *opts, void **vdata) +{ + buffer *sqlquery = NULL; + const char *dbname=NULL, *user=NULL, *pass=NULL, *host=NULL, *port=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->ptr; + } else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("user"))) { + user = ds->value->ptr; + } else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("password"))) { + pass = 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("port"))) { + port = ds->value->ptr; + } + } + } + + /* required: + * - sql (sql query) + * - dbname + * - user (unless dbname is a pgsql conninfo URI) + * + * optional: + * - password, default: empty + * - hostname + * - port, default: 5432 + */ + + if (!buffer_string_is_empty(sqlquery) && NULL != dbname) { + vhostdb_config *dbconf; + PGconn *dbconn = PQsetdbLogin(host,port,NULL,NULL,dbname,user,pass); + if (NULL == dbconn) { + log_error_write(srv, __FILE__, __LINE__, "s", + "PGsetdbLogin() failed, exiting..."); + return -1; + } + + if (CONNECTION_OK != PQstatus(dbconn)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "Failed to login to database, exiting..."); + PQfinish(dbconn); + return -1; + } + + /* Postgres sets FD_CLOEXEC on database socket descriptors */ + + dbconf = (vhostdb_config *)calloc(1, sizeof(*dbconf)); + dbconf->dbconn = dbconn; + dbconf->sqlquery = sqlquery; + *vdata = dbconf; + } + + return 0; +} + +static void mod_vhostdb_patch_connection (server *srv, connection *con, plugin_data *p); + +static int mod_vhostdb_pgsql_query(server *srv, connection *con, void *p_d, buffer *docroot) +{ + plugin_data *p = (plugin_data *)p_d; + vhostdb_config *dbconf; + PGresult *res; + int cols, rows; + + /*(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 */ + size_t len; + int err; + buffer_append_string_len(sqlquery, b, (size_t)(d - b)); + buffer_string_prepare_append(sqlquery, buffer_string_length(con->uri.authority) * 2); + len = PQescapeStringConn(dbconf->dbconn, + sqlquery->ptr + buffer_string_length(sqlquery), + CONST_BUF_LEN(con->uri.authority), &err); + buffer_commit(sqlquery, len); + if (0 != err) return -1; + } else { + d = dbconf->sqlquery->ptr + buffer_string_length(dbconf->sqlquery); + buffer_append_string_len(sqlquery, b, (size_t)(d - b)); + break; + } + } + + res = PQexec(dbconf->dbconn, sqlquery->ptr); + + buffer_string_set_length(docroot, 0); /*(reset buffer to store result)*/ + + if (PGRES_TUPLES_OK != PQresultStatus(res)) { + log_error_write(srv, __FILE__, __LINE__, "s", + PQerrorMessage(dbconf->dbconn)); + PQclear(res); + return -1; + } + + cols = PQnfields(res); + rows = PQntuples(res); + if (rows == 1 && cols >= 1) { + buffer_copy_string(docroot, PQgetvalue(res, 0, 0)); + } /* else no such virtual host */ + + PQclear(res); + return 0; +} + + + + +INIT_FUNC(mod_vhostdb_init) { + static http_vhostdb_backend_t http_vhostdb_backend_pgsql = + { "pgsql", mod_vhostdb_pgsql_query, NULL }; + plugin_data *p = calloc(1, sizeof(*p)); + + /* register http_vhostdb_backend_pgsql */ + http_vhostdb_backend_pgsql.p_d = p; + http_vhostdb_backend_set(&http_vhostdb_backend_pgsql); + + 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.pgsql", 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.pgsql"))){ + PATCH(vdata); + } + } + } +} +#undef PATCH + +/* this function is called at dlopen() time and inits the callbacks */ +int mod_vhostdb_pgsql_plugin_init (plugin *p); +int mod_vhostdb_pgsql_plugin_init (plugin *p) +{ + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("vhostdb_pgsql"); + + p->init = mod_vhostdb_init; + p->cleanup = mod_vhostdb_cleanup; + p->set_defaults = mod_vhostdb_set_defaults; + + return 0; +} diff --git a/src/server.c b/src/server.c index f0df8d52..ad4914fa 100644 --- a/src/server.c +++ b/src/server.c @@ -623,6 +623,16 @@ static void show_features (void) { #else "\t- MySQL support\n" #endif +#ifdef HAVE_PGSQL + "\t+ PgSQL support\n" +#else + "\t- PgSQL support\n" +#endif +#ifdef HAVE_DBI + "\t+ DBI support\n" +#else + "\t- DBI support\n" +#endif #ifdef HAVE_KRB5 "\t+ Kerberos support\n" #else